explainthisrepo 0.4.4 → 0.5.1
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 +62 -27
- package/dist/cli.js +125 -56
- package/dist/local_reader.d.ts +2 -0
- package/dist/local_reader.js +77 -0
- package/dist/repo_reader.d.ts +1 -2
- package/package.json +3 -2
package/README.md
CHANGED
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
# ExplainThisRepo
|
|
2
2
|
|
|
3
|
-
ExplainThisRepo is a CLI that generates plain-English explanations of public GitHub repositories by analyzing
|
|
3
|
+
ExplainThisRepo is a CLI that generates plain-English explanations of public GitHub repositories and local directories by analyzing project structure, README content, and high signal files.
|
|
4
4
|
|
|
5
|
-
It
|
|
5
|
+
It helps developers quickly understand unfamiliar codebases by deriving architectural explanations from real project structure and code signals, producing a clear, structured `EXPLAIN.md`.
|
|
6
6
|
|
|
7
7
|
[](https://pypi.org/project/explainthisrepo/)
|
|
8
8
|
[](https://pepy.tech/projects/explainthisrepo)
|
|
@@ -19,15 +19,15 @@ It's helps developers understand unfamiliar repositories does by generating a st
|
|
|
19
19
|
|
|
20
20
|
## Key Features
|
|
21
21
|
|
|
22
|
-
-
|
|
23
|
-
- Fetches public GitHub
|
|
24
|
-
- Analyzes
|
|
22
|
+
- Generates architectural summaries from repository structure and code signals
|
|
23
|
+
- Fetches public repositories by GitHub URLs (with or without https), `owner/repo` format, issue links, query strings, and SSH clone links
|
|
24
|
+
- Analyzes repository data including file tree, configs, entrypoints, and high signal source files
|
|
25
25
|
- Extracts repo signals from key files (package.json, pyproject.toml, config files, entrypoints)
|
|
26
26
|
- Builds a file tree summary to understand project architecture
|
|
27
|
-
- Detects programming languages
|
|
28
|
-
-
|
|
27
|
+
- Detects programming languages with the GitHub API
|
|
28
|
+
- Analyzes local project directories using the same pipeline as GitHub repositories
|
|
29
29
|
- Generates a structured plain English explanation grounded in actual project files
|
|
30
|
-
- Outputs an EXPLAIN.md file in your current directory
|
|
30
|
+
- Outputs the explanation to an `EXPLAIN.md` file in your current directory or print it directly in the terminal
|
|
31
31
|
- Multi mode command-line interface
|
|
32
32
|
|
|
33
33
|
---
|
|
@@ -38,13 +38,13 @@ It's helps developers understand unfamiliar repositories does by generating a st
|
|
|
38
38
|
|
|
39
39
|
- `--quick` → One-sentence summary
|
|
40
40
|
|
|
41
|
-
- `--simple` → Short,
|
|
41
|
+
- `--simple` → Short, simplified explanation
|
|
42
42
|
|
|
43
43
|
- `--detailed` → Deeper explanation including structure and architecture
|
|
44
44
|
|
|
45
45
|
- `--stack` → Tech stack breakdown from repo signals
|
|
46
46
|
|
|
47
|
-
- `--version` →
|
|
47
|
+
- `--version` → Check installed CLI version
|
|
48
48
|
|
|
49
49
|
- `--help` → Show usage guide
|
|
50
50
|
|
|
@@ -56,9 +56,9 @@ It's helps developers understand unfamiliar repositories does by generating a st
|
|
|
56
56
|
|
|
57
57
|
ExplainThisRepo uses Gemini models for code analysis.
|
|
58
58
|
|
|
59
|
-
Set your API key as an environment variable.
|
|
59
|
+
Set your Google Gemini API key as an environment variable.
|
|
60
60
|
|
|
61
|
-
|
|
61
|
+
Linux / macOS
|
|
62
62
|
|
|
63
63
|
```bash
|
|
64
64
|
export GEMINI_API_KEY="your_api_key_here"
|
|
@@ -74,7 +74,7 @@ Restart your terminal after setting the key.
|
|
|
74
74
|
|
|
75
75
|
## Installation
|
|
76
76
|
|
|
77
|
-
### Option 1: install
|
|
77
|
+
### Option 1: install with pip (recommended):
|
|
78
78
|
|
|
79
79
|
Requirements: Python 3.9+
|
|
80
80
|
|
|
@@ -99,29 +99,32 @@ explainthisrepo owner/repo
|
|
|
99
99
|
# or: npx explainthisrepo owner/repo
|
|
100
100
|
```
|
|
101
101
|
|
|
102
|
+
Replace `owner/repo` with the GitHub repository identifier (e.g., `facebook/react`).
|
|
103
|
+
|
|
102
104
|
---
|
|
103
105
|
|
|
104
|
-
## Flexible Repository Input
|
|
106
|
+
## Flexible Repository and Local Directory Input
|
|
105
107
|
|
|
106
|
-
|
|
108
|
+
Accepts various formats for repository input, full GitHub URLs, issue links, and SSH clone links.
|
|
107
109
|
|
|
108
|
-
ExplainThisRepo accepts GitHub repositories the way you actually copy them.
|
|
109
110
|
```bash
|
|
110
111
|
explainthisrepo https://github.com/owner/repo
|
|
111
112
|
explainthisrepo github.com/owner/repo
|
|
112
113
|
explainthisrepo https://github.com/owner/repo/issues/123
|
|
113
114
|
explainthisrepo https://github.com/owner/repo?tab=readme
|
|
114
115
|
explainthisrepo git@github.com:owner/repo.git
|
|
116
|
+
explainthisrepo .
|
|
117
|
+
explainthisrepo ./path/to/directory
|
|
115
118
|
```
|
|
116
119
|
|
|
117
|
-
All inputs are normalized internally to owner/repo
|
|
120
|
+
All inputs are normalized internally to `owner/repo`.
|
|
118
121
|
|
|
119
122
|
---
|
|
120
123
|
|
|
121
124
|
## Usage
|
|
122
125
|
|
|
123
126
|
### Basic
|
|
124
|
-
|
|
127
|
+
Writes a full explanation to `EXPLAIN.md`:
|
|
125
128
|
|
|
126
129
|
```bash
|
|
127
130
|
explainthisrepo owner/repo
|
|
@@ -134,7 +137,8 @@ explainthisrepo facebook/react
|
|
|
134
137
|
|
|
135
138
|
### Quick mode
|
|
136
139
|
|
|
137
|
-
|
|
140
|
+
Prints a one-sentence summary to stdout:
|
|
141
|
+
|
|
138
142
|
```bash
|
|
139
143
|
explainthisrepo owner/repo --quick
|
|
140
144
|
```
|
|
@@ -148,7 +152,8 @@ explainthisrepo facebook/react --quick
|
|
|
148
152
|
|
|
149
153
|
### Detailed mode
|
|
150
154
|
|
|
151
|
-
|
|
155
|
+
Writes a more detailed explanation of repository structure and architecture:
|
|
156
|
+
|
|
152
157
|
```bash
|
|
153
158
|
explainthisrepo owner/repo --detailed
|
|
154
159
|
```
|
|
@@ -159,7 +164,8 @@ explainthisrepo owner/repo --detailed
|
|
|
159
164
|
|
|
160
165
|
### Simple mode
|
|
161
166
|
|
|
162
|
-
Prints
|
|
167
|
+
Prints a short, simplified explanation to stdout. No files are written.
|
|
168
|
+
|
|
163
169
|
```bash
|
|
164
170
|
explainthisrepo owner/repo --simple
|
|
165
171
|
```
|
|
@@ -170,15 +176,43 @@ explainthisrepo owner/repo --simple
|
|
|
170
176
|
|
|
171
177
|
### Stack detector
|
|
172
178
|
|
|
173
|
-
|
|
179
|
+
Tech stack breakdown detected from repo signals. No LLM calls are made.
|
|
180
|
+
|
|
174
181
|
```bash
|
|
175
182
|
explainthisrepo owner/repo --stack
|
|
176
183
|
```
|
|
177
184
|

|
|
178
185
|
|
|
186
|
+
### Local Directory Analysis
|
|
187
|
+
|
|
188
|
+
ExplainThisRepo can analyze local directories directly in the terminal, using the same modes and output formats as GitHub repositories
|
|
189
|
+
|
|
190
|
+
```bash
|
|
191
|
+
explainthisrepo .
|
|
192
|
+
explainthisrepo ./path/to/directory
|
|
193
|
+
```
|
|
194
|
+
|
|
195
|
+
This works with all existing modes:
|
|
196
|
+
|
|
197
|
+
```bash
|
|
198
|
+
explainthisrepo . --quick
|
|
199
|
+
explainthisrepo . --simple
|
|
200
|
+
explainthisrepo . --detailed
|
|
201
|
+
explainthisrepo . --stack
|
|
202
|
+
```
|
|
203
|
+
|
|
204
|
+
When analyzing a local directory:
|
|
205
|
+
- Repository structure is derived from the filesystem
|
|
206
|
+
- Key files (README, configs, entrypoints) are extracted locally
|
|
207
|
+
- No GitHub APIs calls are made
|
|
208
|
+
- All prompts and outputs remain identical
|
|
209
|
+
|
|
210
|
+
This allows analysis of projects directly from the local filesystem, without requiring a GitHub repository.
|
|
211
|
+
|
|
179
212
|
### Version
|
|
180
213
|
|
|
181
|
-
Print the installed version:
|
|
214
|
+
Print the installed CLI version:
|
|
215
|
+
|
|
182
216
|
```bash
|
|
183
217
|
explainthisrepo --version
|
|
184
218
|
```
|
|
@@ -187,12 +221,13 @@ explainthisrepo --version
|
|
|
187
221
|
|
|
188
222
|
### Doctor
|
|
189
223
|
|
|
190
|
-
Check environment
|
|
224
|
+
Check environment and connectivity (useful for debugging):
|
|
225
|
+
|
|
191
226
|
```bash
|
|
192
227
|
explainthisrepo --doctor
|
|
193
228
|
```
|
|
194
229
|
|
|
195
|
-
|
|
230
|
+
### Termux (Android) install notes
|
|
196
231
|
|
|
197
232
|
Termux has some environment limitations that can make `pip install explainthisrepo` fail to create the `explainthisrepo` command in `$PREFIX/bin`.
|
|
198
233
|
|
|
@@ -216,7 +251,7 @@ If you do not want to modify PATH, you can run ExplainThisRepo as a module:
|
|
|
216
251
|
python -m explain_this_repo owner/repo
|
|
217
252
|
```
|
|
218
253
|
|
|
219
|
-
Gemini support on Termux (Optional)
|
|
254
|
+
### Gemini support on Termux (Optional)
|
|
220
255
|
|
|
221
256
|
Installing Gemini support may require building Rust-based dependencies on Android, which can take time on first install:
|
|
222
257
|
|
|
@@ -247,4 +282,4 @@ Caleb Wodi
|
|
|
247
282
|
|
|
248
283
|
- Email: caleb@explainthisrepo.com
|
|
249
284
|
- Twitter: [@calchiwo](https://x.com/calchiwo)
|
|
250
|
-
- LinkedIn: [@calchiwo](https://linkedin.com/in/calchiwo)
|
|
285
|
+
- LinkedIn: [@calchiwo](https://linkedin.com/in/calchiwo)
|
package/dist/cli.js
CHANGED
|
@@ -1,15 +1,18 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
import os from "node:os";
|
|
3
3
|
import process from "node:process";
|
|
4
|
+
import fs from "node:fs";
|
|
4
5
|
import { readFileSync } from "node:fs";
|
|
5
6
|
import path from "node:path";
|
|
6
7
|
import { fileURLToPath } from "node:url";
|
|
7
8
|
import { Command } from "commander";
|
|
9
|
+
import ora from "ora";
|
|
8
10
|
import { fetchRepo, fetchReadme } from "./github.js";
|
|
9
11
|
import { buildPrompt, buildQuickPrompt, buildSimplePrompt } from "./prompt.js";
|
|
10
12
|
import { generateExplanation } from "./generate.js";
|
|
11
13
|
import { writeOutput } from "./writer.js";
|
|
12
14
|
import { readRepoSignalFiles } from "./repo_reader.js";
|
|
15
|
+
import { readLocalRepoSignalFiles } from "./local_reader.js";
|
|
13
16
|
import { fetchLanguages } from "./github.js";
|
|
14
17
|
import { detectStack } from "./stack-detector.js";
|
|
15
18
|
import { printStack } from "./stack_printer.js";
|
|
@@ -22,8 +25,8 @@ function resolveRepoTarget(target) {
|
|
|
22
25
|
target = target.replace("http//", "http://");
|
|
23
26
|
}
|
|
24
27
|
if (target.startsWith("git@github.com:")) {
|
|
25
|
-
const
|
|
26
|
-
const [owner, repoRaw] =
|
|
28
|
+
const p = target.replace("git@github.com:", "");
|
|
29
|
+
const [owner, repoRaw] = p.split("/", 2);
|
|
27
30
|
if (!owner || !repoRaw)
|
|
28
31
|
throw new Error("Invalid GitHub SSH URL");
|
|
29
32
|
return { owner, repo: repoRaw.replace(/\.git$/, "") };
|
|
@@ -83,10 +86,7 @@ async function checkUrl(url, timeoutMs = 6000) {
|
|
|
83
86
|
clearTimeout(t);
|
|
84
87
|
const message = e instanceof Error ? e.message : String(e);
|
|
85
88
|
const name = e instanceof Error ? e.name : "Error";
|
|
86
|
-
return {
|
|
87
|
-
ok: false,
|
|
88
|
-
msg: `failed (${name}: ${message})`,
|
|
89
|
-
};
|
|
89
|
+
return { ok: false, msg: `failed (${name}: ${message})` };
|
|
90
90
|
}
|
|
91
91
|
}
|
|
92
92
|
async function runDoctor() {
|
|
@@ -135,7 +135,7 @@ async function main() {
|
|
|
135
135
|
.name("explainthisrepo")
|
|
136
136
|
.description("Explain GitHub repositories in plain English")
|
|
137
137
|
.version(getPkgVersion(), "-v, --version", "Show version")
|
|
138
|
-
.argument("[repository]", "GitHub repository (owner/repo or URL)")
|
|
138
|
+
.argument("[repository]", "GitHub repository (owner/repo or URL) or local path")
|
|
139
139
|
.option("--doctor", "Run diagnostics")
|
|
140
140
|
.option("--quick", "Quick summary mode")
|
|
141
141
|
.option("--simple", "Simple summary mode")
|
|
@@ -151,6 +151,9 @@ Examples:
|
|
|
151
151
|
$ explainthisrepo owner/repo --quick
|
|
152
152
|
$ explainthisrepo owner/repo --simple
|
|
153
153
|
$ explainthisrepo owner/repo --stack
|
|
154
|
+
$ explainthisrepo .
|
|
155
|
+
$ explainthisrepo ./path/to/directory
|
|
156
|
+
$ explainthisrepo . --stack
|
|
154
157
|
$ explainthisrepo --doctor`);
|
|
155
158
|
program.parse(process.argv);
|
|
156
159
|
const options = program.opts();
|
|
@@ -172,27 +175,17 @@ Examples:
|
|
|
172
175
|
if (!repository) {
|
|
173
176
|
program.error("repository argument required");
|
|
174
177
|
}
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
console.
|
|
182
|
-
process.exit(1);
|
|
178
|
+
const local = fs.existsSync(repository);
|
|
179
|
+
let owner = "";
|
|
180
|
+
let repo = "";
|
|
181
|
+
let localPath = "";
|
|
182
|
+
if (local) {
|
|
183
|
+
localPath = path.resolve(repository);
|
|
184
|
+
console.log(`Analyzing local directory: ${repository}`);
|
|
183
185
|
}
|
|
184
|
-
|
|
185
|
-
if (options.stack) {
|
|
186
|
+
else {
|
|
186
187
|
try {
|
|
187
|
-
|
|
188
|
-
const read = await readRepoSignalFiles(owner, repo);
|
|
189
|
-
const report = detectStack({
|
|
190
|
-
languages,
|
|
191
|
-
tree: read.tree,
|
|
192
|
-
keyFiles: read.keyFiles,
|
|
193
|
-
});
|
|
194
|
-
printStack(report, owner, repo);
|
|
195
|
-
return;
|
|
188
|
+
({ owner, repo } = resolveRepoTarget(repository));
|
|
196
189
|
}
|
|
197
190
|
catch (e) {
|
|
198
191
|
const message = e instanceof Error ? e.message : String(e);
|
|
@@ -200,49 +193,125 @@ Examples:
|
|
|
200
193
|
process.exit(1);
|
|
201
194
|
}
|
|
202
195
|
}
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
196
|
+
if (options.stack) {
|
|
197
|
+
let read;
|
|
198
|
+
let languages = {};
|
|
199
|
+
if (local) {
|
|
200
|
+
const spinner = ora("Reading repository files…").start();
|
|
201
|
+
try {
|
|
202
|
+
read = readLocalRepoSignalFiles(localPath);
|
|
203
|
+
spinner.stop();
|
|
204
|
+
}
|
|
205
|
+
catch (e) {
|
|
206
|
+
spinner.stop();
|
|
207
|
+
const message = e instanceof Error ? e.message : String(e);
|
|
208
|
+
console.error(`error: ${message}`);
|
|
209
|
+
process.exit(1);
|
|
210
|
+
}
|
|
211
|
+
}
|
|
212
|
+
else {
|
|
213
|
+
const spinner = ora(`Fetching ${owner}/${repo}…`).start();
|
|
214
|
+
try {
|
|
215
|
+
languages = await fetchLanguages(owner, repo);
|
|
216
|
+
read = await readRepoSignalFiles(owner, repo);
|
|
217
|
+
spinner.stop();
|
|
218
|
+
}
|
|
219
|
+
catch (e) {
|
|
220
|
+
spinner.stop();
|
|
221
|
+
const message = e instanceof Error ? e.message : String(e);
|
|
222
|
+
console.error(`error: ${message}`);
|
|
223
|
+
process.exit(1);
|
|
224
|
+
}
|
|
225
|
+
}
|
|
226
|
+
const report = detectStack({
|
|
227
|
+
languages,
|
|
228
|
+
tree: read.tree,
|
|
229
|
+
keyFiles: read.keyFiles,
|
|
230
|
+
});
|
|
231
|
+
const label = local ? repository : owner;
|
|
232
|
+
const sublabel = local ? "" : repo;
|
|
233
|
+
printStack(report, label, sublabel);
|
|
234
|
+
return;
|
|
215
235
|
}
|
|
236
|
+
let repoData = null;
|
|
216
237
|
let readme = null;
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
238
|
+
if (!local) {
|
|
239
|
+
const spinner = ora(`Fetching ${owner}/${repo}…`).start();
|
|
240
|
+
try {
|
|
241
|
+
repoData = await fetchRepo(owner, repo);
|
|
242
|
+
readme = await fetchReadme(owner, repo);
|
|
243
|
+
spinner.stop();
|
|
244
|
+
}
|
|
245
|
+
catch (e) {
|
|
246
|
+
spinner.stop();
|
|
247
|
+
const message = e instanceof Error ? e.message : String(e);
|
|
248
|
+
console.error("Failed to fetch repository data.");
|
|
249
|
+
console.error(`error: ${message}`);
|
|
250
|
+
console.error("\nfix:");
|
|
251
|
+
console.error("- Ensure the repository exists and is public");
|
|
252
|
+
console.error("- Or set GITHUB_TOKEN to avoid rate limits");
|
|
253
|
+
process.exit(1);
|
|
254
|
+
}
|
|
224
255
|
}
|
|
225
256
|
if (options.quick) {
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
const
|
|
257
|
+
let quickReadme = readme;
|
|
258
|
+
const repoName = local ? localPath : (repoData?.full_name ?? "");
|
|
259
|
+
const description = local ? null : (repoData?.description ?? null);
|
|
260
|
+
if (local) {
|
|
261
|
+
const spinner = ora("Reading repository files…").start();
|
|
262
|
+
try {
|
|
263
|
+
const read = readLocalRepoSignalFiles(localPath);
|
|
264
|
+
spinner.stop();
|
|
265
|
+
const readmeKey = Object.keys(read.keyFiles).find((k) => k.toLowerCase().startsWith("readme"));
|
|
266
|
+
quickReadme = readmeKey !== undefined ? read.keyFiles[readmeKey] : null;
|
|
267
|
+
}
|
|
268
|
+
catch (e) {
|
|
269
|
+
spinner.stop();
|
|
270
|
+
throw e;
|
|
271
|
+
}
|
|
272
|
+
}
|
|
273
|
+
const prompt = buildQuickPrompt(repoName, description, quickReadme);
|
|
274
|
+
const spinner = ora("Generating explanation…").start();
|
|
275
|
+
const output = await generateWithExit(prompt).finally(() => spinner.stop());
|
|
229
276
|
console.log("Quick summary 🎉");
|
|
230
277
|
console.log(output.trim());
|
|
231
278
|
return;
|
|
232
279
|
}
|
|
233
280
|
if (options.simple) {
|
|
234
|
-
|
|
235
|
-
const
|
|
236
|
-
|
|
237
|
-
|
|
281
|
+
let readResult;
|
|
282
|
+
const spinner = ora("Reading repository files…").start();
|
|
283
|
+
try {
|
|
284
|
+
readResult = local
|
|
285
|
+
? readLocalRepoSignalFiles(localPath)
|
|
286
|
+
: await safeReadRepoFiles(owner, repo);
|
|
287
|
+
spinner.stop();
|
|
288
|
+
}
|
|
289
|
+
catch (e) {
|
|
290
|
+
spinner.stop();
|
|
291
|
+
throw e;
|
|
292
|
+
}
|
|
293
|
+
const prompt = buildSimplePrompt(local ? localPath : (repoData?.full_name ?? ""), local ? null : (repoData?.description ?? null), local ? null : readme, readResult?.treeText ?? null);
|
|
294
|
+
const genSpinner = ora("Generating explanation…").start();
|
|
295
|
+
const output = await generateWithExit(prompt).finally(() => genSpinner.stop());
|
|
238
296
|
console.log("Simple summary 🎉");
|
|
239
297
|
console.log(output.trim());
|
|
240
298
|
return;
|
|
241
299
|
}
|
|
242
|
-
|
|
243
|
-
const
|
|
244
|
-
|
|
245
|
-
|
|
300
|
+
let readResult;
|
|
301
|
+
const readSpinner = ora("Reading repository files…").start();
|
|
302
|
+
try {
|
|
303
|
+
readResult = local
|
|
304
|
+
? readLocalRepoSignalFiles(localPath)
|
|
305
|
+
: await safeReadRepoFiles(owner, repo);
|
|
306
|
+
readSpinner.stop();
|
|
307
|
+
}
|
|
308
|
+
catch (e) {
|
|
309
|
+
readSpinner.stop();
|
|
310
|
+
throw e;
|
|
311
|
+
}
|
|
312
|
+
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);
|
|
313
|
+
const genSpinner = ora("Generating explanation…").start();
|
|
314
|
+
const output = await generateWithExit(prompt).finally(() => genSpinner.stop());
|
|
246
315
|
console.log("Writing EXPLAIN.md...");
|
|
247
316
|
writeOutput(output);
|
|
248
317
|
const wordCount = output.split(/\s+/).filter(Boolean).length;
|
|
@@ -0,0 +1,77 @@
|
|
|
1
|
+
import fs from "node:fs";
|
|
2
|
+
import path from "node:path";
|
|
3
|
+
const KEY_FILENAMES = new Set([
|
|
4
|
+
"readme.md", "readme.txt", "readme.rst", "readme",
|
|
5
|
+
"package.json", "pyproject.toml", "setup.py", "setup.cfg",
|
|
6
|
+
"requirements.txt", "cargo.toml", "go.mod", "pom.xml",
|
|
7
|
+
"build.gradle", "composer.json", "gemfile", "makefile",
|
|
8
|
+
"dockerfile", "docker-compose.yml", "docker-compose.yaml",
|
|
9
|
+
".env.example", "tsconfig.json", "angular.json", "next.config.js",
|
|
10
|
+
"vite.config.js", "vite.config.ts", "webpack.config.js",
|
|
11
|
+
]);
|
|
12
|
+
const SKIP_DIRS = new Set([
|
|
13
|
+
".git", ".hg", ".svn", "node_modules", "__pycache__",
|
|
14
|
+
".venv", "venv", "env", ".env", "dist", "build",
|
|
15
|
+
".idea", ".vscode", ".mypy_cache", ".pytest_cache",
|
|
16
|
+
"coverage", ".coverage", "htmlcov",
|
|
17
|
+
]);
|
|
18
|
+
const MAX_FILE_BYTES = 32_000;
|
|
19
|
+
const MAX_KEY_FILES = 12;
|
|
20
|
+
function walkDir(root, dir, tree, keyFiles) {
|
|
21
|
+
let entries;
|
|
22
|
+
try {
|
|
23
|
+
entries = fs.readdirSync(dir, { withFileTypes: true });
|
|
24
|
+
}
|
|
25
|
+
catch {
|
|
26
|
+
return;
|
|
27
|
+
}
|
|
28
|
+
const dirs = [];
|
|
29
|
+
const files = [];
|
|
30
|
+
for (const entry of entries) {
|
|
31
|
+
if (entry.isDirectory() && !SKIP_DIRS.has(entry.name)) {
|
|
32
|
+
dirs.push(entry);
|
|
33
|
+
}
|
|
34
|
+
else if (entry.isFile()) {
|
|
35
|
+
files.push(entry);
|
|
36
|
+
}
|
|
37
|
+
}
|
|
38
|
+
dirs.sort((a, b) => a.name.localeCompare(b.name));
|
|
39
|
+
files.sort((a, b) => a.name.localeCompare(b.name));
|
|
40
|
+
for (const file of files) {
|
|
41
|
+
const absPath = path.join(dir, file.name);
|
|
42
|
+
const relPath = path.relative(root, absPath).replace(/\\/g, "/");
|
|
43
|
+
tree.push({ path: relPath, type: "blob" });
|
|
44
|
+
if (KEY_FILENAMES.has(file.name.toLowerCase()) &&
|
|
45
|
+
Object.keys(keyFiles).length < MAX_KEY_FILES) {
|
|
46
|
+
try {
|
|
47
|
+
const fd = fs.openSync(absPath, "r");
|
|
48
|
+
const buf = Buffer.alloc(MAX_FILE_BYTES);
|
|
49
|
+
const bytesRead = fs.readSync(fd, buf, 0, MAX_FILE_BYTES, 0);
|
|
50
|
+
fs.closeSync(fd);
|
|
51
|
+
keyFiles[relPath] = buf.subarray(0, bytesRead).toString("utf8");
|
|
52
|
+
}
|
|
53
|
+
catch {
|
|
54
|
+
}
|
|
55
|
+
}
|
|
56
|
+
}
|
|
57
|
+
for (const subdir of dirs) {
|
|
58
|
+
walkDir(root, path.join(dir, subdir.name), tree, keyFiles);
|
|
59
|
+
}
|
|
60
|
+
}
|
|
61
|
+
export function readLocalRepoSignalFiles(dirPath) {
|
|
62
|
+
const root = path.resolve(dirPath);
|
|
63
|
+
const tree = [];
|
|
64
|
+
const keyFiles = {};
|
|
65
|
+
walkDir(root, root, tree, keyFiles);
|
|
66
|
+
const treeText = tree.map((item) => item.path).join("\n");
|
|
67
|
+
const filesText = Object.entries(keyFiles)
|
|
68
|
+
.map(([relPath, content]) => `### ${relPath}\n${content}`)
|
|
69
|
+
.join("\n\n");
|
|
70
|
+
return {
|
|
71
|
+
tree,
|
|
72
|
+
treeText,
|
|
73
|
+
keyFiles,
|
|
74
|
+
filesText,
|
|
75
|
+
selectedFiles: Object.keys(keyFiles),
|
|
76
|
+
};
|
|
77
|
+
}
|
package/dist/repo_reader.d.ts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
type TreeItem = {
|
|
1
|
+
export type TreeItem = {
|
|
2
2
|
path: string;
|
|
3
3
|
type: "blob" | "tree";
|
|
4
4
|
size?: number;
|
|
@@ -11,4 +11,3 @@ export type RepoReadResult = {
|
|
|
11
11
|
keyFiles: Record<string, string>;
|
|
12
12
|
};
|
|
13
13
|
export declare function readRepoSignalFiles(owner: string, repo: string): Promise<RepoReadResult>;
|
|
14
|
-
export {};
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "explainthisrepo",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.5.1",
|
|
4
4
|
"description": "A CLI developer tool to explain any GitHub repository in plain English",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"type": "module",
|
|
@@ -47,7 +47,8 @@
|
|
|
47
47
|
"@google/generative-ai": "^0.24.1",
|
|
48
48
|
"axios": "^1.13.2",
|
|
49
49
|
"commander": "^14.0.3",
|
|
50
|
-
"dotenv": "^17.2.3"
|
|
50
|
+
"dotenv": "^17.2.3",
|
|
51
|
+
"ora": "^9.3.0"
|
|
51
52
|
},
|
|
52
53
|
"devDependencies": {
|
|
53
54
|
"@types/node": "^22.0.0",
|