explainthisrepo 0.9.6 → 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 +55 -14
- package/dist/cli.js +39 -371
- 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 -128
- package/dist/init.d.ts +0 -1
- package/dist/init.js +0 -174
- 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 -148
- 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
|
@@ -20,6 +20,7 @@ ExplainThisRepo analyzes real project signals; configs, entrypoints, manifests,
|
|
|
20
20
|
- Derives architectural summaries from repository structure and code signals.
|
|
21
21
|
Not blind AI summarization.
|
|
22
22
|
- Translates complex code structures into plain English
|
|
23
|
+
- Speeds up understanding of unfamiliar codebases
|
|
23
24
|
- Extract architecture signals from configs, entrypoints, and manifests
|
|
24
25
|
- Works with GitHub repositories, local directories, private repositories, and monorepos
|
|
25
26
|
- Outputs the explanation to an `EXPLAIN.md` file in your current directory or prints it directly in the terminal
|
|
@@ -27,7 +28,7 @@ Not blind AI summarization.
|
|
|
27
28
|
|
|
28
29
|
## Installation
|
|
29
30
|
|
|
30
|
-
### Option 1: install with pip (
|
|
31
|
+
### Option 1: install with pip (Python source version):
|
|
31
32
|
|
|
32
33
|
Requirements: Python 3.9+
|
|
33
34
|
|
|
@@ -46,6 +47,14 @@ pipx install explainthisrepo
|
|
|
46
47
|
explainthisrepo owner/repo
|
|
47
48
|
```
|
|
48
49
|
|
|
50
|
+
After installation, use any of the available commands:
|
|
51
|
+
|
|
52
|
+
```bash
|
|
53
|
+
explainthisrepo owner/repo
|
|
54
|
+
explain-this-repo owner/repo
|
|
55
|
+
etr owner/repo
|
|
56
|
+
```
|
|
57
|
+
|
|
49
58
|
To install support for specific models:
|
|
50
59
|
|
|
51
60
|
```bash
|
|
@@ -55,7 +64,9 @@ pip install explainthisrepo[anthropic]
|
|
|
55
64
|
pip install explainthisrepo[groq]
|
|
56
65
|
```
|
|
57
66
|
|
|
58
|
-
|
|
67
|
+
Replace `owner/repo` with the GitHub repository identifier (e.g., `facebook/react`, `torvalds/linux`).
|
|
68
|
+
|
|
69
|
+
### Option 2: Install with npm (prebuilt binary, no Python required)
|
|
59
70
|
|
|
60
71
|
Install globally and use forever:
|
|
61
72
|
|
|
@@ -71,9 +82,19 @@ Or without install:
|
|
|
71
82
|
|
|
72
83
|
```bash
|
|
73
84
|
npx explainthisrepo owner/repo
|
|
85
|
+
|
|
86
|
+
# npx explainthisrepo .
|
|
74
87
|
```
|
|
75
88
|
|
|
76
|
-
|
|
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.
|
|
77
98
|
|
|
78
99
|
### Option 3: Download standalone binary
|
|
79
100
|
|
|
@@ -124,8 +145,22 @@ explainthisrepo init
|
|
|
124
145
|
# or npx explainthisrepo init
|
|
125
146
|
```
|
|
126
147
|
|
|
127
|
-
> 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).
|
|
149
|
+
|
|
150
|
+
## GitHub token Access (Private Repos & Rate Limits)
|
|
151
|
+
|
|
152
|
+
ExplainThisRepo supports GitHub authentication for:
|
|
153
|
+
|
|
154
|
+
- Accessing private repositories
|
|
155
|
+
- Higher API rate limits on public repositories
|
|
156
|
+
|
|
157
|
+
Run:
|
|
158
|
+
|
|
159
|
+
```bash
|
|
160
|
+
explainthisrepo init
|
|
161
|
+
```
|
|
128
162
|
|
|
163
|
+
For step-by-step instructions, see [docs/GITHUB_TOKEN.md](docs/GITHUB_TOKEN.md)
|
|
129
164
|
|
|
130
165
|
## Flag options
|
|
131
166
|
|
|
@@ -147,8 +182,6 @@ explainthisrepo init
|
|
|
147
182
|
|
|
148
183
|
- `--llm` → Override provider selection
|
|
149
184
|
|
|
150
|
-
- `--token/-t` → Set GitHub token for private repositories and to avoid rate limits
|
|
151
|
-
|
|
152
185
|
## Flexible Repository and Local Directory Input
|
|
153
186
|
|
|
154
187
|
Accepts various formats for repository input, full GitHub URLs (with or without https), `owner/repo` format, issue links, query strings, and SSH clone links
|
|
@@ -165,6 +198,22 @@ explainthisrepo ./path/to/directory
|
|
|
165
198
|
|
|
166
199
|
All inputs are normalized internally to `owner/repo`.
|
|
167
200
|
|
|
201
|
+
## CLI aliases
|
|
202
|
+
|
|
203
|
+
ExplainThisRepo ships with multiple command names that all map to the same entrypoint:
|
|
204
|
+
|
|
205
|
+
- `explainthisrepo` → primary command
|
|
206
|
+
- `explain-this-repo` → readable alias
|
|
207
|
+
- `etr` → short alias for faster typing
|
|
208
|
+
|
|
209
|
+
All three commands run the same tool and support the same flags and modes.
|
|
210
|
+
|
|
211
|
+
```bash
|
|
212
|
+
explainthisrepo owner/repo
|
|
213
|
+
explain-this-repo owner/repo
|
|
214
|
+
etr owner/repo
|
|
215
|
+
```
|
|
216
|
+
|
|
168
217
|
## Model selection
|
|
169
218
|
|
|
170
219
|
The `--llm` flag selects which configured model backend to use for the current command.
|
|
@@ -269,14 +318,6 @@ When analyzing a local directory:
|
|
|
269
318
|
|
|
270
319
|
This allows analysis of projects directly from the local filesystem, without requiring a GitHub repository.
|
|
271
320
|
|
|
272
|
-
### For private repositories, use the --token/-t option.
|
|
273
|
-
|
|
274
|
-
Setting a `GITHUB_TOKEN` environment variable is recommended to avoid rate limits when analyzing public repositories.
|
|
275
|
-
|
|
276
|
-
```bash
|
|
277
|
-
export GITHUB_TOKEN=yourActualTokenHere
|
|
278
|
-
```
|
|
279
|
-
|
|
280
321
|
### Version
|
|
281
322
|
|
|
282
323
|
Check the installed CLI version:
|
package/dist/cli.js
CHANGED
|
@@ -1,383 +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() {
|
|
56
|
-
try {
|
|
57
|
-
const __filename = fileURLToPath(import.meta.url);
|
|
58
|
-
const __dirname = path.dirname(__filename);
|
|
59
|
-
const pkgPath = path.join(__dirname, "..", "package.json");
|
|
60
|
-
const pkg = JSON.parse(readFileSync(pkgPath, "utf8"));
|
|
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);
|
|
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() {
|
|
74
34
|
try {
|
|
75
|
-
const
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
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("\nenvironment:");
|
|
97
|
-
console.log(`- GITHUB_TOKEN set: ${hasEnv("GITHUB_TOKEN")}`);
|
|
98
|
-
console.log("\nnetwork checks:");
|
|
99
|
-
const gh = await checkUrl("https://api.github.com");
|
|
100
|
-
console.log(`- github api: ${gh.msg}`);
|
|
101
|
-
console.log("\nprovider diagnostics:");
|
|
102
|
-
let providerOk = true;
|
|
103
|
-
try {
|
|
104
|
-
const { getActiveProvider } = await import("./providers/registry.js");
|
|
105
|
-
const provider = await getActiveProvider(llmOverride);
|
|
106
|
-
const providerName = provider.name ?? llmOverride ?? "unknown";
|
|
107
|
-
console.log(`- active provider: ${providerName}`);
|
|
108
|
-
const doctorFn = provider.doctor;
|
|
109
|
-
if (typeof doctorFn === "function") {
|
|
110
|
-
const result = await doctorFn.call(provider);
|
|
111
|
-
if (typeof result === "boolean") {
|
|
112
|
-
console.log(`- ${providerName}: ${result ? "ok" : "checks did not pass"}`);
|
|
113
|
-
providerOk = result;
|
|
114
|
-
}
|
|
115
|
-
else if (Array.isArray(result)) {
|
|
116
|
-
if (result.length === 0) {
|
|
117
|
-
console.log(`- ${providerName}: ok`);
|
|
118
|
-
}
|
|
119
|
-
else {
|
|
120
|
-
for (const line of result) {
|
|
121
|
-
console.log(`- ${providerName}: ${line}`);
|
|
122
|
-
}
|
|
123
|
-
providerOk = false;
|
|
124
|
-
}
|
|
125
|
-
}
|
|
126
|
-
else {
|
|
127
|
-
console.log(`- ${providerName}: ok`);
|
|
128
|
-
}
|
|
129
|
-
}
|
|
130
|
-
else {
|
|
131
|
-
console.log(`- ${providerName}: no diagnostics implemented`);
|
|
40
|
+
if (result.error) {
|
|
41
|
+
throw result.error;
|
|
132
42
|
}
|
|
43
|
+
process.exit(result.status ?? 1);
|
|
133
44
|
}
|
|
134
|
-
catch (
|
|
135
|
-
const message =
|
|
136
|
-
if (llmOverride) {
|
|
137
|
-
console.log(`- provider '${llmOverride}' could not be resolved`);
|
|
138
|
-
console.log("- check that the provider name is correct and properly configured");
|
|
139
|
-
}
|
|
140
|
-
else {
|
|
141
|
-
console.log(`- provider registry error: ${message}`);
|
|
142
|
-
console.log("- run `explainthisrepo init` to configure a provider");
|
|
143
|
-
}
|
|
144
|
-
providerOk = false;
|
|
145
|
-
}
|
|
146
|
-
return gh.ok && providerOk ? 0 : 1;
|
|
147
|
-
}
|
|
148
|
-
async function safeReadRepoFiles(owner, repo) {
|
|
149
|
-
try {
|
|
150
|
-
return await readRepoSignalFiles(owner, repo);
|
|
151
|
-
}
|
|
152
|
-
catch (e) {
|
|
153
|
-
const message = e instanceof Error ? e.message : String(e);
|
|
154
|
-
console.warn(`Warning: Could not read repo files: ${message}`);
|
|
155
|
-
return null;
|
|
156
|
-
}
|
|
157
|
-
}
|
|
158
|
-
async function generateWithExit(prompt, llm) {
|
|
159
|
-
try {
|
|
160
|
-
return await generateExplanation(prompt, llm);
|
|
161
|
-
}
|
|
162
|
-
catch (e) {
|
|
163
|
-
const message = e instanceof Error ? e.message : String(e);
|
|
164
|
-
console.error("Failed to generate explanation.");
|
|
45
|
+
catch (error) {
|
|
46
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
165
47
|
console.error(`error: ${message}`);
|
|
166
|
-
console.error("\nfix:");
|
|
167
|
-
console.error("- Check that the provider name is correct (e.g. gemini, openai, ollama)");
|
|
168
|
-
console.error("- Ensure your API key is set for the selected provider");
|
|
169
|
-
console.error("- Or run: explainthisrepo --doctor");
|
|
170
|
-
process.exit(1);
|
|
171
|
-
}
|
|
172
|
-
}
|
|
173
|
-
async function runAnalysis(repository, options) {
|
|
174
|
-
if (options.doctor) {
|
|
175
|
-
const code = await runDoctor(options.llm);
|
|
176
|
-
process.exit(code);
|
|
177
|
-
}
|
|
178
|
-
const modeFlags = [
|
|
179
|
-
options.quick,
|
|
180
|
-
options.simple,
|
|
181
|
-
options.detailed,
|
|
182
|
-
options.stack,
|
|
183
|
-
].filter(Boolean);
|
|
184
|
-
if (modeFlags.length > 1) {
|
|
185
|
-
console.error("error: only one mode flag can be used at a time");
|
|
186
48
|
process.exit(1);
|
|
187
49
|
}
|
|
188
|
-
const local = fs.existsSync(repository);
|
|
189
|
-
let owner = "";
|
|
190
|
-
let repo = "";
|
|
191
|
-
let localPath = "";
|
|
192
|
-
if (local) {
|
|
193
|
-
localPath = path.resolve(repository);
|
|
194
|
-
console.log(`Analyzing local directory: ${repository}`);
|
|
195
|
-
}
|
|
196
|
-
else {
|
|
197
|
-
try {
|
|
198
|
-
({ owner, repo } = resolveRepoTarget(repository));
|
|
199
|
-
}
|
|
200
|
-
catch (e) {
|
|
201
|
-
const message = e instanceof Error ? e.message : String(e);
|
|
202
|
-
console.error(`error: ${message}`);
|
|
203
|
-
process.exit(1);
|
|
204
|
-
}
|
|
205
|
-
}
|
|
206
|
-
if (options.stack) {
|
|
207
|
-
let read;
|
|
208
|
-
let languages = {};
|
|
209
|
-
if (local) {
|
|
210
|
-
const spinner = ora("Reading repository files…").start();
|
|
211
|
-
try {
|
|
212
|
-
read = readLocalRepoSignalFiles(localPath);
|
|
213
|
-
spinner.stop();
|
|
214
|
-
}
|
|
215
|
-
catch (e) {
|
|
216
|
-
spinner.stop();
|
|
217
|
-
const message = e instanceof Error ? e.message : String(e);
|
|
218
|
-
console.error(`error: ${message}`);
|
|
219
|
-
process.exit(1);
|
|
220
|
-
}
|
|
221
|
-
}
|
|
222
|
-
else {
|
|
223
|
-
const spinner = ora(`Fetching ${owner}/${repo}…`).start();
|
|
224
|
-
try {
|
|
225
|
-
languages = await fetchLanguages(owner, repo);
|
|
226
|
-
read = await readRepoSignalFiles(owner, repo);
|
|
227
|
-
spinner.stop();
|
|
228
|
-
}
|
|
229
|
-
catch (e) {
|
|
230
|
-
spinner.stop();
|
|
231
|
-
const message = e instanceof Error ? e.message : String(e);
|
|
232
|
-
console.error(`error: ${message}`);
|
|
233
|
-
process.exit(1);
|
|
234
|
-
}
|
|
235
|
-
}
|
|
236
|
-
const report = detectStack({
|
|
237
|
-
languages,
|
|
238
|
-
tree: read.tree,
|
|
239
|
-
keyFiles: read.keyFiles,
|
|
240
|
-
});
|
|
241
|
-
const label = local ? repository : owner;
|
|
242
|
-
const sublabel = local ? "" : repo;
|
|
243
|
-
printStack(report, label, sublabel);
|
|
244
|
-
return;
|
|
245
|
-
}
|
|
246
|
-
let repoData = null;
|
|
247
|
-
let readme = null;
|
|
248
|
-
if (!local) {
|
|
249
|
-
const spinner = ora(`Fetching ${owner}/${repo}…`).start();
|
|
250
|
-
try {
|
|
251
|
-
repoData = await fetchRepo(owner, repo);
|
|
252
|
-
readme = await fetchReadme(owner, repo);
|
|
253
|
-
spinner.stop();
|
|
254
|
-
}
|
|
255
|
-
catch (e) {
|
|
256
|
-
spinner.stop();
|
|
257
|
-
const message = e instanceof Error ? e.message : String(e);
|
|
258
|
-
console.error("Failed to fetch repository data.");
|
|
259
|
-
console.error(`error: ${message}`);
|
|
260
|
-
console.error("\nfix:");
|
|
261
|
-
console.error("- Ensure the repository exists and is public");
|
|
262
|
-
console.error("- Or set GITHUB_TOKEN to avoid rate limits");
|
|
263
|
-
process.exit(1);
|
|
264
|
-
}
|
|
265
|
-
}
|
|
266
|
-
if (options.quick) {
|
|
267
|
-
let quickReadme = readme;
|
|
268
|
-
const repoName = local ? localPath : (repoData?.full_name ?? "");
|
|
269
|
-
const description = local
|
|
270
|
-
? null
|
|
271
|
-
: (repoData?.description ?? null);
|
|
272
|
-
if (local) {
|
|
273
|
-
const spinner = ora("Reading repository files…").start();
|
|
274
|
-
try {
|
|
275
|
-
const read = readLocalRepoSignalFiles(localPath);
|
|
276
|
-
spinner.stop();
|
|
277
|
-
const readmeKey = Object.keys(read.keyFiles).find((k) => k.toLowerCase().startsWith("readme"));
|
|
278
|
-
quickReadme = readmeKey !== undefined ? read.keyFiles[readmeKey] : null;
|
|
279
|
-
}
|
|
280
|
-
catch (e) {
|
|
281
|
-
spinner.stop();
|
|
282
|
-
throw e;
|
|
283
|
-
}
|
|
284
|
-
}
|
|
285
|
-
const prompt = buildQuickPrompt(repoName, description, quickReadme);
|
|
286
|
-
const spinner = ora("Generating explanation…").start();
|
|
287
|
-
const output = await generateWithExit(prompt, options.llm).finally(() => spinner.stop());
|
|
288
|
-
console.log("Quick summary 🎉");
|
|
289
|
-
console.log(output.trim());
|
|
290
|
-
return;
|
|
291
|
-
}
|
|
292
|
-
if (options.simple) {
|
|
293
|
-
let readResult;
|
|
294
|
-
const spinner = ora("Reading repository files…").start();
|
|
295
|
-
try {
|
|
296
|
-
readResult = local
|
|
297
|
-
? readLocalRepoSignalFiles(localPath)
|
|
298
|
-
: await safeReadRepoFiles(owner, repo);
|
|
299
|
-
spinner.stop();
|
|
300
|
-
}
|
|
301
|
-
catch (e) {
|
|
302
|
-
spinner.stop();
|
|
303
|
-
throw e;
|
|
304
|
-
}
|
|
305
|
-
const prompt = buildSimplePrompt(local ? localPath : (repoData?.full_name ?? ""), local ? null : (repoData?.description ?? null), local ? null : readme, readResult?.treeText ?? null);
|
|
306
|
-
const genSpinner = ora("Generating explanation…").start();
|
|
307
|
-
const output = await generateWithExit(prompt, options.llm).finally(() => genSpinner.stop());
|
|
308
|
-
console.log("Simple summary 🎉");
|
|
309
|
-
console.log(output.trim());
|
|
310
|
-
return;
|
|
311
|
-
}
|
|
312
|
-
let readResult;
|
|
313
|
-
const readSpinner = ora("Reading repository files…").start();
|
|
314
|
-
try {
|
|
315
|
-
readResult = local
|
|
316
|
-
? readLocalRepoSignalFiles(localPath)
|
|
317
|
-
: await safeReadRepoFiles(owner, repo);
|
|
318
|
-
readSpinner.stop();
|
|
319
|
-
}
|
|
320
|
-
catch (e) {
|
|
321
|
-
readSpinner.stop();
|
|
322
|
-
throw e;
|
|
323
|
-
}
|
|
324
|
-
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);
|
|
325
|
-
const genSpinner = ora("Generating explanation…").start();
|
|
326
|
-
const output = await generateWithExit(prompt, options.llm).finally(() => genSpinner.stop());
|
|
327
|
-
console.log("Writing EXPLAIN.md...");
|
|
328
|
-
writeOutput(output);
|
|
329
|
-
const wordCount = output.split(/\s+/).filter(Boolean).length;
|
|
330
|
-
console.log("EXPLAIN.md generated successfully 🎉");
|
|
331
|
-
console.log(`Words: ${wordCount}`);
|
|
332
|
-
console.log("Open EXPLAIN.md to read it.");
|
|
333
50
|
}
|
|
334
|
-
|
|
335
|
-
program
|
|
336
|
-
.name("explainthisrepo")
|
|
337
|
-
.description("CLI that generates plain English explanations of any codebase")
|
|
338
|
-
.version(getPkgVersion(), "-v, --version", "Show version")
|
|
339
|
-
.argument("[repository]", "GitHub repository (owner/repo or URL) or local directories")
|
|
340
|
-
.option("--doctor", "Run diagnostics")
|
|
341
|
-
.option("--quick", "Quick summary mode")
|
|
342
|
-
.option("--simple", "Simple summary mode")
|
|
343
|
-
.option("--detailed", "Detailed explanation mode")
|
|
344
|
-
.option("--stack", "Stack detection mode")
|
|
345
|
-
.option("--llm <provider>", "LLM provider to use (e.g. gemini, openai, ollama). Overrides config default.")
|
|
346
|
-
.addHelpText("after", `
|
|
347
|
-
Examples:
|
|
348
|
-
$ explainthisrepo owner/repo
|
|
349
|
-
$ explainthisrepo https://github.com/owner/repo
|
|
350
|
-
$ explainthisrepo github.com/owner/repo
|
|
351
|
-
$ explainthisrepo git@github.com:owner/repo.git
|
|
352
|
-
$ explainthisrepo owner/repo --detailed
|
|
353
|
-
$ explainthisrepo owner/repo --quick
|
|
354
|
-
$ explainthisrepo owner/repo --simple
|
|
355
|
-
$ explainthisrepo owner/repo --stack
|
|
356
|
-
$ explainthisrepo owner/repo --llm gemini
|
|
357
|
-
$ explainthisrepo owner/repo --llm openai
|
|
358
|
-
$ explainthisrepo owner/repo --llm ollama
|
|
359
|
-
$ explainthisrepo .
|
|
360
|
-
$ explainthisrepo ./path/to/directory
|
|
361
|
-
$ explainthisrepo . --stack
|
|
362
|
-
$ explainthisrepo --doctor
|
|
363
|
-
$ explainthisrepo --doctor --llm gemini
|
|
364
|
-
$ explainthisrepo --doctor --llm openai
|
|
365
|
-
$ explainthisrepo --doctor --llm ollama`)
|
|
366
|
-
.action(async (repository, options) => {
|
|
367
|
-
if (options.doctor) {
|
|
368
|
-
const code = await runDoctor(options.llm);
|
|
369
|
-
process.exit(code);
|
|
370
|
-
}
|
|
371
|
-
if (!repository) {
|
|
372
|
-
program.error("repository argument required (or use `explainthisrepo init` to configure a provider)");
|
|
373
|
-
return;
|
|
374
|
-
}
|
|
375
|
-
await runAnalysis(repository, options);
|
|
376
|
-
});
|
|
377
|
-
program
|
|
378
|
-
.command("init")
|
|
379
|
-
.description("Configure your LLM provider (Gemini, OpenAI, or Ollama)")
|
|
380
|
-
.action(async () => {
|
|
381
|
-
await runInit();
|
|
382
|
-
});
|
|
383
|
-
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 declare function fetchRepo(owner: string, repo: string): Promise<any>;
|
|
2
|
-
export declare function fetchReadme(owner: string, repo: string): Promise<string | null>;
|
|
3
|
-
export declare function fetchTree(owner: string, repo: string): Promise<any[]>;
|
|
4
|
-
export declare function fetchFile(owner: string, repo: string, filePath: string): Promise<string>;
|
|
5
|
-
export type RepoLanguageMap = Record<string, number>;
|
|
6
|
-
export declare function fetchLanguages(owner: string, repo: string): Promise<RepoLanguageMap>;
|