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 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 args = process.argv.slice(2);
124
- if (args.length === 0 || args[0] === "-h" || args[0] === "--help") {
125
- usage();
126
- process.exit(0);
127
- }
128
- if (args[0] === "--version") {
129
- printVersion();
130
- process.exit(0);
131
- }
132
- if (args[0] === "--doctor") {
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
- let detailed = false;
137
- let quick = false;
138
- let simple = false;
139
- let stack = false;
140
- if (args.length === 2) {
141
- if (args[1] === "--detailed")
142
- detailed = true;
143
- else if (args[1] === "--quick")
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 ((quick && simple) ||
159
- (detailed && simple) ||
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(args[0]));
151
+ ({ owner, repo } = resolveRepoTarget(repository));
168
152
  }
169
153
  catch (e) {
170
- console.log(`error: ${e.message}`);
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
- repoData = await fetchRepo(owner, repo);
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.log("Failed to fetch repository data.");
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
- let readme = null;
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
- readme = await fetchReadme(owner, repo);
200
+ output = await generateExplanation(prompt);
205
201
  }
206
- catch {
207
- readme = null;
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
- if (!quick) {
211
- try {
212
- readResult = await readRepoSignalFiles(owner, repo);
213
- }
214
- catch {
215
- readResult = null;
216
- }
216
+ try {
217
+ readResult = await readRepoSignalFiles(owner, repo);
217
218
  }
218
- const prompt = buildPrompt(repoData.full_name, repoData.description, readme, detailed, quick, readResult?.treeText ?? null, readResult?.filesText ?? null);
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
- const output = await generateExplanation(prompt);
221
- if (quick) {
222
- console.log("Quick summary 🎉");
223
- console.log(output.trim());
224
- return;
225
+ let output;
226
+ try {
227
+ output = await generateExplanation(prompt);
225
228
  }
226
- if (simple) {
227
- console.log("Summarizing...");
228
- const simplePrompt = buildSimplePrompt(output);
229
- const simpleOutput = await generateExplanation(simplePrompt);
230
- console.log("Simple summary 🎉");
231
- console.log(simpleOutput.trim());
232
- return;
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("Writing EXPLAIN.md...");
235
- writeOutput(output);
236
- const wordCount = output.split(/\s+/).filter(Boolean).length;
237
- console.log("EXPLAIN.md generated successfully 🎉");
238
- console.log(`Words: ${wordCount}`);
239
- console.log("Open EXPLAIN.md to read it.");
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.log("Failed to generate explanation.");
243
- console.log(`error: ${e?.message || e}`);
244
- console.log("\nfix:");
245
- console.log("- Ensure GEMINI_API_KEY is set");
246
- console.log("- Or run: explainthisrepo --doctor");
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, quick?: boolean, treeText?: string | null, filesText?: string | null): string;
2
- export declare function buildSimplePrompt(longExplanation: string): string;
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, quick = false, treeText = null, filesText = null) {
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
- Repository structure:
38
- ${treeText || "No tree provided"}
13
+ Repo structure:
14
+ ${treeText || "No file tree provided"}
39
15
 
40
- Key files (snippets):
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 buildSimplePrompt(longExplanation) {
74
- return `
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
- Rewrite the long repository explanation below into a SIMPLE version in the exact style specified.
84
+ README content:
85
+ ${readmeContent}
78
86
 
79
- Input explanation:
80
- ${longExplanation}
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
- - If the input contains architecture/pipeline steps, capture them naturally.
93
- - If the input does NOT contain architecture/pipeline steps, do NOT invent them.
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
- `.trim();
107
+ `;
108
+ return prompt.trim();
101
109
  }
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "explainthisrepo",
3
- "version": "0.3.0",
4
- "description": "ExplainThisRepo is a CLI developer tool to explain any GitHub repository in plain English",
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": ">=18"
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": {