explainthisrepo 0.10.0 → 0.20.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.
- package/README.md +13 -3
- package/dist/cli.js +39 -383
- package/dist/native/win-x64/explainthisrepo.exe +0 -0
- package/package.json +2 -9
- package/dist/config.d.ts +0 -5
- package/dist/config.js +0 -46
- package/dist/generate.d.ts +0 -1
- package/dist/generate.js +0 -15
- package/dist/github.d.ts +0 -6
- package/dist/github.js +0 -300
- package/dist/init.d.ts +0 -1
- package/dist/init.js +0 -189
- package/dist/local_reader.d.ts +0 -2
- package/dist/local_reader.js +0 -77
- package/dist/prompt.d.ts +0 -3
- package/dist/prompt.js +0 -127
- package/dist/providers/anthropic.d.ts +0 -17
- package/dist/providers/anthropic.js +0 -68
- package/dist/providers/base.d.ts +0 -8
- package/dist/providers/base.js +0 -6
- package/dist/providers/gemini.d.ts +0 -16
- package/dist/providers/gemini.js +0 -47
- package/dist/providers/groq.d.ts +0 -17
- package/dist/providers/groq.js +0 -70
- package/dist/providers/index.d.ts +0 -1
- package/dist/providers/index.js +0 -1
- package/dist/providers/ollama.d.ts +0 -15
- package/dist/providers/ollama.js +0 -82
- package/dist/providers/openai.d.ts +0 -17
- package/dist/providers/openai.js +0 -57
- package/dist/providers/openrouter.d.ts +0 -17
- package/dist/providers/openrouter.js +0 -72
- package/dist/providers/registry.d.ts +0 -4
- package/dist/providers/registry.js +0 -38
- package/dist/repo_reader.d.ts +0 -13
- package/dist/repo_reader.js +0 -150
- package/dist/stack-detector.d.ts +0 -23
- package/dist/stack-detector.js +0 -54
- package/dist/stack_printer.d.ts +0 -2
- package/dist/stack_printer.js +0 -20
- package/dist/writer.d.ts +0 -1
- package/dist/writer.js +0 -6
package/README.md
CHANGED
|
@@ -28,7 +28,7 @@ Not blind AI summarization.
|
|
|
28
28
|
|
|
29
29
|
## Installation
|
|
30
30
|
|
|
31
|
-
### Option 1: install with pip (
|
|
31
|
+
### Option 1: install with pip (Python source version):
|
|
32
32
|
|
|
33
33
|
Requirements: Python 3.9+
|
|
34
34
|
|
|
@@ -66,7 +66,7 @@ pip install explainthisrepo[groq]
|
|
|
66
66
|
|
|
67
67
|
Replace `owner/repo` with the GitHub repository identifier (e.g., `facebook/react`, `torvalds/linux`).
|
|
68
68
|
|
|
69
|
-
### Option 2: Install with npm
|
|
69
|
+
### Option 2: Install with npm (prebuilt binary, no Python required)
|
|
70
70
|
|
|
71
71
|
Install globally and use forever:
|
|
72
72
|
|
|
@@ -86,6 +86,16 @@ npx explainthisrepo owner/repo
|
|
|
86
86
|
# npx explainthisrepo .
|
|
87
87
|
```
|
|
88
88
|
|
|
89
|
+
## How it works
|
|
90
|
+
|
|
91
|
+
ExplainThisRepo uses a hybrid architecture:
|
|
92
|
+
|
|
93
|
+
- Python → core implementaion (analysis, prompts, providers, output)
|
|
94
|
+
- npm → ships prebuilt native binaries (no Python required)
|
|
95
|
+
- pip → installs the full Python package
|
|
96
|
+
|
|
97
|
+
> The npm and pip versions run the same core engine.
|
|
98
|
+
|
|
89
99
|
### Option 3: Download standalone binary
|
|
90
100
|
|
|
91
101
|
Prebuilt standalone binaries are available for macOS, Linux, and Windows.
|
|
@@ -135,7 +145,7 @@ explainthisrepo init
|
|
|
135
145
|
# or npx explainthisrepo init
|
|
136
146
|
```
|
|
137
147
|
|
|
138
|
-
> For details about how initialization works, see [INIT.md](INIT.md).
|
|
148
|
+
> For details about how initialization works, see [docs/INIT.md](docs/INIT.md).
|
|
139
149
|
|
|
140
150
|
## GitHub token Access (Private Repos & Rate Limits)
|
|
141
151
|
|
package/dist/cli.js
CHANGED
|
@@ -1,395 +1,51 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
|
-
import os from "node:os";
|
|
3
|
-
import process from "node:process";
|
|
4
2
|
import fs from "node:fs";
|
|
5
|
-
import {
|
|
3
|
+
import { spawnSync } from "node:child_process";
|
|
6
4
|
import path from "node:path";
|
|
5
|
+
import process from "node:process";
|
|
7
6
|
import { fileURLToPath } from "node:url";
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
if (target.startsWith("github.com/")) {
|
|
36
|
-
target = "https://" + target;
|
|
37
|
-
}
|
|
38
|
-
if (target.startsWith("http://") || target.startsWith("https://")) {
|
|
39
|
-
const url = new URL(target);
|
|
40
|
-
if (!["github.com", "www.github.com"].includes(url.hostname)) {
|
|
41
|
-
throw new Error("Only GitHub repository URLs are supported");
|
|
42
|
-
}
|
|
43
|
-
const parts = url.pathname.split("/").filter(Boolean);
|
|
44
|
-
if (parts.length < 2) {
|
|
45
|
-
throw new Error("URL must point to a repository");
|
|
46
|
-
}
|
|
47
|
-
return { owner: parts[0], repo: parts[1].replace(/\.git$/, "") };
|
|
48
|
-
}
|
|
49
|
-
const parts = target.split("/");
|
|
50
|
-
if (parts.length === 2 && parts[0] && parts[1]) {
|
|
51
|
-
return { owner: parts[0], repo: parts[1] };
|
|
52
|
-
}
|
|
53
|
-
throw new Error("Invalid format. Use owner/repo or a GitHub repo URL");
|
|
54
|
-
}
|
|
55
|
-
function getPkgVersion() {
|
|
7
|
+
function getTargetKey() {
|
|
8
|
+
const platform = process.platform;
|
|
9
|
+
const arch = process.arch;
|
|
10
|
+
if (platform === "darwin" && arch === "x64")
|
|
11
|
+
return "darwin-x64";
|
|
12
|
+
if (platform === "darwin" && arch === "arm64")
|
|
13
|
+
return "darwin-arm64";
|
|
14
|
+
if (platform === "linux" && arch === "x64")
|
|
15
|
+
return "linux-x64";
|
|
16
|
+
if (platform === "linux" && arch === "arm64")
|
|
17
|
+
return "linux-arm64";
|
|
18
|
+
if (platform === "win32" && arch === "x64")
|
|
19
|
+
return "win-x64";
|
|
20
|
+
throw new Error(`Unsupported platform: ${platform} ${arch}`);
|
|
21
|
+
}
|
|
22
|
+
function getBinaryName() {
|
|
23
|
+
return process.platform === "win32" ? "explainthisrepo.exe" : "explainthisrepo";
|
|
24
|
+
}
|
|
25
|
+
function getBinaryPath() {
|
|
26
|
+
const launcherDir = path.dirname(fileURLToPath(import.meta.url));
|
|
27
|
+
const binaryPath = path.join(launcherDir, "native", getTargetKey(), getBinaryName());
|
|
28
|
+
if (!fs.existsSync(binaryPath)) {
|
|
29
|
+
throw new Error(`Bundled binary not found: ${binaryPath}`);
|
|
30
|
+
}
|
|
31
|
+
return binaryPath;
|
|
32
|
+
}
|
|
33
|
+
function main() {
|
|
56
34
|
try {
|
|
57
|
-
const
|
|
58
|
-
const
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
return pkg.version ?? "unknown";
|
|
62
|
-
}
|
|
63
|
-
catch {
|
|
64
|
-
return "unknown";
|
|
65
|
-
}
|
|
66
|
-
}
|
|
67
|
-
function hasEnv(key) {
|
|
68
|
-
const v = process.env[key];
|
|
69
|
-
return Boolean(v && v.trim());
|
|
70
|
-
}
|
|
71
|
-
async function checkUrl(url, timeoutMs = 6000) {
|
|
72
|
-
const controller = new AbortController();
|
|
73
|
-
const t = setTimeout(() => controller.abort(), timeoutMs);
|
|
74
|
-
try {
|
|
75
|
-
const res = await fetch(url, {
|
|
76
|
-
method: "GET",
|
|
77
|
-
headers: { "User-Agent": "explainthisrepo" },
|
|
78
|
-
signal: controller.signal,
|
|
35
|
+
const binaryPath = getBinaryPath();
|
|
36
|
+
const result = spawnSync(binaryPath, process.argv.slice(2), {
|
|
37
|
+
stdio: "inherit",
|
|
38
|
+
windowsHide: true,
|
|
79
39
|
});
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
}
|
|
83
|
-
catch (e) {
|
|
84
|
-
clearTimeout(t);
|
|
85
|
-
const message = e instanceof Error ? e.message : String(e);
|
|
86
|
-
const name = e instanceof Error ? e.name : "Error";
|
|
87
|
-
return { ok: false, msg: `failed (${name}: ${message})` };
|
|
88
|
-
}
|
|
89
|
-
}
|
|
90
|
-
async function runDoctor(llmOverride) {
|
|
91
|
-
console.log("explainthisrepo doctor report\n");
|
|
92
|
-
console.log(`node: ${process.version}`);
|
|
93
|
-
console.log(`os: ${os.type()} ${os.release()}`);
|
|
94
|
-
console.log(`platform: ${process.platform} ${process.arch}`);
|
|
95
|
-
console.log(`version: ${getPkgVersion()}`);
|
|
96
|
-
console.log("\ngithub auth:");
|
|
97
|
-
if (hasEnv("GITHUB_TOKEN") || hasEnv("GH_TOKEN")) {
|
|
98
|
-
console.log("-token: set");
|
|
99
|
-
}
|
|
100
|
-
else {
|
|
101
|
-
console.log("-token: not set (limited + no private repos)");
|
|
102
|
-
}
|
|
103
|
-
console.log("\nnetwork checks:");
|
|
104
|
-
const gh = await checkUrl("https://api.github.com");
|
|
105
|
-
console.log(`- github api: ${gh.msg}`);
|
|
106
|
-
console.log("\nprovider diagnostics:");
|
|
107
|
-
let providerOk = true;
|
|
108
|
-
try {
|
|
109
|
-
const { getActiveProvider } = await import("./providers/registry.js");
|
|
110
|
-
const provider = await getActiveProvider(llmOverride);
|
|
111
|
-
const providerName = provider.name ?? llmOverride ?? "unknown";
|
|
112
|
-
console.log(`- active provider: ${providerName}`);
|
|
113
|
-
const doctorFn = provider.doctor;
|
|
114
|
-
if (typeof doctorFn === "function") {
|
|
115
|
-
const result = await doctorFn.call(provider);
|
|
116
|
-
if (typeof result === "boolean") {
|
|
117
|
-
console.log(`- ${providerName}: ${result ? "ok" : "checks did not pass"}`);
|
|
118
|
-
providerOk = result;
|
|
119
|
-
}
|
|
120
|
-
else if (Array.isArray(result)) {
|
|
121
|
-
if (result.length === 0) {
|
|
122
|
-
console.log(`- ${providerName}: ok`);
|
|
123
|
-
}
|
|
124
|
-
else {
|
|
125
|
-
for (const line of result) {
|
|
126
|
-
console.log(`- ${providerName}: ${line}`);
|
|
127
|
-
}
|
|
128
|
-
providerOk = false;
|
|
129
|
-
}
|
|
130
|
-
}
|
|
131
|
-
else {
|
|
132
|
-
console.log(`- ${providerName}: ok`);
|
|
133
|
-
}
|
|
40
|
+
if (result.error) {
|
|
41
|
+
throw result.error;
|
|
134
42
|
}
|
|
135
|
-
|
|
136
|
-
console.log(`- ${providerName}: no diagnostics implemented`);
|
|
137
|
-
}
|
|
138
|
-
}
|
|
139
|
-
catch (e) {
|
|
140
|
-
const message = e instanceof Error ? e.message : String(e);
|
|
141
|
-
if (llmOverride) {
|
|
142
|
-
console.log(`- provider '${llmOverride}' could not be resolved`);
|
|
143
|
-
console.log("- check that the provider name is correct and properly configured");
|
|
144
|
-
}
|
|
145
|
-
else {
|
|
146
|
-
console.log(`- provider registry error: ${message}`);
|
|
147
|
-
console.log("- run `explainthisrepo init` to configure a provider");
|
|
148
|
-
}
|
|
149
|
-
providerOk = false;
|
|
150
|
-
}
|
|
151
|
-
return gh.ok && providerOk ? 0 : 1;
|
|
152
|
-
}
|
|
153
|
-
async function safeReadRepoFiles(owner, repo) {
|
|
154
|
-
try {
|
|
155
|
-
return await readRepoSignalFiles(owner, repo);
|
|
43
|
+
process.exit(result.status ?? 1);
|
|
156
44
|
}
|
|
157
|
-
catch (
|
|
158
|
-
const message =
|
|
159
|
-
console.warn(`Warning: Could not read repo files: ${message}`);
|
|
160
|
-
return null;
|
|
161
|
-
}
|
|
162
|
-
}
|
|
163
|
-
async function generateWithExit(prompt, llm) {
|
|
164
|
-
try {
|
|
165
|
-
return await generateExplanation(prompt, llm);
|
|
166
|
-
}
|
|
167
|
-
catch (e) {
|
|
168
|
-
const message = e instanceof Error ? e.message : String(e);
|
|
169
|
-
console.error("Failed to generate explanation.");
|
|
45
|
+
catch (error) {
|
|
46
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
170
47
|
console.error(`error: ${message}`);
|
|
171
|
-
console.error("\nfix:");
|
|
172
|
-
console.error("- Check that the provider name is correct (e.g. gemini, openai, ollama, anthropic, openrouter)");
|
|
173
|
-
console.error("- Ensure your API key is set for the selected provider");
|
|
174
|
-
console.error("- Or run: explainthisrepo --doctor");
|
|
175
48
|
process.exit(1);
|
|
176
49
|
}
|
|
177
50
|
}
|
|
178
|
-
|
|
179
|
-
if (options.doctor) {
|
|
180
|
-
const code = await runDoctor(options.llm);
|
|
181
|
-
process.exit(code);
|
|
182
|
-
}
|
|
183
|
-
const modeFlags = [
|
|
184
|
-
options.quick,
|
|
185
|
-
options.simple,
|
|
186
|
-
options.detailed,
|
|
187
|
-
options.stack,
|
|
188
|
-
].filter(Boolean);
|
|
189
|
-
if (modeFlags.length > 1) {
|
|
190
|
-
console.error("error: only one mode flag can be used at a time");
|
|
191
|
-
process.exit(1);
|
|
192
|
-
}
|
|
193
|
-
const local = fs.existsSync(repository);
|
|
194
|
-
let owner = "";
|
|
195
|
-
let repo = "";
|
|
196
|
-
let localPath = "";
|
|
197
|
-
if (local) {
|
|
198
|
-
localPath = path.resolve(repository);
|
|
199
|
-
console.log(`Analyzing local directory: ${repository}`);
|
|
200
|
-
}
|
|
201
|
-
else {
|
|
202
|
-
try {
|
|
203
|
-
({ owner, repo } = resolveRepoTarget(repository));
|
|
204
|
-
}
|
|
205
|
-
catch (e) {
|
|
206
|
-
const message = e instanceof Error ? e.message : String(e);
|
|
207
|
-
console.error(`error: ${message}`);
|
|
208
|
-
process.exit(1);
|
|
209
|
-
}
|
|
210
|
-
}
|
|
211
|
-
if (options.stack) {
|
|
212
|
-
let read;
|
|
213
|
-
let languages = {};
|
|
214
|
-
if (local) {
|
|
215
|
-
const spinner = ora("Reading repository files…").start();
|
|
216
|
-
try {
|
|
217
|
-
read = readLocalRepoSignalFiles(localPath);
|
|
218
|
-
spinner.stop();
|
|
219
|
-
}
|
|
220
|
-
catch (e) {
|
|
221
|
-
spinner.stop();
|
|
222
|
-
const message = e instanceof Error ? e.message : String(e);
|
|
223
|
-
console.error(`error: ${message}`);
|
|
224
|
-
process.exit(1);
|
|
225
|
-
}
|
|
226
|
-
}
|
|
227
|
-
else {
|
|
228
|
-
const spinner = ora(`Fetching ${owner}/${repo}…`).start();
|
|
229
|
-
try {
|
|
230
|
-
languages = await fetchLanguages(owner, repo);
|
|
231
|
-
read = await readRepoSignalFiles(owner, repo);
|
|
232
|
-
spinner.stop();
|
|
233
|
-
}
|
|
234
|
-
catch (e) {
|
|
235
|
-
spinner.stop();
|
|
236
|
-
const message = e instanceof Error ? e.message : String(e);
|
|
237
|
-
console.error(`error: ${message}`);
|
|
238
|
-
process.exit(1);
|
|
239
|
-
}
|
|
240
|
-
}
|
|
241
|
-
const report = detectStack({
|
|
242
|
-
languages,
|
|
243
|
-
tree: read.tree,
|
|
244
|
-
keyFiles: read.keyFiles,
|
|
245
|
-
});
|
|
246
|
-
const label = local ? repository : owner;
|
|
247
|
-
const sublabel = local ? "" : repo;
|
|
248
|
-
printStack(report, label, sublabel);
|
|
249
|
-
return;
|
|
250
|
-
}
|
|
251
|
-
let repoData = null;
|
|
252
|
-
let readme = null;
|
|
253
|
-
if (!local) {
|
|
254
|
-
const spinner = ora(`Fetching ${owner}/${repo}…`).start();
|
|
255
|
-
try {
|
|
256
|
-
repoData = await fetchRepo(owner, repo);
|
|
257
|
-
readme = await fetchReadme(owner, repo);
|
|
258
|
-
spinner.stop();
|
|
259
|
-
}
|
|
260
|
-
catch (e) {
|
|
261
|
-
spinner.stop();
|
|
262
|
-
const message = e instanceof Error ? e.message : String(e);
|
|
263
|
-
console.error("Failed to fetch repository data.");
|
|
264
|
-
console.error(`error: ${message}`);
|
|
265
|
-
console.error("\nfix:");
|
|
266
|
-
console.error("- Run explainthisrepo init");
|
|
267
|
-
console.error("- Or set GITHUB_TOKEN to avoid rate limits");
|
|
268
|
-
process.exit(1);
|
|
269
|
-
}
|
|
270
|
-
}
|
|
271
|
-
if (options.quick) {
|
|
272
|
-
let quickReadme = readme;
|
|
273
|
-
const repoName = local ? localPath : (repoData?.full_name ?? "");
|
|
274
|
-
const description = local
|
|
275
|
-
? null
|
|
276
|
-
: (repoData?.description ?? null);
|
|
277
|
-
if (local) {
|
|
278
|
-
const spinner = ora("Reading repository files…").start();
|
|
279
|
-
try {
|
|
280
|
-
const read = readLocalRepoSignalFiles(localPath);
|
|
281
|
-
spinner.stop();
|
|
282
|
-
const readmeKey = Object.keys(read.keyFiles).find((k) => k.toLowerCase().startsWith("readme"));
|
|
283
|
-
quickReadme = readmeKey !== undefined ? read.keyFiles[readmeKey] : null;
|
|
284
|
-
}
|
|
285
|
-
catch (e) {
|
|
286
|
-
spinner.stop();
|
|
287
|
-
throw e;
|
|
288
|
-
}
|
|
289
|
-
}
|
|
290
|
-
const prompt = buildQuickPrompt(repoName, description, quickReadme);
|
|
291
|
-
const spinner = ora("Generating explanation…").start();
|
|
292
|
-
const output = await generateWithExit(prompt, options.llm).finally(() => spinner.stop());
|
|
293
|
-
console.log("Quick summary 🎉");
|
|
294
|
-
console.log(output.trim());
|
|
295
|
-
return;
|
|
296
|
-
}
|
|
297
|
-
if (options.simple) {
|
|
298
|
-
let readResult;
|
|
299
|
-
const spinner = ora("Reading repository files…").start();
|
|
300
|
-
try {
|
|
301
|
-
readResult = local
|
|
302
|
-
? readLocalRepoSignalFiles(localPath)
|
|
303
|
-
: await safeReadRepoFiles(owner, repo);
|
|
304
|
-
spinner.stop();
|
|
305
|
-
}
|
|
306
|
-
catch (e) {
|
|
307
|
-
spinner.stop();
|
|
308
|
-
throw e;
|
|
309
|
-
}
|
|
310
|
-
const prompt = buildSimplePrompt(local ? localPath : (repoData?.full_name ?? ""), local ? null : (repoData?.description ?? null), local ? null : readme, readResult?.treeText ?? null);
|
|
311
|
-
const genSpinner = ora("Generating explanation…").start();
|
|
312
|
-
const output = await generateWithExit(prompt, options.llm).finally(() => genSpinner.stop());
|
|
313
|
-
console.log("Simple summary 🎉");
|
|
314
|
-
console.log(output.trim());
|
|
315
|
-
return;
|
|
316
|
-
}
|
|
317
|
-
let readResult;
|
|
318
|
-
const readSpinner = ora("Reading repository files…").start();
|
|
319
|
-
try {
|
|
320
|
-
readResult = local
|
|
321
|
-
? readLocalRepoSignalFiles(localPath)
|
|
322
|
-
: await safeReadRepoFiles(owner, repo);
|
|
323
|
-
readSpinner.stop();
|
|
324
|
-
}
|
|
325
|
-
catch (e) {
|
|
326
|
-
readSpinner.stop();
|
|
327
|
-
throw e;
|
|
328
|
-
}
|
|
329
|
-
const prompt = buildPrompt(local ? localPath : (repoData?.full_name ?? ""), local ? null : (repoData?.description ?? null), local ? null : readme, options.detailed || false, readResult?.treeText ?? null, readResult?.filesText ?? null);
|
|
330
|
-
const genSpinner = ora("Generating explanation…").start();
|
|
331
|
-
const output = await generateWithExit(prompt, options.llm).finally(() => genSpinner.stop());
|
|
332
|
-
console.log("Writing EXPLAIN.md...");
|
|
333
|
-
writeOutput(output);
|
|
334
|
-
const wordCount = output.split(/\s+/).filter(Boolean).length;
|
|
335
|
-
console.log("EXPLAIN.md generated successfully 🎉");
|
|
336
|
-
console.log(`Words: ${wordCount}`);
|
|
337
|
-
console.log("Open EXPLAIN.md to read it.");
|
|
338
|
-
}
|
|
339
|
-
const program = new Command();
|
|
340
|
-
program
|
|
341
|
-
.name("explainthisrepo")
|
|
342
|
-
.description("The fastest way to understand any codebase in plain English")
|
|
343
|
-
.version(getPkgVersion(), "-v, --version", "Show version")
|
|
344
|
-
.argument("[repository]", "GitHub repository (owner/repo or URL) or local directories")
|
|
345
|
-
.option("--doctor", "Run diagnostics")
|
|
346
|
-
.option("--quick", "Quick summary mode")
|
|
347
|
-
.option("--simple", "Simple summary mode")
|
|
348
|
-
.option("--detailed", "Detailed explanation mode")
|
|
349
|
-
.option("--stack", "Stack detection mode")
|
|
350
|
-
.option("--llm <provider>", "LLM provider to use (e.g. gemini, openai, ollama, anthropic, openrouter). Overrides config default.")
|
|
351
|
-
.addHelpText("after", `
|
|
352
|
-
Examples:
|
|
353
|
-
$ explainthisrepo owner/repo
|
|
354
|
-
$ explainthisrepo https://github.com/owner/repo
|
|
355
|
-
$ explainthisrepo github.com/owner/repo
|
|
356
|
-
$ explainthisrepo git@github.com:owner/repo.git
|
|
357
|
-
$ explainthisrepo owner/repo --detailed
|
|
358
|
-
$ explainthisrepo owner/repo --quick
|
|
359
|
-
$ explainthisrepo owner/repo --simple
|
|
360
|
-
$ explainthisrepo owner/repo --stack
|
|
361
|
-
$ explainthisrepo owner/repo --llm gemini
|
|
362
|
-
$ explainthisrepo owner/repo --llm openai
|
|
363
|
-
$ explainthisrepo owner/repo --llm ollama
|
|
364
|
-
$ explainthisrepo .
|
|
365
|
-
$ explainthisrepo ./path/to/directory
|
|
366
|
-
$ explainthisrepo . --stack
|
|
367
|
-
$ explainthisrepo --doctor
|
|
368
|
-
$ explainthisrepo --doctor --llm gemini
|
|
369
|
-
$ explainthisrepo --doctor --llm openai
|
|
370
|
-
$ explainthisrepo --doctor --llm ollama
|
|
371
|
-
$ explainthisrepo --version
|
|
372
|
-
$ GitHub token:
|
|
373
|
-
$ Access private repos and higher rate limits
|
|
374
|
-
$ Run:
|
|
375
|
-
$ explainthisrepo init
|
|
376
|
-
$ Or set:
|
|
377
|
-
$ GITHUB_TOKEN=ghp_xxx explainthisrepo owner/repo`)
|
|
378
|
-
.action(async (repository, options) => {
|
|
379
|
-
if (options.doctor) {
|
|
380
|
-
const code = await runDoctor(options.llm);
|
|
381
|
-
process.exit(code);
|
|
382
|
-
}
|
|
383
|
-
if (!repository) {
|
|
384
|
-
program.error("repository argument required (or use `explainthisrepo init` to configure a provider)");
|
|
385
|
-
return;
|
|
386
|
-
}
|
|
387
|
-
await runAnalysis(repository, options);
|
|
388
|
-
});
|
|
389
|
-
program
|
|
390
|
-
.command("init")
|
|
391
|
-
.description("Configure your LLM provider (Gemini, OpenAI, or Ollama)")
|
|
392
|
-
.action(async () => {
|
|
393
|
-
await runInit();
|
|
394
|
-
});
|
|
395
|
-
program.parseAsync(process.argv);
|
|
51
|
+
main();
|
|
Binary file
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "explainthisrepo",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.20.0",
|
|
4
4
|
"description": "The fastest way to understand any codebase in plain English. Not blind AI summarization",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"type": "module",
|
|
@@ -46,14 +46,7 @@
|
|
|
46
46
|
"node": ">=20"
|
|
47
47
|
},
|
|
48
48
|
"dependencies": {
|
|
49
|
-
"
|
|
50
|
-
"@iarna/toml": "^2.2.5",
|
|
51
|
-
"axios": "^1.13.2",
|
|
52
|
-
"commander": "^14.0.3",
|
|
53
|
-
"dotenv": "^17.2.3",
|
|
54
|
-
"openai": "^4.0.0",
|
|
55
|
-
"ora": "^9.3.0",
|
|
56
|
-
"toml": "^3.0.0"
|
|
49
|
+
"commander": "^14.0.3"
|
|
57
50
|
},
|
|
58
51
|
"devDependencies": {
|
|
59
52
|
"@types/node": "^22.0.0",
|
package/dist/config.d.ts
DELETED
package/dist/config.js
DELETED
|
@@ -1,46 +0,0 @@
|
|
|
1
|
-
import fs from "node:fs";
|
|
2
|
-
import os from "node:os";
|
|
3
|
-
import path from "node:path";
|
|
4
|
-
import { parse } from "@iarna/toml";
|
|
5
|
-
const CONFIG_DIR_NAME = "ExplainThisRepo";
|
|
6
|
-
const CONFIG_FILE_NAME = "config.toml";
|
|
7
|
-
export function getConfigPath() {
|
|
8
|
-
if (process.platform === "win32") {
|
|
9
|
-
const appdata = process.env.APPDATA;
|
|
10
|
-
if (!appdata) {
|
|
11
|
-
throw new Error("APPDATA environment variable is not set");
|
|
12
|
-
}
|
|
13
|
-
return path.join(appdata, CONFIG_DIR_NAME, CONFIG_FILE_NAME);
|
|
14
|
-
}
|
|
15
|
-
const xdg = process.env.XDG_CONFIG_HOME;
|
|
16
|
-
const base = xdg ?? path.join(os.homedir(), ".config");
|
|
17
|
-
return path.join(base, "explainthisrepo", CONFIG_FILE_NAME);
|
|
18
|
-
}
|
|
19
|
-
export function ensureConfigDir() {
|
|
20
|
-
const configPath = getConfigPath();
|
|
21
|
-
const dir = path.dirname(configPath);
|
|
22
|
-
fs.mkdirSync(dir, { recursive: true });
|
|
23
|
-
return configPath;
|
|
24
|
-
}
|
|
25
|
-
export function writeConfig(contents) {
|
|
26
|
-
const path = ensureConfigDir();
|
|
27
|
-
fs.writeFileSync(path, contents, { encoding: "utf-8" });
|
|
28
|
-
}
|
|
29
|
-
export function readConfig() {
|
|
30
|
-
const path = getConfigPath();
|
|
31
|
-
if (!fs.existsSync(path))
|
|
32
|
-
return null;
|
|
33
|
-
return fs.readFileSync(path, "utf-8");
|
|
34
|
-
}
|
|
35
|
-
export function loadConfig() {
|
|
36
|
-
const raw = readConfig();
|
|
37
|
-
if (!raw) {
|
|
38
|
-
return {};
|
|
39
|
-
}
|
|
40
|
-
try {
|
|
41
|
-
return parse(raw);
|
|
42
|
-
}
|
|
43
|
-
catch (err) {
|
|
44
|
-
throw new Error("Invalid config.toml format");
|
|
45
|
-
}
|
|
46
|
-
}
|
package/dist/generate.d.ts
DELETED
|
@@ -1 +0,0 @@
|
|
|
1
|
-
export declare function generateExplanation(prompt: string, providerOverride?: string): Promise<string>;
|
package/dist/generate.js
DELETED
|
@@ -1,15 +0,0 @@
|
|
|
1
|
-
import { getActiveProvider } from "./providers/registry.js";
|
|
2
|
-
export async function generateExplanation(prompt, providerOverride) {
|
|
3
|
-
const provider = getActiveProvider(providerOverride);
|
|
4
|
-
try {
|
|
5
|
-
const output = await provider.generate(prompt);
|
|
6
|
-
if (!output || !output.trim()) {
|
|
7
|
-
throw new Error(`${provider.name} returned no output`);
|
|
8
|
-
}
|
|
9
|
-
return output.trim();
|
|
10
|
-
}
|
|
11
|
-
catch (err) {
|
|
12
|
-
const message = err?.message ? String(err.message) : String(err);
|
|
13
|
-
throw new Error(`${provider.name} generation failed: ${message}`);
|
|
14
|
-
}
|
|
15
|
-
}
|
package/dist/github.d.ts
DELETED
|
@@ -1,6 +0,0 @@
|
|
|
1
|
-
export type RepoLanguageMap = Record<string, number>;
|
|
2
|
-
export declare function fetchRepo(owner: string, repo: string, token?: string): Promise<any>;
|
|
3
|
-
export declare function fetchReadme(owner: string, repo: string, token?: string): Promise<string | null>;
|
|
4
|
-
export declare function fetchTree(owner: string, repo: string, token?: string): Promise<any[]>;
|
|
5
|
-
export declare function fetchFile(owner: string, repo: string, filePath: string, token?: string): Promise<string | null>;
|
|
6
|
-
export declare function fetchLanguages(owner: string, repo: string, token?: string): Promise<RepoLanguageMap>;
|