explainthisrepo 0.3.0 → 0.4.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/dist/cli.js +135 -115
- package/dist/prompt.d.ts +3 -2
- package/dist/prompt.js +46 -38
- package/package.json +4 -3
package/dist/cli.js
CHANGED
|
@@ -4,31 +4,15 @@ import process from "node:process";
|
|
|
4
4
|
import { readFileSync } from "node:fs";
|
|
5
5
|
import path from "node:path";
|
|
6
6
|
import { fileURLToPath } from "node:url";
|
|
7
|
+
import { Command } from "commander";
|
|
7
8
|
import { fetchRepo, fetchReadme } from "./github.js";
|
|
8
|
-
import { buildPrompt, buildSimplePrompt } from "./prompt.js";
|
|
9
|
+
import { buildPrompt, buildQuickPrompt, buildSimplePrompt } from "./prompt.js";
|
|
9
10
|
import { generateExplanation } from "./generate.js";
|
|
10
11
|
import { writeOutput } from "./writer.js";
|
|
11
12
|
import { readRepoSignalFiles } from "./repo_reader.js";
|
|
12
13
|
import { fetchLanguages } from "./github.js";
|
|
13
14
|
import { detectStack } from "./stack-detector.js";
|
|
14
15
|
import { printStack } from "./stack_printer.js";
|
|
15
|
-
function usage() {
|
|
16
|
-
const version = getPkgVersion();
|
|
17
|
-
console.log(`ExplainThisRepo v${version}`);
|
|
18
|
-
console.log("Explain GitHub repositories in plain English.\n");
|
|
19
|
-
console.log("usage:");
|
|
20
|
-
console.log(" explainthisrepo owner/repo");
|
|
21
|
-
console.log(" explainthisrepo https://github.com/owner/repo");
|
|
22
|
-
console.log(" explainthisrepo github.com/owner/repo");
|
|
23
|
-
console.log(" explainthisrepo git@github.com:owner/repo.git");
|
|
24
|
-
console.log(" explainthisrepo owner/repo --detailed");
|
|
25
|
-
console.log(" explainthisrepo owner/repo --quick");
|
|
26
|
-
console.log(" explainthisrepo owner/repo --simple");
|
|
27
|
-
console.log(" explainthisrepo owner/repo --stack");
|
|
28
|
-
console.log(" explainthisrepo --doctor");
|
|
29
|
-
console.log(" explainthisrepo --version");
|
|
30
|
-
console.log(" explainthisrepo --help");
|
|
31
|
-
}
|
|
32
16
|
function resolveRepoTarget(target) {
|
|
33
17
|
target = target.trim();
|
|
34
18
|
if (target.startsWith("https//")) {
|
|
@@ -117,134 +101,170 @@ async function runDoctor() {
|
|
|
117
101
|
console.log(`- github api: ${gh.msg}`);
|
|
118
102
|
const gem = await checkUrl("https://generativelanguage.googleapis.com");
|
|
119
103
|
console.log(`- gemini endpoint: ${gem.msg}`);
|
|
120
|
-
return gh.ok ? 0 : 1;
|
|
104
|
+
return gh.ok && gem.ok ? 0 : 1;
|
|
121
105
|
}
|
|
122
106
|
async function main() {
|
|
123
|
-
const
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
107
|
+
const program = new Command();
|
|
108
|
+
program
|
|
109
|
+
.name("explainthisrepo")
|
|
110
|
+
.description("Explain GitHub repositories in plain English")
|
|
111
|
+
.version(getPkgVersion(), "-v, --version", "Show version")
|
|
112
|
+
.argument("[repository]", "GitHub repository (owner/repo or URL)")
|
|
113
|
+
.option("--doctor", "Run diagnostics")
|
|
114
|
+
.option("--quick", "Quick summary mode")
|
|
115
|
+
.option("--simple", "Simple summary mode")
|
|
116
|
+
.option("--detailed", "Detailed explanation mode")
|
|
117
|
+
.option("--stack", "Stack detection mode")
|
|
118
|
+
.addHelpText("after", `
|
|
119
|
+
Examples:
|
|
120
|
+
$ explainthisrepo owner/repo
|
|
121
|
+
$ explainthisrepo https://github.com/owner/repo
|
|
122
|
+
$ explainthisrepo github.com/owner/repo
|
|
123
|
+
$ explainthisrepo git@github.com:owner/repo.git
|
|
124
|
+
$ explainthisrepo owner/repo --detailed
|
|
125
|
+
$ explainthisrepo owner/repo --quick
|
|
126
|
+
$ explainthisrepo owner/repo --simple
|
|
127
|
+
$ explainthisrepo owner/repo --stack
|
|
128
|
+
$ explainthisrepo --doctor`);
|
|
129
|
+
program.parse(process.argv);
|
|
130
|
+
const options = program.opts();
|
|
131
|
+
const repository = program.args[0];
|
|
132
|
+
if (options.doctor) {
|
|
133
133
|
const code = await runDoctor();
|
|
134
134
|
process.exit(code);
|
|
135
135
|
}
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
quick = true;
|
|
145
|
-
else if (args[1] === "--simple")
|
|
146
|
-
simple = true;
|
|
147
|
-
else if (args[1] === "--stack")
|
|
148
|
-
stack = true;
|
|
149
|
-
else {
|
|
150
|
-
usage();
|
|
151
|
-
process.exit(1);
|
|
152
|
-
}
|
|
153
|
-
}
|
|
154
|
-
else if (args.length !== 1) {
|
|
155
|
-
usage();
|
|
136
|
+
const modeFlags = [
|
|
137
|
+
options.quick,
|
|
138
|
+
options.simple,
|
|
139
|
+
options.detailed,
|
|
140
|
+
options.stack,
|
|
141
|
+
].filter(Boolean);
|
|
142
|
+
if (modeFlags.length > 1) {
|
|
143
|
+
console.error("error: only one mode flag can be used at a time");
|
|
156
144
|
process.exit(1);
|
|
157
145
|
}
|
|
158
|
-
if (
|
|
159
|
-
(
|
|
160
|
-
(detailed && quick) ||
|
|
161
|
-
(stack && (quick || simple || detailed))) {
|
|
162
|
-
usage();
|
|
163
|
-
process.exit(1);
|
|
146
|
+
if (!repository) {
|
|
147
|
+
program.error("repository argument required");
|
|
164
148
|
}
|
|
165
149
|
let owner, repo;
|
|
166
150
|
try {
|
|
167
|
-
({ owner, repo } = resolveRepoTarget(
|
|
151
|
+
({ owner, repo } = resolveRepoTarget(repository));
|
|
168
152
|
}
|
|
169
153
|
catch (e) {
|
|
170
|
-
console.
|
|
171
|
-
process.exit(1);
|
|
172
|
-
}
|
|
173
|
-
if (!owner || !repo) {
|
|
174
|
-
console.log("Invalid format. Use owner/repo");
|
|
154
|
+
console.error(`error: ${e.message}`);
|
|
175
155
|
process.exit(1);
|
|
176
156
|
}
|
|
177
157
|
console.log(`Fetching ${owner}/${repo}...`);
|
|
178
|
-
if (stack) {
|
|
179
|
-
const languages = await fetchLanguages(owner, repo);
|
|
180
|
-
const read = await readRepoSignalFiles(owner, repo);
|
|
181
|
-
const report = detectStack({
|
|
182
|
-
languages,
|
|
183
|
-
tree: read.tree,
|
|
184
|
-
keyFiles: read.keyFiles,
|
|
185
|
-
});
|
|
186
|
-
printStack(report, owner, repo);
|
|
187
|
-
return;
|
|
188
|
-
}
|
|
189
|
-
try {
|
|
190
|
-
let repoData;
|
|
158
|
+
if (options.stack) {
|
|
191
159
|
try {
|
|
192
|
-
|
|
160
|
+
const languages = await fetchLanguages(owner, repo);
|
|
161
|
+
const read = await readRepoSignalFiles(owner, repo);
|
|
162
|
+
const report = detectStack({
|
|
163
|
+
languages,
|
|
164
|
+
tree: read.tree,
|
|
165
|
+
keyFiles: read.keyFiles,
|
|
166
|
+
});
|
|
167
|
+
printStack(report, owner, repo);
|
|
168
|
+
return;
|
|
193
169
|
}
|
|
194
170
|
catch (e) {
|
|
195
|
-
console.
|
|
196
|
-
console.log(`error: ${e?.message || e}`);
|
|
197
|
-
console.log("\nfix:");
|
|
198
|
-
console.log("- Ensure the repository exists and is public");
|
|
199
|
-
console.log("- Or set GITHUB_TOKEN to avoid rate limits");
|
|
171
|
+
console.error(`error: ${e?.message || e}`);
|
|
200
172
|
process.exit(1);
|
|
201
173
|
}
|
|
202
|
-
|
|
174
|
+
}
|
|
175
|
+
let repoData;
|
|
176
|
+
try {
|
|
177
|
+
repoData = await fetchRepo(owner, repo);
|
|
178
|
+
}
|
|
179
|
+
catch (e) {
|
|
180
|
+
console.error("Failed to fetch repository data.");
|
|
181
|
+
console.error(`error: ${e?.message || e}`);
|
|
182
|
+
console.error("\nfix:");
|
|
183
|
+
console.error("- Ensure the repository exists and is public");
|
|
184
|
+
console.error("- Or set GITHUB_TOKEN to avoid rate limits");
|
|
185
|
+
process.exit(1);
|
|
186
|
+
}
|
|
187
|
+
let readme = null;
|
|
188
|
+
try {
|
|
189
|
+
readme = await fetchReadme(owner, repo);
|
|
190
|
+
}
|
|
191
|
+
catch (e) {
|
|
192
|
+
console.warn(`Warning: Could not fetch README: ${e?.message || e}`);
|
|
193
|
+
readme = null;
|
|
194
|
+
}
|
|
195
|
+
if (options.quick) {
|
|
196
|
+
const prompt = buildQuickPrompt(repoData.full_name, repoData.description, readme);
|
|
197
|
+
console.log("Generating explanation...");
|
|
198
|
+
let output;
|
|
203
199
|
try {
|
|
204
|
-
|
|
200
|
+
output = await generateExplanation(prompt);
|
|
205
201
|
}
|
|
206
|
-
catch {
|
|
207
|
-
|
|
202
|
+
catch (e) {
|
|
203
|
+
console.error("Failed to generate explanation.");
|
|
204
|
+
console.error(`error: ${e?.message || e}`);
|
|
205
|
+
console.error("\nfix:");
|
|
206
|
+
console.error("- Ensure GEMINI_API_KEY is set");
|
|
207
|
+
console.error("- Or run: explainthisrepo --doctor");
|
|
208
|
+
process.exit(1);
|
|
208
209
|
}
|
|
210
|
+
console.log("Quick summary 🎉");
|
|
211
|
+
console.log(output.trim());
|
|
212
|
+
return;
|
|
213
|
+
}
|
|
214
|
+
if (options.simple) {
|
|
209
215
|
let readResult = null;
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
readResult = await readRepoSignalFiles(owner, repo);
|
|
213
|
-
}
|
|
214
|
-
catch {
|
|
215
|
-
readResult = null;
|
|
216
|
-
}
|
|
216
|
+
try {
|
|
217
|
+
readResult = await readRepoSignalFiles(owner, repo);
|
|
217
218
|
}
|
|
218
|
-
|
|
219
|
+
catch (e) {
|
|
220
|
+
console.warn(`Warning: Could not read repo files: ${e?.message || e}`);
|
|
221
|
+
readResult = null;
|
|
222
|
+
}
|
|
223
|
+
const prompt = buildSimplePrompt(repoData.full_name, repoData.description, readme, readResult?.treeText ?? null);
|
|
219
224
|
console.log("Generating explanation...");
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
console.log(output.trim());
|
|
224
|
-
return;
|
|
225
|
+
let output;
|
|
226
|
+
try {
|
|
227
|
+
output = await generateExplanation(prompt);
|
|
225
228
|
}
|
|
226
|
-
|
|
227
|
-
console.
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
console.
|
|
231
|
-
console.
|
|
232
|
-
|
|
229
|
+
catch (e) {
|
|
230
|
+
console.error("Failed to generate explanation.");
|
|
231
|
+
console.error(`error: ${e?.message || e}`);
|
|
232
|
+
console.error("\nfix:");
|
|
233
|
+
console.error("- Ensure GEMINI_API_KEY is set");
|
|
234
|
+
console.error("- Or run: explainthisrepo --doctor");
|
|
235
|
+
process.exit(1);
|
|
233
236
|
}
|
|
234
|
-
console.log("
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
237
|
+
console.log("Simple summary 🎉");
|
|
238
|
+
console.log(output.trim());
|
|
239
|
+
return;
|
|
240
|
+
}
|
|
241
|
+
let readResult = null;
|
|
242
|
+
try {
|
|
243
|
+
readResult = await readRepoSignalFiles(owner, repo);
|
|
244
|
+
}
|
|
245
|
+
catch (e) {
|
|
246
|
+
console.warn(`Warning: Could not read repo files: ${e?.message || e}`);
|
|
247
|
+
readResult = null;
|
|
248
|
+
}
|
|
249
|
+
const prompt = buildPrompt(repoData.full_name, repoData.description, readme, options.detailed || false, readResult?.treeText ?? null, readResult?.filesText ?? null);
|
|
250
|
+
console.log("Generating explanation...");
|
|
251
|
+
let output;
|
|
252
|
+
try {
|
|
253
|
+
output = await generateExplanation(prompt);
|
|
240
254
|
}
|
|
241
255
|
catch (e) {
|
|
242
|
-
console.
|
|
243
|
-
console.
|
|
244
|
-
console.
|
|
245
|
-
console.
|
|
246
|
-
console.
|
|
256
|
+
console.error("Failed to generate explanation.");
|
|
257
|
+
console.error(`error: ${e?.message || e}`);
|
|
258
|
+
console.error("\nfix:");
|
|
259
|
+
console.error("- Ensure GEMINI_API_KEY is set");
|
|
260
|
+
console.error("- Or run: explainthisrepo --doctor");
|
|
247
261
|
process.exit(1);
|
|
248
262
|
}
|
|
263
|
+
console.log("Writing EXPLAIN.md...");
|
|
264
|
+
writeOutput(output);
|
|
265
|
+
const wordCount = output.split(/\s+/).filter(Boolean).length;
|
|
266
|
+
console.log("EXPLAIN.md generated successfully 🎉");
|
|
267
|
+
console.log(`Words: ${wordCount}`);
|
|
268
|
+
console.log("Open EXPLAIN.md to read it.");
|
|
249
269
|
}
|
|
250
270
|
main();
|
package/dist/prompt.d.ts
CHANGED
|
@@ -1,2 +1,3 @@
|
|
|
1
|
-
export declare function buildPrompt(repoName: string, description: string | null, readme: string | null, detailed?: boolean,
|
|
2
|
-
export declare function
|
|
1
|
+
export declare function buildPrompt(repoName: string, description: string | null, readme: string | null, detailed?: boolean, treeText?: string | null, filesText?: string | null): string;
|
|
2
|
+
export declare function buildQuickPrompt(repoName: string, description: string | null, readme: string | null): string;
|
|
3
|
+
export declare function buildSimplePrompt(repoName: string, description: string | null, readme: string | null, treeText?: string | null): string;
|
package/dist/prompt.js
CHANGED
|
@@ -1,28 +1,4 @@
|
|
|
1
|
-
export function buildPrompt(repoName, description, readme, detailed = false,
|
|
2
|
-
if (quick) {
|
|
3
|
-
const readmeSnippet = (readme || "").slice(0, 2000);
|
|
4
|
-
return `
|
|
5
|
-
You are a senior software engineer.
|
|
6
|
-
|
|
7
|
-
Write a ONE-SENTENCE plain-English definition of what this GitHub repository is.
|
|
8
|
-
|
|
9
|
-
Repository:
|
|
10
|
-
- Name: ${repoName}
|
|
11
|
-
- Description: ${description || "No description provided"}
|
|
12
|
-
|
|
13
|
-
README snippet:
|
|
14
|
-
${readmeSnippet || "No README provided"}
|
|
15
|
-
|
|
16
|
-
Rules:
|
|
17
|
-
- Output MUST be exactly 1 sentence.
|
|
18
|
-
- Plain English.
|
|
19
|
-
- No markdown.
|
|
20
|
-
- No quotes.
|
|
21
|
-
- No bullet points.
|
|
22
|
-
- No extra text.
|
|
23
|
-
- Do not add features not stated in the description/README.
|
|
24
|
-
`.trim();
|
|
25
|
-
}
|
|
1
|
+
export function buildPrompt(repoName, description, readme, detailed = false, treeText = null, filesText = null) {
|
|
26
2
|
let prompt = `You are a senior software engineer.
|
|
27
3
|
|
|
28
4
|
Your task is to explain a GitHub repository clearly and concisely for a human reader.
|
|
@@ -34,10 +10,10 @@ Repository:
|
|
|
34
10
|
README content:
|
|
35
11
|
${readme || "No README provided"}
|
|
36
12
|
|
|
37
|
-
|
|
38
|
-
${treeText || "No tree provided"}
|
|
13
|
+
Repo structure:
|
|
14
|
+
${treeText || "No file tree provided"}
|
|
39
15
|
|
|
40
|
-
Key files
|
|
16
|
+
Key code files:
|
|
41
17
|
${filesText || "No code files provided"}
|
|
42
18
|
|
|
43
19
|
Instructions:
|
|
@@ -70,14 +46,46 @@ Output format:
|
|
|
70
46
|
`;
|
|
71
47
|
return prompt.trim();
|
|
72
48
|
}
|
|
73
|
-
export function
|
|
74
|
-
|
|
75
|
-
You are a senior software engineer.
|
|
49
|
+
export function buildQuickPrompt(repoName, description, readme) {
|
|
50
|
+
const readmeSnippet = (readme || "No README provided").slice(0, 2000);
|
|
51
|
+
const prompt = `You are a senior software engineer.
|
|
52
|
+
|
|
53
|
+
Write a ONE-SENTENCE plain-English definition of what this GitHub repository is.
|
|
54
|
+
|
|
55
|
+
Repository:
|
|
56
|
+
- Name: ${repoName}
|
|
57
|
+
- Description: ${description || "No description provided"}
|
|
58
|
+
|
|
59
|
+
README snippet:
|
|
60
|
+
${readmeSnippet}
|
|
61
|
+
|
|
62
|
+
Rules:
|
|
63
|
+
- Output MUST be exactly 1 sentence.
|
|
64
|
+
- Plain English.
|
|
65
|
+
- No markdown.
|
|
66
|
+
- No quotes.
|
|
67
|
+
- No bullet points.
|
|
68
|
+
- No extra text.
|
|
69
|
+
- Do not add features not stated in the description/README.
|
|
70
|
+
`;
|
|
71
|
+
return prompt.trim();
|
|
72
|
+
}
|
|
73
|
+
export function buildSimplePrompt(repoName, description, readme, treeText = null) {
|
|
74
|
+
const readmeContent = (readme || "No README provided").slice(0, 4000);
|
|
75
|
+
const treeContent = (treeText || "No file tree provided").slice(0, 1500);
|
|
76
|
+
const prompt = `You are a senior software engineer.
|
|
77
|
+
|
|
78
|
+
Summarize this GitHub repository in a concise bullet-point format.
|
|
79
|
+
|
|
80
|
+
Repository:
|
|
81
|
+
- Name: ${repoName}
|
|
82
|
+
- Description: ${description || "No description provided"}
|
|
76
83
|
|
|
77
|
-
|
|
84
|
+
README content:
|
|
85
|
+
${readmeContent}
|
|
78
86
|
|
|
79
|
-
|
|
80
|
-
${
|
|
87
|
+
Repo structure:
|
|
88
|
+
${treeContent}
|
|
81
89
|
|
|
82
90
|
Output style rules:
|
|
83
91
|
- Plain English.
|
|
@@ -89,13 +97,13 @@ Key points from the repo:
|
|
|
89
97
|
- Each bullet MUST start with: ⬤
|
|
90
98
|
- Each bullet title should be 1–3 words only (example: "Purpose", "Stack", "Entrypoints", "How it works", "Usage", "Structure").
|
|
91
99
|
- Each bullet body should be 1–2 lines max.
|
|
92
|
-
-
|
|
93
|
-
-
|
|
100
|
+
- Base bullets strictly on the provided README and structure.
|
|
101
|
+
- Do NOT invent features, architecture, or details not present in the input.
|
|
94
102
|
- Optional: end with one extra line starting with:
|
|
95
103
|
Also interesting:
|
|
96
|
-
- Do NOT add features not present in the input.
|
|
97
104
|
- No quotes.
|
|
98
105
|
|
|
99
106
|
Make it feel like a human developer explaining to another developer in simple terms.
|
|
100
|
-
|
|
107
|
+
`;
|
|
108
|
+
return prompt.trim();
|
|
101
109
|
}
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "explainthisrepo",
|
|
3
|
-
"version": "0.
|
|
4
|
-
"description": "
|
|
3
|
+
"version": "0.4.1",
|
|
4
|
+
"description": "A CLI developer tool to explain any GitHub repository in plain English",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"type": "module",
|
|
7
7
|
"author": "Caleb Wodi <calebwodi33@gmail.com>",
|
|
@@ -40,11 +40,12 @@
|
|
|
40
40
|
"prepublishOnly": "npm run build"
|
|
41
41
|
},
|
|
42
42
|
"engines": {
|
|
43
|
-
"node": ">=
|
|
43
|
+
"node": ">=20"
|
|
44
44
|
},
|
|
45
45
|
"dependencies": {
|
|
46
46
|
"@google/generative-ai": "^0.24.1",
|
|
47
47
|
"axios": "^1.13.2",
|
|
48
|
+
"commander": "^14.0.3",
|
|
48
49
|
"dotenv": "^17.2.3"
|
|
49
50
|
},
|
|
50
51
|
"devDependencies": {
|