explainthisrepo 0.4.3 → 0.5.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/LICENSE +21 -0
- package/README.md +220 -57
- package/dist/cli.js +84 -50
- 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/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 Caleb Wodi
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
package/README.md
CHANGED
|
@@ -1,87 +1,250 @@
|
|
|
1
1
|
# ExplainThisRepo
|
|
2
2
|
|
|
3
|
-
|
|
4
|
-
While the original Python implementation is the primary version of this tool, this Node.js port was created to provide a "zero-compilation" experience for users where Python C-extensions (like Pillow) can be difficult to build.
|
|
3
|
+
ExplainThisRepo is a CLI that generates plain-English explanations of public GitHub repositories by analyzing repository structure, README content, and selected high signal files.
|
|
5
4
|
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
5
|
+
It's helps developers understand unfamiliar repositories does by generating a structured `EXPLAIN.md` from real
|
|
6
|
+
|
|
7
|
+
[](https://pypi.org/project/explainthisrepo/)
|
|
8
|
+
[](https://pepy.tech/projects/explainthisrepo)
|
|
9
|
+
[](https://pypi.org/project/explainthisrepo/)
|
|
10
|
+
[](LICENSE)
|
|
11
|
+
[](https://www.npmjs.com/package/explainthisrepo)
|
|
12
|
+
[](https://www.npmjs.com/package/explainthisrepo)
|
|
13
|
+
[](https://explainthisrepo.com)
|
|
14
|
+
|
|
15
|
+
---
|
|
16
|
+
|
|
17
|
+

|
|
18
|
+
---
|
|
19
|
+
|
|
20
|
+
## Key Features
|
|
21
|
+
|
|
22
|
+
- Understand unfamiliar repositories instantly through structural and architechural summaries by turning structure and code signals into a readable architectural summary
|
|
23
|
+
- Fetches public GitHub repositories automatically
|
|
24
|
+
- Analyzes real repository data including file tree, configs, entrypoints, and high signal source files
|
|
25
|
+
- Extracts repo signals from key files (package.json, pyproject.toml, config files, entrypoints)
|
|
26
|
+
- Builds a file tree summary to understand project architecture
|
|
27
|
+
- Detects programming languages via the GitHub API
|
|
28
|
+
- Accepts repositories via owner/repo, GitHub URLs (with or without https), issue links, query strings, and SSH clone links
|
|
29
|
+
- Generates a structured plain English explanation grounded in actual project files
|
|
30
|
+
- Outputs an EXPLAIN.md file in your current directory (default mode)
|
|
31
|
+
- Multi mode command-line interface
|
|
32
|
+
|
|
33
|
+
---
|
|
34
|
+
|
|
35
|
+
## Modes
|
|
36
|
+
|
|
37
|
+
- (no flag) → Full repository explanation written to EXPLAIN.md
|
|
38
|
+
|
|
39
|
+
- `--quick` → One-sentence summary
|
|
40
|
+
|
|
41
|
+
- `--simple` → Short, easy explanation
|
|
42
|
+
|
|
43
|
+
- `--detailed` → Deeper explanation including structure and architecture
|
|
44
|
+
|
|
45
|
+
- `--stack` → Tech stack breakdown from repo signals
|
|
46
|
+
|
|
47
|
+
- `--version` → Show CLI version
|
|
48
|
+
|
|
49
|
+
- `--help` → Show usage guide
|
|
50
|
+
|
|
51
|
+
- `--doctor` → Check environmental health and API connectivity
|
|
52
|
+
|
|
53
|
+
---
|
|
54
|
+
|
|
55
|
+
## Configuration
|
|
56
|
+
|
|
57
|
+
ExplainThisRepo uses Gemini models for code analysis.
|
|
58
|
+
|
|
59
|
+
Set your API key as an environment variable.
|
|
60
|
+
|
|
61
|
+
macOS / Linux
|
|
62
|
+
|
|
63
|
+
```bash
|
|
64
|
+
export GEMINI_API_KEY="your_api_key_here"
|
|
65
|
+
```
|
|
66
|
+
|
|
67
|
+
Windows (PowerShell)
|
|
68
|
+
|
|
69
|
+
```bash
|
|
70
|
+
setx GEMINI_API_KEY "your_api_key_here"
|
|
71
|
+
```
|
|
72
|
+
|
|
73
|
+
Restart your terminal after setting the key.
|
|
12
74
|
|
|
13
75
|
## Installation
|
|
14
76
|
|
|
15
|
-
|
|
77
|
+
### Option 1: install via pip (recommended):
|
|
78
|
+
|
|
79
|
+
Requirements: Python 3.9+
|
|
80
|
+
|
|
81
|
+
```bash
|
|
82
|
+
pip install explainthisrepo
|
|
83
|
+
explainthisrepo owner/repo
|
|
84
|
+
```
|
|
16
85
|
|
|
17
|
-
|
|
18
|
-
```bash
|
|
19
|
-
git clone https://github.com/calchiwo/ExplainThisRepo.git
|
|
20
|
-
cd ExplainThisRepo/node_version
|
|
21
|
-
```
|
|
86
|
+
Alternatively,
|
|
22
87
|
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
88
|
+
```bash
|
|
89
|
+
pipx install explainthisrepo
|
|
90
|
+
explainthisrepo owner/repo
|
|
91
|
+
```
|
|
27
92
|
|
|
28
|
-
|
|
29
|
-
Create a `.env` file in the root directory or export the variable directly in your terminal:
|
|
30
|
-
```bash
|
|
31
|
-
export GEMINI_API_KEY=your_actual_api_key_here
|
|
32
|
-
```
|
|
93
|
+
### Option 2: Install with npm
|
|
33
94
|
|
|
34
|
-
|
|
35
|
-
Compile the TypeScript source code into executable JavaScript:
|
|
36
|
-
```bash
|
|
37
|
-
npm run build
|
|
38
|
-
```
|
|
39
|
-
5 **Link the command globally:**
|
|
95
|
+
Install globally and use forever:
|
|
40
96
|
```bash
|
|
41
|
-
|
|
42
|
-
|
|
97
|
+
npm install -g explainthisrepo
|
|
98
|
+
explainthisrepo owner/repo
|
|
99
|
+
# or: npx explainthisrepo owner/repo
|
|
100
|
+
```
|
|
101
|
+
|
|
102
|
+
---
|
|
43
103
|
|
|
104
|
+
## Flexible Repository Input
|
|
105
|
+
|
|
106
|
+
You don’t need to reformat links anymore.
|
|
107
|
+
|
|
108
|
+
ExplainThisRepo accepts GitHub repositories the way you actually copy them.
|
|
109
|
+
```bash
|
|
110
|
+
explainthisrepo https://github.com/owner/repo
|
|
111
|
+
explainthisrepo github.com/owner/repo
|
|
112
|
+
explainthisrepo https://github.com/owner/repo/issues/123
|
|
113
|
+
explainthisrepo https://github.com/owner/repo?tab=readme
|
|
114
|
+
explainthisrepo git@github.com:owner/repo.git
|
|
115
|
+
```
|
|
116
|
+
|
|
117
|
+
All inputs are normalized internally to owner/repo.
|
|
118
|
+
|
|
119
|
+
---
|
|
44
120
|
|
|
45
121
|
## Usage
|
|
46
122
|
|
|
47
|
-
|
|
123
|
+
### Basic
|
|
124
|
+
Generate a full explanation and saves it to `EXPLAIN.md`:
|
|
125
|
+
|
|
126
|
+
```bash
|
|
127
|
+
explainthisrepo owner/repo
|
|
128
|
+
```
|
|
129
|
+
Example:
|
|
130
|
+
```bash
|
|
131
|
+
explainthisrepo facebook/react
|
|
132
|
+
```
|
|
133
|
+
---
|
|
134
|
+
|
|
135
|
+
### Quick mode
|
|
136
|
+
|
|
137
|
+
Get a one-sentence definition (prints only, no file created):
|
|
138
|
+
```bash
|
|
139
|
+
explainthisrepo owner/repo --quick
|
|
140
|
+
```
|
|
141
|
+
Example:
|
|
142
|
+
```bash
|
|
143
|
+
explainthisrepo facebook/react --quick
|
|
144
|
+
```
|
|
145
|
+

|
|
146
|
+
|
|
147
|
+
---
|
|
148
|
+
|
|
149
|
+
### Detailed mode
|
|
150
|
+
|
|
151
|
+
Generate a more detailed explanation (includes architecture / folder structure):
|
|
152
|
+
```bash
|
|
153
|
+
explainthisrepo owner/repo --detailed
|
|
154
|
+
```
|
|
155
|
+
|
|
156
|
+

|
|
48
157
|
|
|
49
|
-
|
|
50
|
-
Execute the tool by passing the `owner/repo` string as an argument:
|
|
158
|
+
---
|
|
51
159
|
|
|
160
|
+
### Simple mode
|
|
161
|
+
|
|
162
|
+
Prints only the simple output (no EXPLAIN.md)
|
|
163
|
+
```bash
|
|
164
|
+
explainthisrepo owner/repo --simple
|
|
165
|
+
```
|
|
166
|
+
|
|
167
|
+

|
|
168
|
+
|
|
169
|
+
---
|
|
170
|
+
|
|
171
|
+
### Stack detector
|
|
172
|
+
|
|
173
|
+
Get a tech stack breakdown detected from repo signals. No AI explanation. Prints only.
|
|
52
174
|
```bash
|
|
53
|
-
|
|
175
|
+
explainthisrepo owner/repo --stack
|
|
54
176
|
```
|
|
177
|
+

|
|
178
|
+
|
|
179
|
+
### Version
|
|
55
180
|
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
- Project Overview
|
|
61
|
-
- Functional Breakdown
|
|
62
|
-
- Target User Identification
|
|
63
|
-
- Setup/Execution Instructions
|
|
181
|
+
Print the installed version:
|
|
182
|
+
```bash
|
|
183
|
+
explainthisrepo --version
|
|
184
|
+
```
|
|
64
185
|
|
|
65
|
-
|
|
66
|
-
- `npm run build`: Compiles TypeScript to the `dist` folder.
|
|
67
|
-
- `npm run format`: Formats the codebase using Prettier.
|
|
68
|
-
- `npm start`: Runs the tool (requires the repository argument).
|
|
186
|
+
---
|
|
69
187
|
|
|
70
|
-
|
|
188
|
+
### Doctor
|
|
71
189
|
|
|
72
|
-
|
|
190
|
+
Check environment + connectivity (useful for debugging):
|
|
191
|
+
```bash
|
|
192
|
+
explainthisrepo --doctor
|
|
193
|
+
```
|
|
73
194
|
|
|
74
|
-
|
|
75
|
-
- **Feature Requests**: Proposals for new features are always welcome.
|
|
76
|
-
- **Pull Requests**: Ensure your code follows the existing style and all linting passes.
|
|
195
|
+
## Termux (Android) install notes
|
|
77
196
|
|
|
78
|
-
|
|
197
|
+
Termux has some environment limitations that can make `pip install explainthisrepo` fail to create the `explainthisrepo` command in `$PREFIX/bin`.
|
|
198
|
+
|
|
199
|
+
### Recommended install (Termux)
|
|
200
|
+
|
|
201
|
+
```bash
|
|
202
|
+
pip install --user -U explainthisrepo
|
|
203
|
+
```
|
|
204
|
+
|
|
205
|
+
Make sure your user bin directory is on your PATH:
|
|
206
|
+
```bash
|
|
207
|
+
export PATH="$HOME/.local/bin:$PATH"
|
|
208
|
+
```
|
|
209
|
+
> Tip: Add the PATH export to your ~/.bashrc or ~/.zshrc so it persists.
|
|
210
|
+
|
|
211
|
+
Alternative (No PATH changes)
|
|
212
|
+
|
|
213
|
+
If you do not want to modify PATH, you can run ExplainThisRepo as a module:
|
|
214
|
+
|
|
215
|
+
```bash
|
|
216
|
+
python -m explain_this_repo owner/repo
|
|
217
|
+
```
|
|
218
|
+
|
|
219
|
+
Gemini support on Termux (Optional)
|
|
220
|
+
|
|
221
|
+
Installing Gemini support may require building Rust-based dependencies on Android, which can take time on first install:
|
|
222
|
+
|
|
223
|
+
```bash
|
|
224
|
+
pip install --user -U "explainthisrepo[gemini]"
|
|
225
|
+
```
|
|
226
|
+
|
|
227
|
+
## Contributions
|
|
228
|
+
|
|
229
|
+
Contributions are welcome!
|
|
230
|
+
|
|
231
|
+
If you find a bug, have an idea, or want to improve the tool:
|
|
232
|
+
- See [CONTRIBUTING](CONTRIBUTING.md) for setup and guidelines
|
|
233
|
+
- Open an issue for bugs/feature requests
|
|
234
|
+
- Or submit a pull request for fixes/improvements
|
|
235
|
+
|
|
236
|
+
---
|
|
79
237
|
|
|
80
238
|
## License
|
|
81
|
-
This project is licensed under the MIT License as specified in the package configuration.
|
|
82
239
|
|
|
83
|
-
|
|
240
|
+
This project is licensed under the MIT License. See the [LICENSE](LICENSE) file for details.
|
|
241
|
+
|
|
242
|
+
---
|
|
243
|
+
|
|
244
|
+
## Author
|
|
84
245
|
|
|
85
|
-
|
|
246
|
+
Caleb Wodi
|
|
86
247
|
|
|
87
|
-
|
|
248
|
+
- Email: caleb@explainthisrepo.com
|
|
249
|
+
- Twitter: [@calchiwo](https://x.com/calchiwo)
|
|
250
|
+
- LinkedIn: [@calchiwo](https://linkedin.com/in/calchiwo)
|
package/dist/cli.js
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
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";
|
|
@@ -10,6 +11,7 @@ import { buildPrompt, buildQuickPrompt, buildSimplePrompt } from "./prompt.js";
|
|
|
10
11
|
import { generateExplanation } from "./generate.js";
|
|
11
12
|
import { writeOutput } from "./writer.js";
|
|
12
13
|
import { readRepoSignalFiles } from "./repo_reader.js";
|
|
14
|
+
import { readLocalRepoSignalFiles } from "./local_reader.js";
|
|
13
15
|
import { fetchLanguages } from "./github.js";
|
|
14
16
|
import { detectStack } from "./stack-detector.js";
|
|
15
17
|
import { printStack } from "./stack_printer.js";
|
|
@@ -22,8 +24,8 @@ function resolveRepoTarget(target) {
|
|
|
22
24
|
target = target.replace("http//", "http://");
|
|
23
25
|
}
|
|
24
26
|
if (target.startsWith("git@github.com:")) {
|
|
25
|
-
const
|
|
26
|
-
const [owner, repoRaw] =
|
|
27
|
+
const p = target.replace("git@github.com:", "");
|
|
28
|
+
const [owner, repoRaw] = p.split("/", 2);
|
|
27
29
|
if (!owner || !repoRaw)
|
|
28
30
|
throw new Error("Invalid GitHub SSH URL");
|
|
29
31
|
return { owner, repo: repoRaw.replace(/\.git$/, "") };
|
|
@@ -83,10 +85,7 @@ async function checkUrl(url, timeoutMs = 6000) {
|
|
|
83
85
|
clearTimeout(t);
|
|
84
86
|
const message = e instanceof Error ? e.message : String(e);
|
|
85
87
|
const name = e instanceof Error ? e.name : "Error";
|
|
86
|
-
return {
|
|
87
|
-
ok: false,
|
|
88
|
-
msg: `failed (${name}: ${message})`,
|
|
89
|
-
};
|
|
88
|
+
return { ok: false, msg: `failed (${name}: ${message})` };
|
|
90
89
|
}
|
|
91
90
|
}
|
|
92
91
|
async function runDoctor() {
|
|
@@ -135,7 +134,7 @@ async function main() {
|
|
|
135
134
|
.name("explainthisrepo")
|
|
136
135
|
.description("Explain GitHub repositories in plain English")
|
|
137
136
|
.version(getPkgVersion(), "-v, --version", "Show version")
|
|
138
|
-
.argument("[repository]", "GitHub repository (owner/repo or URL)")
|
|
137
|
+
.argument("[repository]", "GitHub repository (owner/repo or URL) or local path")
|
|
139
138
|
.option("--doctor", "Run diagnostics")
|
|
140
139
|
.option("--quick", "Quick summary mode")
|
|
141
140
|
.option("--simple", "Simple summary mode")
|
|
@@ -151,6 +150,9 @@ Examples:
|
|
|
151
150
|
$ explainthisrepo owner/repo --quick
|
|
152
151
|
$ explainthisrepo owner/repo --simple
|
|
153
152
|
$ explainthisrepo owner/repo --stack
|
|
153
|
+
$ explainthisrepo .
|
|
154
|
+
$ explainthisrepo ./path/to/directory
|
|
155
|
+
$ explainthisrepo . --stack
|
|
154
156
|
$ explainthisrepo --doctor`);
|
|
155
157
|
program.parse(process.argv);
|
|
156
158
|
const options = program.opts();
|
|
@@ -172,58 +174,86 @@ Examples:
|
|
|
172
174
|
if (!repository) {
|
|
173
175
|
program.error("repository argument required");
|
|
174
176
|
}
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
177
|
+
const local = fs.existsSync(repository);
|
|
178
|
+
let owner = "";
|
|
179
|
+
let repo = "";
|
|
180
|
+
let localPath = "";
|
|
181
|
+
if (local) {
|
|
182
|
+
localPath = path.resolve(repository);
|
|
183
|
+
console.log(`Analyzing local directory: ${repository}`);
|
|
178
184
|
}
|
|
179
|
-
|
|
180
|
-
const message = e instanceof Error ? e.message : String(e);
|
|
181
|
-
console.error(`error: ${message}`);
|
|
182
|
-
process.exit(1);
|
|
183
|
-
}
|
|
184
|
-
console.log(`Fetching ${owner}/${repo}...`);
|
|
185
|
-
if (options.stack) {
|
|
185
|
+
else {
|
|
186
186
|
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;
|
|
187
|
+
({ owner, repo } = resolveRepoTarget(repository));
|
|
196
188
|
}
|
|
197
189
|
catch (e) {
|
|
198
190
|
const message = e instanceof Error ? e.message : String(e);
|
|
199
191
|
console.error(`error: ${message}`);
|
|
200
192
|
process.exit(1);
|
|
201
193
|
}
|
|
194
|
+
console.log(`Fetching ${owner}/${repo}...`);
|
|
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
|
+
read = readLocalRepoSignalFiles(localPath);
|
|
201
|
+
}
|
|
202
|
+
else {
|
|
203
|
+
try {
|
|
204
|
+
languages = await fetchLanguages(owner, repo);
|
|
205
|
+
read = await readRepoSignalFiles(owner, repo);
|
|
206
|
+
}
|
|
207
|
+
catch (e) {
|
|
208
|
+
const message = e instanceof Error ? e.message : String(e);
|
|
209
|
+
console.error(`error: ${message}`);
|
|
210
|
+
process.exit(1);
|
|
211
|
+
}
|
|
212
|
+
}
|
|
213
|
+
const report = detectStack({
|
|
214
|
+
languages,
|
|
215
|
+
tree: read.tree,
|
|
216
|
+
keyFiles: read.keyFiles,
|
|
217
|
+
});
|
|
218
|
+
const label = local ? repository : owner;
|
|
219
|
+
const sublabel = local ? "" : repo;
|
|
220
|
+
printStack(report, label, sublabel);
|
|
221
|
+
return;
|
|
215
222
|
}
|
|
223
|
+
let repoData = null;
|
|
216
224
|
let readme = null;
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
225
|
+
if (!local) {
|
|
226
|
+
try {
|
|
227
|
+
repoData = await fetchRepo(owner, repo);
|
|
228
|
+
}
|
|
229
|
+
catch (e) {
|
|
230
|
+
const message = e instanceof Error ? e.message : String(e);
|
|
231
|
+
console.error("Failed to fetch repository data.");
|
|
232
|
+
console.error(`error: ${message}`);
|
|
233
|
+
console.error("\nfix:");
|
|
234
|
+
console.error("- Ensure the repository exists and is public");
|
|
235
|
+
console.error("- Or set GITHUB_TOKEN to avoid rate limits");
|
|
236
|
+
process.exit(1);
|
|
237
|
+
}
|
|
238
|
+
try {
|
|
239
|
+
readme = await fetchReadme(owner, repo);
|
|
240
|
+
}
|
|
241
|
+
catch (e) {
|
|
242
|
+
const message = e instanceof Error ? e.message : String(e);
|
|
243
|
+
console.warn(`Warning: Could not fetch README: ${message}`);
|
|
244
|
+
readme = null;
|
|
245
|
+
}
|
|
224
246
|
}
|
|
225
247
|
if (options.quick) {
|
|
226
|
-
|
|
248
|
+
let quickReadme = readme;
|
|
249
|
+
const repoName = local ? localPath : (repoData?.full_name ?? "");
|
|
250
|
+
const description = local ? null : (repoData?.description ?? null);
|
|
251
|
+
if (local) {
|
|
252
|
+
const read = readLocalRepoSignalFiles(localPath);
|
|
253
|
+
const readmeKey = Object.keys(read.keyFiles).find((k) => k.toLowerCase().startsWith("readme"));
|
|
254
|
+
quickReadme = readmeKey !== undefined ? read.keyFiles[readmeKey] : null;
|
|
255
|
+
}
|
|
256
|
+
const prompt = buildQuickPrompt(repoName, description, quickReadme);
|
|
227
257
|
console.log("Generating explanation...");
|
|
228
258
|
const output = await generateWithExit(prompt);
|
|
229
259
|
console.log("Quick summary 🎉");
|
|
@@ -231,16 +261,20 @@ Examples:
|
|
|
231
261
|
return;
|
|
232
262
|
}
|
|
233
263
|
if (options.simple) {
|
|
234
|
-
const readResult =
|
|
235
|
-
|
|
264
|
+
const readResult = local
|
|
265
|
+
? readLocalRepoSignalFiles(localPath)
|
|
266
|
+
: await safeReadRepoFiles(owner, repo);
|
|
267
|
+
const prompt = buildSimplePrompt(local ? localPath : (repoData?.full_name ?? ""), local ? null : (repoData?.description ?? null), local ? null : readme, readResult?.treeText ?? null);
|
|
236
268
|
console.log("Generating explanation...");
|
|
237
269
|
const output = await generateWithExit(prompt);
|
|
238
270
|
console.log("Simple summary 🎉");
|
|
239
271
|
console.log(output.trim());
|
|
240
272
|
return;
|
|
241
273
|
}
|
|
242
|
-
const readResult =
|
|
243
|
-
|
|
274
|
+
const readResult = local
|
|
275
|
+
? readLocalRepoSignalFiles(localPath)
|
|
276
|
+
: await safeReadRepoFiles(owner, repo);
|
|
277
|
+
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);
|
|
244
278
|
console.log("Generating explanation...");
|
|
245
279
|
const output = await generateWithExit(prompt);
|
|
246
280
|
console.log("Writing EXPLAIN.md...");
|
|
@@ -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.0",
|
|
4
4
|
"description": "A CLI developer tool to explain any GitHub repository in plain English",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"type": "module",
|
|
@@ -37,7 +37,8 @@
|
|
|
37
37
|
"scripts": {
|
|
38
38
|
"build": "tsc",
|
|
39
39
|
"start": "node dist/cli.js",
|
|
40
|
-
"
|
|
40
|
+
"sync-meta": "cp ../README.md README.md && cp ../LICENSE LICENSE",
|
|
41
|
+
"prepublishOnly": "npm run sync-meta && npm run build"
|
|
41
42
|
},
|
|
42
43
|
"engines": {
|
|
43
44
|
"node": ">=20"
|