explainthisrepo 0.1.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 ADDED
@@ -0,0 +1,87 @@
1
+ # ExplainThisRepo
2
+
3
+ This folder contains a high-performance, lightweight port of the ExplainThisRepo CLI tool.
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.
5
+
6
+ ## Features
7
+ - **Automated Repository Analysis**: Seamlessly fetches repository data and documentation via the GitHub REST API.
8
+ - **AI-Driven Contextualization**: Uses Google Gemini 2.5 Flash Lite to extract technical value and purpose from codebases.
9
+ - **Senior Engineer Perspective**: Generates explanations tailored for developers, focusing on architecture, target audience, and execution.
10
+ - **Markdown Generation**: Automatically outputs a clean, structured `EXPLAIN.md` file for immediate reading.
11
+ - **TypeScript Core**: Built with type safety and modern asynchronous patterns for reliable performance.
12
+
13
+ ## Installation
14
+
15
+ Follow these steps to set up the project locally on your machine:
16
+
17
+ 1. **Clone the Repository**
18
+ ```bash
19
+ git clone https://github.com/Spectra010s/ExplainThisRepo.git
20
+ cd ExplainThisRepo/node_version
21
+ ```
22
+
23
+ 2. **Install Dependencies**
24
+ ```bash
25
+ npm install
26
+ ```
27
+
28
+ 3. **Set Up Environment Variables**
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
+ ```
33
+
34
+ 4. **Build the Project**
35
+ Compile the TypeScript source code into executable JavaScript:
36
+ ```bash
37
+ npm run build
38
+ ```
39
+ 5 **Link the command globally:**
40
+ ```bash
41
+ npm link
42
+ ```
43
+
44
+
45
+ ## Usage
46
+
47
+ Once the project is built, you can use the CLI tool to analyze any public GitHub repository.
48
+
49
+ ### Running the CLI
50
+ Execute the tool by passing the `owner/repo` string as an argument:
51
+
52
+ ```bash
53
+ node dist/cli.js facebook/react
54
+ ```
55
+
56
+ ### Expected Workflow
57
+ 1. The tool fetches the repository description and README from GitHub.
58
+ 2. It processes the information and sends a structured prompt to the Gemini AI.
59
+ 3. An `EXPLAIN.md` file is generated in your current working directory containing:
60
+ - Project Overview
61
+ - Functional Breakdown
62
+ - Target User Identification
63
+ - Setup/Execution Instructions
64
+
65
+ ### Scripts
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).
69
+
70
+ ## Contributing
71
+
72
+ Contributions are what make the open-source community an amazing place to learn, inspire, and create. Any contributions you make are greatly appreciated.
73
+
74
+ -️ **Report Bugs**: Open an issue if you find any unexpected behavior.
75
+ - **Feature Requests**: Proposals for new features are always welcome.
76
+ - **Pull Requests**: Ensure your code follows the existing style and all linting passes.
77
+
78
+ > Note: Please run npm run format before submitting a Pull Request to ensure code consistency.
79
+
80
+ ## License
81
+ This project is licensed under the MIT License as specified in the package configuration.
82
+
83
+ ## Author Info
84
+
85
+ **Spectra010s**
86
+ - [Portfolio](https://spectra010s.vercel.app)
87
+ - [Twitter/X](https://x.com/Spectra010s)
package/dist/cli.d.ts ADDED
@@ -0,0 +1,2 @@
1
+ #!/usr/bin/env node
2
+ export {};
package/dist/cli.js ADDED
@@ -0,0 +1,117 @@
1
+ #!/usr/bin/env node
2
+ import os from "node:os";
3
+ import process from "node:process";
4
+ import { fetchRepo, fetchReadme } from "./github.js";
5
+ import { buildPrompt } from "./prompt.js";
6
+ import { generateExplanation } from "./generate.js";
7
+ import { writeOutput } from "./writer.js";
8
+ function usage() {
9
+ console.log("usage:");
10
+ console.log(" explainthisrepo owner/repo");
11
+ console.log(" explainthisrepo owner/repo --detailed");
12
+ console.log(" explainthisrepo --doctor");
13
+ console.log(" explainthisrepo --version");
14
+ }
15
+ function getPkgVersion() {
16
+ return process.env.npm_package_version || "unknown";
17
+ }
18
+ function printVersion() {
19
+ console.log(getPkgVersion());
20
+ }
21
+ function hasEnv(key) {
22
+ const v = process.env[key];
23
+ return Boolean(v && v.trim());
24
+ }
25
+ async function checkUrl(url, timeoutMs = 6000) {
26
+ const controller = new AbortController();
27
+ const t = setTimeout(() => controller.abort(), timeoutMs);
28
+ try {
29
+ const res = await fetch(url, {
30
+ method: "GET",
31
+ headers: { "User-Agent": "explainthisrepo" },
32
+ signal: controller.signal,
33
+ });
34
+ clearTimeout(t);
35
+ return { ok: res.ok, msg: `ok (${res.status})` };
36
+ }
37
+ catch (e) {
38
+ clearTimeout(t);
39
+ return { ok: false, msg: `failed (${e?.name || "Error"}: ${e?.message || e})` };
40
+ }
41
+ }
42
+ async function runDoctor() {
43
+ console.log("explainthisrepo doctor report\n");
44
+ console.log(`node: ${process.version}`);
45
+ console.log(`os: ${os.type()} ${os.release()}`);
46
+ console.log(`platform: ${process.platform} ${process.arch}`);
47
+ console.log(`version: ${getPkgVersion()}`);
48
+ console.log("\nenvironment:");
49
+ console.log(`- GEMINI_API_KEY set: ${hasEnv("GEMINI_API_KEY")}`);
50
+ console.log(`- GITHUB_TOKEN set: ${hasEnv("GITHUB_TOKEN")}`);
51
+ console.log("\nnetwork checks:");
52
+ const gh = await checkUrl("https://api.github.com");
53
+ console.log(`- github api: ${gh.msg}`);
54
+ const gem = await checkUrl("https://generativelanguage.googleapis.com");
55
+ console.log(`- gemini endpoint: ${gem.msg}`);
56
+ return gh.ok ? 0 : 1;
57
+ }
58
+ async function main() {
59
+ const args = process.argv.slice(2);
60
+ if (args.length === 0 || args[0] === "-h" || args[0] === "--help") {
61
+ usage();
62
+ process.exit(0);
63
+ }
64
+ if (args[0] === "--version") {
65
+ printVersion();
66
+ process.exit(0);
67
+ }
68
+ if (args[0] === "--doctor") {
69
+ const code = await runDoctor();
70
+ process.exit(code);
71
+ }
72
+ let detailed = false;
73
+ if (args.length === 2) {
74
+ if (args[1] !== "--detailed") {
75
+ usage();
76
+ process.exit(1);
77
+ }
78
+ detailed = true;
79
+ }
80
+ else if (args.length !== 1) {
81
+ usage();
82
+ process.exit(1);
83
+ }
84
+ const target = args[0];
85
+ if (!target.includes("/") || target.split("/").length !== 2) {
86
+ console.log("Invalid format. Use owner/repo");
87
+ process.exit(1);
88
+ }
89
+ const [owner, repo] = target.split("/");
90
+ if (!owner || !repo) {
91
+ console.log("Invalid format. Use owner/repo");
92
+ process.exit(1);
93
+ }
94
+ console.log(`Fetching ${owner}/${repo}...`);
95
+ try {
96
+ const repoData = await fetchRepo(owner, repo);
97
+ const readme = await fetchReadme(owner, repo);
98
+ const prompt = buildPrompt(repoData.full_name, repoData.description, readme, detailed);
99
+ console.log("Generating explanation...");
100
+ const output = await generateExplanation(prompt);
101
+ console.log("Writing EXPLAIN.md...");
102
+ writeOutput(output);
103
+ const wordCount = output.split(/\s+/).filter(Boolean).length;
104
+ console.log("EXPLAIN.md generated successfully 🎉");
105
+ console.log(`Words: ${wordCount}`);
106
+ console.log("Open EXPLAIN.md to read it.");
107
+ }
108
+ catch (e) {
109
+ console.log("Failed to generate explanation.");
110
+ console.log(`error: ${e?.message || e}`);
111
+ console.log("\nfix:");
112
+ console.log("- Ensure GEMINI_API_KEY is set");
113
+ console.log("- Or run: explainthisrepo --doctor");
114
+ process.exit(1);
115
+ }
116
+ }
117
+ main();
@@ -0,0 +1 @@
1
+ export declare function generateExplanation(prompt: string): Promise<string>;
@@ -0,0 +1,36 @@
1
+ import { GoogleGenerativeAI } from "@google/generative-ai";
2
+ const DEFAULT_MODEL = "gemini-2.5-flash-lite";
3
+ function getApiKey() {
4
+ const key = process.env.GEMINI_API_KEY;
5
+ if (!key || !key.trim()) {
6
+ throw new Error([
7
+ "GEMINI_API_KEY is not set.",
8
+ "",
9
+ "Fix:",
10
+ ' export GEMINI_API_KEY="your_key_here"',
11
+ ].join("\n"));
12
+ }
13
+ return key.trim();
14
+ }
15
+ export async function generateExplanation(prompt) {
16
+ const apiKey = getApiKey();
17
+ const genAI = new GoogleGenerativeAI(apiKey);
18
+ const modelName = (process.env.GEMINI_MODEL || DEFAULT_MODEL).trim();
19
+ const model = genAI.getGenerativeModel({ model: modelName });
20
+ try {
21
+ const result = await model.generateContent(prompt);
22
+ const text = result?.response?.text?.() ?? "";
23
+ if (!text.trim()) {
24
+ throw new Error("Gemini returned no text");
25
+ }
26
+ return text.trim();
27
+ }
28
+ catch (err) {
29
+ const msg = err?.message ? String(err.message) : String(err);
30
+ throw new Error([
31
+ "Failed to generate explanation (Gemini).",
32
+ `Model: ${modelName}`,
33
+ `Error: ${msg}`,
34
+ ].join("\n"));
35
+ }
36
+ }
@@ -0,0 +1,2 @@
1
+ export declare function fetchRepo(owner: string, repo: string): Promise<any>;
2
+ export declare function fetchReadme(owner: string, repo: string): Promise<string | null>;
package/dist/github.js ADDED
@@ -0,0 +1,97 @@
1
+ import axios from "axios";
2
+ const GITHUB_API_BASE = "https://api.github.com";
3
+ function sleep(ms) {
4
+ return new Promise((resolve) => setTimeout(resolve, ms));
5
+ }
6
+ function getGithubHeaders() {
7
+ const headers = {
8
+ Accept: "application/vnd.github.v3+json",
9
+ "User-Agent": "ExplainThisRepo",
10
+ };
11
+ const token = process.env.GITHUB_TOKEN || process.env.GITHUB_API_KEY;
12
+ if (token && token.trim()) {
13
+ headers.Authorization = `Bearer ${token.trim()}`;
14
+ }
15
+ return headers;
16
+ }
17
+ const github = axios.create({
18
+ baseURL: GITHUB_API_BASE,
19
+ headers: getGithubHeaders(),
20
+ timeout: 12000,
21
+ });
22
+ function formatAxiosError(err) {
23
+ if (!axios.isAxiosError(err))
24
+ return "Unknown error";
25
+ const status = err.response?.status;
26
+ const data = err.response?.data;
27
+ const msg = data?.message || err.message;
28
+ if (status === 404)
29
+ return "Repository not found (404). Check owner/repo.";
30
+ if (status === 401)
31
+ return "GitHub auth failed (401). Invalid GITHUB_TOKEN.";
32
+ if (status === 403 && typeof msg === "string" && msg.toLowerCase().includes("rate limit")) {
33
+ return "GitHub rate limit hit (403). Set GITHUB_TOKEN to increase limits.";
34
+ }
35
+ if (status === 429)
36
+ return "GitHub rate limit hit (429). Try again in a minute.";
37
+ if (status && status >= 500)
38
+ return `GitHub server error (${status}). Try again later.`;
39
+ return `GitHub request failed (${status ?? "no status"}): ${msg}`;
40
+ }
41
+ async function requestWithRetry(fn, opts) {
42
+ const maxRetries = opts?.maxRetries ?? 3;
43
+ const baseDelayMs = opts?.baseDelayMs ?? 700;
44
+ let attempt = 0;
45
+ while (true) {
46
+ try {
47
+ return await fn();
48
+ }
49
+ catch (err) {
50
+ attempt += 1;
51
+ const isAxios = axios.isAxiosError(err);
52
+ const status = isAxios ? err.response?.status : undefined;
53
+ const retryable = status === 403 || status === 429 || (status !== undefined && status >= 500);
54
+ if (!retryable || attempt > maxRetries) {
55
+ throw new Error(formatAxiosError(err));
56
+ }
57
+ const resetHeader = isAxios ? err.response?.headers?.["x-ratelimit-reset"] : undefined;
58
+ if ((status === 403 || status === 429) && resetHeader) {
59
+ const resetSeconds = Number(resetHeader);
60
+ if (!Number.isNaN(resetSeconds)) {
61
+ const waitMs = Math.max(resetSeconds * 1000 - Date.now(), 1000);
62
+ await sleep(Math.min(waitMs, 30_000));
63
+ continue;
64
+ }
65
+ }
66
+ const delay = baseDelayMs * Math.pow(2, attempt - 1);
67
+ await sleep(Math.min(delay, 8000));
68
+ }
69
+ }
70
+ }
71
+ export async function fetchRepo(owner, repo) {
72
+ return requestWithRetry(async () => {
73
+ const res = await github.get(`/repos/${owner}/${repo}`);
74
+ return res.data;
75
+ });
76
+ }
77
+ export async function fetchReadme(owner, repo) {
78
+ try {
79
+ return await requestWithRetry(async () => {
80
+ const res = await github.get(`/repos/${owner}/${repo}/readme`, {
81
+ headers: {
82
+ ...getGithubHeaders(),
83
+ Accept: "application/vnd.github.v3.raw",
84
+ },
85
+ });
86
+ return res.data;
87
+ });
88
+ }
89
+ catch (err) {
90
+ if (axios.isAxiosError(err)) {
91
+ const status = err.response?.status;
92
+ if (status === 404)
93
+ return null;
94
+ }
95
+ throw err;
96
+ }
97
+ }
@@ -0,0 +1 @@
1
+ export declare function buildPrompt(repoName: string, description: string | null, readme: string | null, detailed?: boolean): string;
package/dist/prompt.js ADDED
@@ -0,0 +1,43 @@
1
+ export function buildPrompt(repoName, description, readme, detailed = false) {
2
+ let prompt = `
3
+ You are a senior software engineer.
4
+
5
+ Your task is to explain a GitHub repository clearly and concisely for a human reader.
6
+
7
+ Repository:
8
+ - Name: ${repoName}
9
+ - Description: ${description || "No description provided"}
10
+
11
+ README content:
12
+ ${readme || "No README provided"}
13
+
14
+ Instructions:
15
+ - Explain what this project does.
16
+ - Say who it is for.
17
+ - Explain how to run or use it.
18
+ - Do not assume missing details.
19
+ - If something is unclear, say so.
20
+ - Avoid hype or marketing language.
21
+ - Be concise and practical.
22
+ - Use clear markdown headings.
23
+ `.trim();
24
+ if (detailed) {
25
+ prompt += `
26
+
27
+ Additional instructions:
28
+ - Explain the high-level architecture.
29
+ - Describe the folder structure.
30
+ - Mention important files and their roles.
31
+ `;
32
+ }
33
+ prompt += `
34
+
35
+ Output format:
36
+ # Overview
37
+ # What this project does
38
+ # Who it is for
39
+ # How to run or use it
40
+ # Notes or limitations
41
+ `;
42
+ return prompt.trim();
43
+ }
@@ -0,0 +1 @@
1
+ export declare function writeOutput(content: string): void;
package/dist/writer.js ADDED
@@ -0,0 +1,6 @@
1
+ import fs from "fs";
2
+ import path from "path";
3
+ export function writeOutput(content) {
4
+ const outputPath = path.join(process.cwd(), "EXPLAIN.md");
5
+ fs.writeFileSync(outputPath, content, "utf8");
6
+ }
package/package.json ADDED
@@ -0,0 +1,54 @@
1
+ {
2
+ "name": "explainthisrepo",
3
+ "version": "0.1.0",
4
+ "description": "CLI tool to explain a GitHub repository in plain English",
5
+ "license": "MIT",
6
+ "type": "module",
7
+ "author": "Caleb Wodi <calebwodi33@gmail.com>",
8
+ "homepage": "https://explainthisrepo.com",
9
+ "repository": {
10
+ "type": "git",
11
+ "url": "git+https://github.com/calchiwo/ExplainThisRepo.git",
12
+ "directory": "node_version"
13
+ },
14
+ "bugs": {
15
+ "url": "https://github.com/calchiwo/ExplainThisRepo/issues"
16
+ },
17
+ "keywords": [
18
+ "github",
19
+ "cli",
20
+ "explain",
21
+ "repository",
22
+ "ai",
23
+ "developer-tools"
24
+ ],
25
+ "bin": {
26
+ "explainthisrepo": "./dist/cli.js"
27
+ },
28
+ "main": "./dist/index.js",
29
+ "exports": {
30
+ ".": "./dist/index.js"
31
+ },
32
+ "files": [
33
+ "dist",
34
+ "README.md",
35
+ "LICENSE"
36
+ ],
37
+ "scripts": {
38
+ "build": "tsc",
39
+ "start": "node dist/cli.js",
40
+ "prepublishOnly": "npm run build"
41
+ },
42
+ "engines": {
43
+ "node": ">=18"
44
+ },
45
+ "dependencies": {
46
+ "@google/generative-ai": "^0.24.1",
47
+ "axios": "^1.13.2",
48
+ "dotenv": "^17.2.3"
49
+ },
50
+ "devDependencies": {
51
+ "@types/node": "^22.0.0",
52
+ "typescript": "^5.0.0"
53
+ }
54
+ }