readmeg 1.0.0 → 1.0.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 +68 -78
- package/bin/readmeg.js +542 -30
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -5,27 +5,27 @@
|
|
|
5
5
|
[![Stargazers][stars-shield]][stars-url]
|
|
6
6
|
[![Issues][issues-shield]][issues-url]
|
|
7
7
|
[![MIT License][license-shield]][license-url]
|
|
8
|
-
[![
|
|
8
|
+
[![Twitter][twitter-shield]][twitter-url]
|
|
9
9
|
|
|
10
10
|
<br />
|
|
11
11
|
<div align="center">
|
|
12
|
-
<a href="https://github.com/
|
|
13
|
-
<img src="
|
|
12
|
+
<a href="https://github.com/iamtyroon/readme-gen">
|
|
13
|
+
<img src="assets/logo.png" alt="Logo" width="80" height="80">
|
|
14
14
|
</a>
|
|
15
15
|
|
|
16
|
-
|
|
16
|
+
<h3 align="center">readmeg</h3>
|
|
17
17
|
|
|
18
18
|
<p align="center">
|
|
19
|
-
|
|
19
|
+
Zero-config terminal tool powered by Gemini that automatically generates comprehensive, production-grade README files for local repositories.
|
|
20
20
|
<br />
|
|
21
|
-
<a href="https://github.com/
|
|
21
|
+
<a href="https://github.com/iamtyroon/readme-gen"><strong>Explore the docs »</strong></a>
|
|
22
22
|
<br />
|
|
23
23
|
<br />
|
|
24
|
-
<a href="https://github.com/
|
|
24
|
+
<a href="https://github.com/iamtyroon/readme-gen">View Demo</a>
|
|
25
25
|
·
|
|
26
|
-
<a href="https://github.com/
|
|
26
|
+
<a href="https://github.com/iamtyroon/readme-gen/issues">Report Bug</a>
|
|
27
27
|
·
|
|
28
|
-
<a href="https://github.com/
|
|
28
|
+
<a href="https://github.com/iamtyroon/readme-gen/issues">Request Feature</a>
|
|
29
29
|
</p>
|
|
30
30
|
</div>
|
|
31
31
|
|
|
@@ -56,27 +56,25 @@
|
|
|
56
56
|
|
|
57
57
|
## About The Project
|
|
58
58
|
|
|
59
|
-
[![
|
|
59
|
+
[![Product Name Screen Shot][product-screenshot]](https://github.com/iamtyroon/readme-gen)
|
|
60
60
|
|
|
61
|
-
Writing a high-quality README is often the most tedious part of finishing a project. `readmeg`
|
|
61
|
+
Writing a comprehensive, high-quality README is often the most tedious part of finishing a project. `readmeg` solves this by automating the entire process.
|
|
62
|
+
|
|
63
|
+
By scanning your local repository structure, analyzing key manifest files (such as `package.json`, `tsconfig.json`, and `.env.example`), and leveraging the advanced contextual understanding of Google's Gemini AI, `readmeg` generates a production-grade, beautifully formatted README in seconds.
|
|
62
64
|
|
|
63
65
|
Here's why you should use `readmeg`:
|
|
64
|
-
* **
|
|
65
|
-
* **
|
|
66
|
-
* **
|
|
67
|
-
* **
|
|
66
|
+
* **Zero-Config**: No complex setup or configuration files required.
|
|
67
|
+
* **Context-Aware**: It parses your actual codebase structure and dependencies to write accurate documentation.
|
|
68
|
+
* **Interactive CLI**: Prompts you for key details to customize the output to your exact needs.
|
|
69
|
+
* **Time-Saving**: Instantly generates standard sections like Installation, Usage, Roadmap, and License.
|
|
68
70
|
|
|
69
71
|
<p align="right">(<a href="#readme-top">back to top</a>)</p>
|
|
70
72
|
|
|
71
73
|
### Built With
|
|
72
74
|
|
|
73
|
-
This project is built using modern Node.js and TypeScript, leveraging powerful CLI and AI libraries:
|
|
74
|
-
|
|
75
75
|
* [![Node.js][Node.js]][Node-url]
|
|
76
|
-
* [![
|
|
77
|
-
* [![
|
|
78
|
-
* [![Commander.js][Commander]][Commander-url]
|
|
79
|
-
* [![Inquirer.js][Inquirer]][Inquirer-url]
|
|
76
|
+
* [![Gemini][Gemini.js]][Gemini-url]
|
|
77
|
+
* [![TypeScript][TypeScript.js]][TypeScript-url]
|
|
80
78
|
|
|
81
79
|
<p align="right">(<a href="#readme-top">back to top</a>)</p>
|
|
82
80
|
|
|
@@ -86,69 +84,66 @@ To get a local copy up and running, follow these simple steps.
|
|
|
86
84
|
|
|
87
85
|
### Prerequisites
|
|
88
86
|
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
* npm
|
|
92
|
-
```sh
|
|
93
|
-
npm install npm@latest -g
|
|
94
|
-
```
|
|
87
|
+
* **Node.js** (v18.0.0 or higher recommended)
|
|
88
|
+
* **Gemini API Key**: You will need an API key from Google AI Studio.
|
|
95
89
|
|
|
96
90
|
### Installation
|
|
97
91
|
|
|
98
|
-
1.
|
|
99
|
-
2. Clone the repository:
|
|
92
|
+
1. Clone the repository:
|
|
100
93
|
```sh
|
|
101
|
-
git clone https://github.com/
|
|
94
|
+
git clone https://github.com/iamtyroon/readme-gen.git
|
|
102
95
|
```
|
|
103
|
-
|
|
96
|
+
2. Navigate to the project directory and install dependencies:
|
|
104
97
|
```sh
|
|
98
|
+
cd readme-gen
|
|
105
99
|
npm install
|
|
106
100
|
```
|
|
107
|
-
|
|
101
|
+
3. Create a `.env` file in the root directory and add your Gemini API key:
|
|
108
102
|
```env
|
|
109
|
-
GEMINI_API_KEY=
|
|
103
|
+
GEMINI_API_KEY="YOUR_GEMINI_API_KEY"
|
|
110
104
|
```
|
|
111
|
-
|
|
105
|
+
4. Link the package globally to use the `readmeg` command anywhere:
|
|
112
106
|
```sh
|
|
113
|
-
npm
|
|
107
|
+
npm link
|
|
114
108
|
```
|
|
115
109
|
|
|
116
110
|
<p align="right">(<a href="#readme-top">back to top</a>)</p>
|
|
117
111
|
|
|
118
112
|
## Usage
|
|
119
113
|
|
|
120
|
-
|
|
114
|
+
Once installed globally, you can run `readmeg` in any local repository to generate its documentation.
|
|
121
115
|
|
|
122
|
-
###
|
|
116
|
+
### Run the Generator
|
|
123
117
|
|
|
124
|
-
|
|
118
|
+
Navigate to the target repository where you want to generate a README and run:
|
|
125
119
|
```sh
|
|
126
|
-
|
|
120
|
+
readmeg
|
|
127
121
|
```
|
|
128
122
|
|
|
129
|
-
|
|
123
|
+
### Command Line Options
|
|
124
|
+
|
|
125
|
+
You can also view help and available options directly from the CLI:
|
|
130
126
|
```sh
|
|
131
|
-
|
|
127
|
+
readmeg --help
|
|
132
128
|
```
|
|
133
129
|
|
|
134
|
-
###
|
|
130
|
+
### How It Works
|
|
135
131
|
|
|
136
|
-
1. **
|
|
137
|
-
2. **
|
|
138
|
-
3. **
|
|
139
|
-
4. **
|
|
140
|
-
5. **Output**: The tool will call the Gemini API and write a polished `README.md` directly to your root directory.
|
|
132
|
+
1. **Codebase Scan**: `readmeg` analyzes your directory tree and identifies key configuration files.
|
|
133
|
+
2. **Context Extraction**: It extracts metadata, dependencies, and scripts from files like `package.json` and `.env.example`.
|
|
134
|
+
3. **AI Generation**: The extracted context is sent to the Gemini API to draft a structured, professional README.md.
|
|
135
|
+
4. **Output**: The generated markdown is saved directly to your workspace as `README.md`.
|
|
141
136
|
|
|
142
137
|
<p align="right">(<a href="#readme-top">back to top</a>)</p>
|
|
143
138
|
|
|
144
139
|
## Roadmap
|
|
145
140
|
|
|
146
|
-
- [ ]
|
|
147
|
-
- [ ]
|
|
148
|
-
- [ ]
|
|
149
|
-
- [ ]
|
|
141
|
+
- [ ] Support for additional language manifests (`Cargo.toml`, `pyproject.toml`, `go.mod`)
|
|
142
|
+
- [ ] Custom template selection (e.g., Minimal, Academic, Enterprise)
|
|
143
|
+
- [ ] Interactive terminal preview before writing to disk
|
|
144
|
+
- [ ] GitHub Actions integration for automated documentation updates
|
|
150
145
|
|
|
151
|
-
See the [open issues](https://github.com/
|
|
146
|
+
See the [open issues](https://github.com/iamtyroon/readme-gen/issues) for a full list of proposed features and known issues.
|
|
152
147
|
|
|
153
148
|
<p align="right">(<a href="#readme-top">back to top</a>)</p>
|
|
154
149
|
|
|
@@ -175,15 +170,15 @@ Distributed under the MIT License. See `LICENSE` for more information.
|
|
|
175
170
|
|
|
176
171
|
## Contact
|
|
177
172
|
|
|
178
|
-
|
|
173
|
+
iamtyroon - [@notyroon](https://x.com/notyroon)
|
|
179
174
|
|
|
180
|
-
Project Link: [https://github.com/
|
|
175
|
+
Project Link: [https://github.com/iamtyroon/readme-gen](https://github.com/iamtyroon/readme-gen)
|
|
181
176
|
|
|
182
177
|
<p align="right">(<a href="#readme-top">back to top</a>)</p>
|
|
183
178
|
|
|
184
179
|
## Acknowledgments
|
|
185
180
|
|
|
186
|
-
* [Google
|
|
181
|
+
* [Google Gemini AI SDK](https://github.com/google/generative-ai-js)
|
|
187
182
|
* [Commander.js](https://github.com/tj/commander.js/)
|
|
188
183
|
* [Inquirer.js](https://github.com/SBoudrias/Inquirer.js/)
|
|
189
184
|
* [Shields.io](https://shields.io)
|
|
@@ -191,27 +186,22 @@ Project Link: [https://github.com/your_username/readmeg](https://github.com/your
|
|
|
191
186
|
|
|
192
187
|
<p align="right">(<a href="#readme-top">back to top</a>)</p>
|
|
193
188
|
|
|
194
|
-
[contributors-shield]: https://img.shields.io/github/contributors/
|
|
195
|
-
[contributors-url]: https://github.com/
|
|
196
|
-
[forks-shield]: https://img.shields.io/github/forks/
|
|
197
|
-
[forks-url]: https://github.com/
|
|
198
|
-
[stars-shield]: https://img.shields.io/github/stars/
|
|
199
|
-
[stars-url]: https://github.com/
|
|
200
|
-
[issues-shield]: https://img.shields.io/github/issues/
|
|
201
|
-
[issues-url]: https://github.com/
|
|
202
|
-
[license-shield]: https://img.shields.io/github/license/
|
|
203
|
-
[license-url]: https://github.com/
|
|
204
|
-
[
|
|
205
|
-
[
|
|
206
|
-
[product-screenshot]:
|
|
207
|
-
|
|
208
|
-
[Node.js]: https://img.shields.io/badge/Node.js-43853D?style=for-the-badge&logo=node.js&logoColor=white
|
|
189
|
+
[contributors-shield]: https://img.shields.io/github/contributors/iamtyroon/readme-gen.svg?style=for-the-badge
|
|
190
|
+
[contributors-url]: https://github.com/iamtyroon/readme-gen/graphs/contributors
|
|
191
|
+
[forks-shield]: https://img.shields.io/github/forks/iamtyroon/readme-gen.svg?style=for-the-badge
|
|
192
|
+
[forks-url]: https://github.com/iamtyroon/readme-gen/network/members
|
|
193
|
+
[stars-shield]: https://img.shields.io/github/stars/iamtyroon/readme-gen.svg?style=for-the-badge
|
|
194
|
+
[stars-url]: https://github.com/iamtyroon/readme-gen/stargazers
|
|
195
|
+
[issues-shield]: https://img.shields.io/github/issues/iamtyroon/readme-gen.svg?style=for-the-badge
|
|
196
|
+
[issues-url]: https://github.com/iamtyroon/readme-gen/issues
|
|
197
|
+
[license-shield]: https://img.shields.io/github/license/iamtyroon/readme-gen.svg?style=for-the-badge
|
|
198
|
+
[license-url]: https://github.com/iamtyroon/readme-gen/blob/main/LICENSE
|
|
199
|
+
[twitter-shield]: https://img.shields.io/badge/-Twitter-black.svg?style=for-the-badge&logo=x&colorB=555
|
|
200
|
+
[twitter-url]: https://x.com/notyroon
|
|
201
|
+
[product-screenshot]: assets/readmeg_scrnshot.png
|
|
202
|
+
[Node.js]: https://img.shields.io/badge/Node.js-6DA55F?style=for-the-badge&logo=node.js&logoColor=white
|
|
209
203
|
[Node-url]: https://nodejs.org/
|
|
210
|
-
[
|
|
211
|
-
[
|
|
212
|
-
[
|
|
213
|
-
[
|
|
214
|
-
[Commander]: https://img.shields.io/badge/Commander.js-black?style=for-the-badge&logo=commander
|
|
215
|
-
[Commander-url]: https://github.com/tj/commander.js/
|
|
216
|
-
[Inquirer]: https://img.shields.io/badge/Inquirer.js-red?style=for-the-badge&logo=npm
|
|
217
|
-
[Inquirer-url]: https://github.com/SBoudrias/Inquirer.js/
|
|
204
|
+
[Gemini.js]: https://img.shields.io/badge/Gemini%20AI-8E75C2?style=for-the-badge&logo=googlegemini&logoColor=white
|
|
205
|
+
[Gemini-url]: https://ai.google.dev/
|
|
206
|
+
[TypeScript.js]: https://img.shields.io/badge/TypeScript-3178C6?style=for-the-badge&logo=typescript&logoColor=white
|
|
207
|
+
[TypeScript-url]: https://www.typescriptlang.org/
|
package/bin/readmeg.js
CHANGED
|
@@ -11,6 +11,7 @@ import { Command } from "commander";
|
|
|
11
11
|
import inquirer from "inquirer";
|
|
12
12
|
import { GoogleGenAI } from "@google/genai";
|
|
13
13
|
import dotenv from "dotenv";
|
|
14
|
+
import { execSync } from "child_process";
|
|
14
15
|
|
|
15
16
|
// Load local .env configurations if present
|
|
16
17
|
dotenv.config();
|
|
@@ -20,10 +21,17 @@ const program = new Command();
|
|
|
20
21
|
program
|
|
21
22
|
.name("readmeg")
|
|
22
23
|
.description("Automatically generate comprehensive production-grade README.md files for your local directory repositories using Gemini.")
|
|
23
|
-
.version("1.0.
|
|
24
|
+
.version("1.0.1")
|
|
24
25
|
.option("-k, --api-key <key>", "Provide your Gemini API key manually")
|
|
25
26
|
.option("-o, --output <file>", "Define custom output file path", "README.md")
|
|
26
27
|
.option("-n, --non-interactive", "Run directly without interactive menu loop", false)
|
|
28
|
+
.option("-i, --include <files...>", "Explicitly include specific files or directories for scanning")
|
|
29
|
+
.option("-u, --github-user <username>", "Provide your GitHub username manually")
|
|
30
|
+
.option("-r, --repo-url <url>", "Provide your GitHub repository URL manually")
|
|
31
|
+
.option("-t, --twitter <url>", "Provide your Twitter Profile URL manually")
|
|
32
|
+
.option("-l, --linkedin <url>", "Provide your LinkedIn Profile URL manually")
|
|
33
|
+
.option("--logo <path>", "Path to the project logo image")
|
|
34
|
+
.option("--screenshot <path>", "Path to the project screenshot image")
|
|
27
35
|
.parse(process.argv);
|
|
28
36
|
|
|
29
37
|
const options = program.opts();
|
|
@@ -38,7 +46,6 @@ const IGNORED_DIRS = new Set([
|
|
|
38
46
|
"venv",
|
|
39
47
|
"env",
|
|
40
48
|
".aistudio",
|
|
41
|
-
"assets",
|
|
42
49
|
".next",
|
|
43
50
|
".svelte-kit",
|
|
44
51
|
".nuxt",
|
|
@@ -58,7 +65,24 @@ const MANIFEST_NAMES = new Set([
|
|
|
58
65
|
"pom.xml",
|
|
59
66
|
"build.gradle",
|
|
60
67
|
"composer.json",
|
|
61
|
-
"setup.py"
|
|
68
|
+
"setup.py",
|
|
69
|
+
"tsconfig.json",
|
|
70
|
+
"jsconfig.json",
|
|
71
|
+
"vite.config.js",
|
|
72
|
+
"vite.config.ts",
|
|
73
|
+
"webpack.config.js",
|
|
74
|
+
"next.config.js",
|
|
75
|
+
"next.config.mjs",
|
|
76
|
+
"next.config.ts",
|
|
77
|
+
"nuxt.config.js",
|
|
78
|
+
"nuxt.config.ts",
|
|
79
|
+
"svelte.config.js",
|
|
80
|
+
"tailwind.config.js",
|
|
81
|
+
"tailwind.config.ts",
|
|
82
|
+
"Makefile",
|
|
83
|
+
"CMakeLists.txt",
|
|
84
|
+
".env.example",
|
|
85
|
+
".env.sample"
|
|
62
86
|
]);
|
|
63
87
|
|
|
64
88
|
const SNIPPET_NAMES = new Set([
|
|
@@ -74,6 +98,181 @@ const SNIPPET_NAMES = new Set([
|
|
|
74
98
|
"App.js"
|
|
75
99
|
]);
|
|
76
100
|
|
|
101
|
+
const SOURCE_EXTENSIONS = new Set([
|
|
102
|
+
".js", ".jsx", ".ts", ".tsx",
|
|
103
|
+
".py",
|
|
104
|
+
".go",
|
|
105
|
+
".rs",
|
|
106
|
+
".java",
|
|
107
|
+
".cpp", ".c", ".h", ".hpp",
|
|
108
|
+
".cs",
|
|
109
|
+
".rb",
|
|
110
|
+
".php",
|
|
111
|
+
".sh",
|
|
112
|
+
".swift",
|
|
113
|
+
".kt"
|
|
114
|
+
]);
|
|
115
|
+
|
|
116
|
+
function isTestOrMockFile(relPath) {
|
|
117
|
+
const lowerPath = relPath.toLowerCase().replace(/\\/g, "/");
|
|
118
|
+
return (
|
|
119
|
+
lowerPath.includes("/test/") ||
|
|
120
|
+
lowerPath.includes("/tests/") ||
|
|
121
|
+
lowerPath.includes("/spec/") ||
|
|
122
|
+
lowerPath.includes("/specs/") ||
|
|
123
|
+
lowerPath.includes("/mock/") ||
|
|
124
|
+
lowerPath.includes("/mocks/") ||
|
|
125
|
+
lowerPath.includes("/__tests__/") ||
|
|
126
|
+
lowerPath.includes("/__mocks__/") ||
|
|
127
|
+
lowerPath.includes("test.") ||
|
|
128
|
+
lowerPath.includes("spec.") ||
|
|
129
|
+
lowerPath.endsWith(".test") ||
|
|
130
|
+
lowerPath.endsWith(".spec")
|
|
131
|
+
);
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
function getAutoSelectedSnippets(snippets) {
|
|
135
|
+
const entryKeywords = ["index", "main", "app", "server", "cli", "run", "start", "config"];
|
|
136
|
+
const scored = snippets.map(s => {
|
|
137
|
+
let score = 0;
|
|
138
|
+
const nameLower = s.name.toLowerCase();
|
|
139
|
+
|
|
140
|
+
if (SNIPPET_NAMES.has(s.name)) {
|
|
141
|
+
score += 100;
|
|
142
|
+
}
|
|
143
|
+
if (!s.relativePath.includes("/") && !s.relativePath.includes("\\")) {
|
|
144
|
+
score += 50; // Root level
|
|
145
|
+
}
|
|
146
|
+
if (entryKeywords.some(kw => nameLower.includes(kw))) {
|
|
147
|
+
score += 30;
|
|
148
|
+
}
|
|
149
|
+
// Files in src/, lib/, bin/, etc. (closer to root is better)
|
|
150
|
+
const depth = s.relativePath.split(/[/\\]/).length - 1;
|
|
151
|
+
score -= depth * 5; // Deeper files have lower priority
|
|
152
|
+
|
|
153
|
+
return { snippet: s, score };
|
|
154
|
+
});
|
|
155
|
+
|
|
156
|
+
// Sort in descending order of score
|
|
157
|
+
scored.sort((a, b) => b.score - a.score);
|
|
158
|
+
|
|
159
|
+
// Select top 20 snippets
|
|
160
|
+
const maxAutoSelect = 20;
|
|
161
|
+
return scored.slice(0, maxAutoSelect).map(item => item.snippet.relativePath);
|
|
162
|
+
}
|
|
163
|
+
|
|
164
|
+
function getExplicitIncludes(includeOption) {
|
|
165
|
+
if (!includeOption) return [];
|
|
166
|
+
const rawArray = Array.isArray(includeOption) ? includeOption : [includeOption];
|
|
167
|
+
const result = [];
|
|
168
|
+
for (const item of rawArray) {
|
|
169
|
+
if (typeof item === 'string') {
|
|
170
|
+
const splitItems = item.split(',').map(s => s.trim()).filter(Boolean);
|
|
171
|
+
result.push(...splitItems);
|
|
172
|
+
}
|
|
173
|
+
}
|
|
174
|
+
return result;
|
|
175
|
+
}
|
|
176
|
+
|
|
177
|
+
function processExplicitFile(filePath, rootPath, result) {
|
|
178
|
+
try {
|
|
179
|
+
const absolutePath = path.resolve(rootPath, filePath);
|
|
180
|
+
if (!fs.existsSync(absolutePath)) {
|
|
181
|
+
console.warn(`\x1b[33m[Warning] Explicitly included file not found: ${filePath}\x1b[0m`);
|
|
182
|
+
return;
|
|
183
|
+
}
|
|
184
|
+
const stats = fs.statSync(absolutePath);
|
|
185
|
+
if (!stats.isFile()) return;
|
|
186
|
+
|
|
187
|
+
const relativePath = path.relative(rootPath, absolutePath);
|
|
188
|
+
const item = path.basename(absolutePath);
|
|
189
|
+
const ext = path.extname(item).toLowerCase();
|
|
190
|
+
const content = fs.readFileSync(absolutePath, "utf-8");
|
|
191
|
+
|
|
192
|
+
// Add to language list
|
|
193
|
+
if (ext === ".ts" || ext === ".tsx") result.languages.add("TypeScript");
|
|
194
|
+
else if (ext === ".js" || ext === ".jsx") result.languages.add("JavaScript");
|
|
195
|
+
else if (ext === ".py") result.languages.add("Python");
|
|
196
|
+
else if (ext === ".go") result.languages.add("Go");
|
|
197
|
+
else if (ext === ".rs") result.languages.add("Rust");
|
|
198
|
+
else if (ext === ".java") result.languages.add("Java");
|
|
199
|
+
else if (ext === ".cpp" || ext === ".c" || ext === ".h" || ext === ".hpp") result.languages.add("C/C++");
|
|
200
|
+
else if (ext === ".cs") result.languages.add("C#");
|
|
201
|
+
else if (ext === ".rb") result.languages.add("Ruby");
|
|
202
|
+
else if (ext === ".php") result.languages.add("PHP");
|
|
203
|
+
else if (ext === ".swift") result.languages.add("Swift");
|
|
204
|
+
else if (ext === ".kt" || ext === ".kts") result.languages.add("Kotlin");
|
|
205
|
+
else if (ext === ".sh") result.languages.add("Shell");
|
|
206
|
+
|
|
207
|
+
if (MANIFEST_NAMES.has(item)) {
|
|
208
|
+
if (!result.manifests.some(m => m.relativePath === relativePath)) {
|
|
209
|
+
result.manifests.push({ relativePath, name: item, content: content.substring(0, 10000) });
|
|
210
|
+
}
|
|
211
|
+
} else if (ext === ".md") {
|
|
212
|
+
if (!result.markdowns.some(m => m.relativePath === relativePath)) {
|
|
213
|
+
result.markdowns.push({ relativePath, name: item, content });
|
|
214
|
+
}
|
|
215
|
+
} else {
|
|
216
|
+
if (!result.snippets.some(s => s.relativePath === relativePath)) {
|
|
217
|
+
result.snippets.push({ relativePath, name: item, content });
|
|
218
|
+
}
|
|
219
|
+
}
|
|
220
|
+
} catch (err) {
|
|
221
|
+
console.error(`\x1b[31m[Error] Failed to process explicit file '${filePath}': ${err.message}\x1b[0m`);
|
|
222
|
+
}
|
|
223
|
+
}
|
|
224
|
+
|
|
225
|
+
function processExplicitPath(inputPath, rootPath, result) {
|
|
226
|
+
try {
|
|
227
|
+
const absolutePath = path.resolve(rootPath, inputPath);
|
|
228
|
+
if (!fs.existsSync(absolutePath)) {
|
|
229
|
+
console.warn(`\x1b[33m[Warning] Explicit path not found: ${inputPath}\x1b[0m`);
|
|
230
|
+
return;
|
|
231
|
+
}
|
|
232
|
+
const stats = fs.statSync(absolutePath);
|
|
233
|
+
if (stats.isDirectory()) {
|
|
234
|
+
const relPath = path.relative(rootPath, absolutePath);
|
|
235
|
+
scanDirectoryFiles(absolutePath, relPath, result);
|
|
236
|
+
} else if (stats.isFile()) {
|
|
237
|
+
processExplicitFile(absolutePath, rootPath, result);
|
|
238
|
+
}
|
|
239
|
+
} catch (err) {
|
|
240
|
+
console.error(`\x1b[31m[Error] Failed to process explicit path '${inputPath}': ${err.message}\x1b[0m`);
|
|
241
|
+
}
|
|
242
|
+
}
|
|
243
|
+
function getGitRemoteInfo() {
|
|
244
|
+
try {
|
|
245
|
+
const remoteUrl = execSync("git remote get-url origin", { encoding: "utf8", stdio: ["ignore", "pipe", "ignore"] }).trim();
|
|
246
|
+
if (remoteUrl) {
|
|
247
|
+
let repoUrl = "";
|
|
248
|
+
let username = "";
|
|
249
|
+
let repoName = "";
|
|
250
|
+
|
|
251
|
+
if (remoteUrl.startsWith("git@")) {
|
|
252
|
+
const match = remoteUrl.match(/git@[^:]+:([^/]+)\/(.+?)(?:\.git)?$/);
|
|
253
|
+
if (match) {
|
|
254
|
+
username = match[1];
|
|
255
|
+
repoName = match[2];
|
|
256
|
+
repoUrl = `https://github.com/${username}/${repoName}`;
|
|
257
|
+
}
|
|
258
|
+
} else if (remoteUrl.startsWith("http")) {
|
|
259
|
+
const urlObj = new URL(remoteUrl);
|
|
260
|
+
const pathParts = urlObj.pathname.split("/").filter(Boolean);
|
|
261
|
+
if (pathParts.length >= 2) {
|
|
262
|
+
username = pathParts[0];
|
|
263
|
+
repoName = pathParts[1].replace(/\.git$/, "");
|
|
264
|
+
repoUrl = `https://${urlObj.host}/${username}/${repoName}`;
|
|
265
|
+
}
|
|
266
|
+
}
|
|
267
|
+
return { repoUrl, username, repoName };
|
|
268
|
+
}
|
|
269
|
+
} catch (_) {}
|
|
270
|
+
return null;
|
|
271
|
+
}
|
|
272
|
+
|
|
273
|
+
|
|
274
|
+
|
|
275
|
+
|
|
77
276
|
// 1. Traverse directory to form visual path layout
|
|
78
277
|
function buildTreeString(currentPath, prefix = "") {
|
|
79
278
|
try {
|
|
@@ -126,7 +325,7 @@ function scanDirectoryFiles(currentPath, relativePath, result) {
|
|
|
126
325
|
try {
|
|
127
326
|
const items = fs.readdirSync(currentPath);
|
|
128
327
|
for (const item of items) {
|
|
129
|
-
if (IGNORED_DIRS.has(item) || item.startsWith(".")) continue;
|
|
328
|
+
if (IGNORED_DIRS.has(item) || (item.startsWith(".") && item !== ".env.example" && item !== ".env.sample")) continue;
|
|
130
329
|
|
|
131
330
|
const itemPath = path.join(currentPath, item);
|
|
132
331
|
const itemRelPath = relativePath ? path.join(relativePath, item) : item;
|
|
@@ -142,11 +341,33 @@ function scanDirectoryFiles(currentPath, relativePath, result) {
|
|
|
142
341
|
else if (ext === ".py") result.languages.add("Python");
|
|
143
342
|
else if (ext === ".go") result.languages.add("Go");
|
|
144
343
|
else if (ext === ".rs") result.languages.add("Rust");
|
|
344
|
+
else if (ext === ".java") result.languages.add("Java");
|
|
345
|
+
else if (ext === ".cpp" || ext === ".c" || ext === ".h" || ext === ".hpp") result.languages.add("C/C++");
|
|
346
|
+
else if (ext === ".cs") result.languages.add("C#");
|
|
347
|
+
else if (ext === ".rb") result.languages.add("Ruby");
|
|
348
|
+
else if (ext === ".php") result.languages.add("PHP");
|
|
349
|
+
else if (ext === ".swift") result.languages.add("Swift");
|
|
350
|
+
else if (ext === ".kt" || ext === ".kts") result.languages.add("Kotlin");
|
|
351
|
+
else if (ext === ".sh") result.languages.add("Shell");
|
|
352
|
+
|
|
353
|
+
const isImage = [".png", ".jpg", ".jpeg", ".svg", ".gif"].includes(ext);
|
|
354
|
+
if (isImage) {
|
|
355
|
+
const lowerName = item.toLowerCase();
|
|
356
|
+
if (lowerName.includes("logo")) {
|
|
357
|
+
result.detectedLogos.push(itemRelPath);
|
|
358
|
+
}
|
|
359
|
+
if (lowerName.includes("screenshot") || lowerName.includes("scrnshot") || lowerName.includes("preview")) {
|
|
360
|
+
result.detectedScreenshots.push(itemRelPath);
|
|
361
|
+
}
|
|
362
|
+
}
|
|
145
363
|
|
|
146
|
-
|
|
364
|
+
const isManifest = MANIFEST_NAMES.has(item);
|
|
365
|
+
const isSnippetCandidate = (SNIPPET_NAMES.has(item) || SOURCE_EXTENSIONS.has(ext)) && !isTestOrMockFile(itemRelPath);
|
|
366
|
+
|
|
367
|
+
if (isManifest) {
|
|
147
368
|
const content = fs.readFileSync(itemPath, "utf-8").substring(0, 10000);
|
|
148
369
|
result.manifests.push({ relativePath: itemRelPath, name: item, content });
|
|
149
|
-
} else if (
|
|
370
|
+
} else if (isSnippetCandidate && stats.size < 40000) {
|
|
150
371
|
const content = fs.readFileSync(itemPath, "utf-8");
|
|
151
372
|
result.snippets.push({ relativePath: itemRelPath, name: item, content });
|
|
152
373
|
} else if (ext === ".md" && item.toLowerCase() !== "readme.md" && stats.size < 15000) {
|
|
@@ -169,7 +390,7 @@ const showBanner = () => {
|
|
|
169
390
|
| | \\ \\ __/ (_| | (_| | | | | \\___|\\__, |
|
|
170
391
|
|_| \\_\\___|\\__,_|\\__,_|_| |_| |___/
|
|
171
392
|
\x1b[0m`);
|
|
172
|
-
console.log(` \x1b[45m\x1b[37m ReadMeg — Interactive CLI v1.0.
|
|
393
|
+
console.log(` \x1b[45m\x1b[37m ReadMeg — Interactive CLI v1.0.1 \x1b[0m Powered by Google Gemini AI\n`);
|
|
173
394
|
};
|
|
174
395
|
|
|
175
396
|
async function main() {
|
|
@@ -210,11 +431,20 @@ async function main() {
|
|
|
210
431
|
manifests: [],
|
|
211
432
|
snippets: [],
|
|
212
433
|
markdowns: [],
|
|
213
|
-
languages: new Set()
|
|
434
|
+
languages: new Set(),
|
|
435
|
+
detectedLogos: [],
|
|
436
|
+
detectedScreenshots: []
|
|
214
437
|
};
|
|
215
438
|
|
|
216
439
|
scanDirectoryFiles(activeCwd, "", scanResult);
|
|
217
440
|
|
|
441
|
+
if (options.include) {
|
|
442
|
+
const includes = getExplicitIncludes(options.include);
|
|
443
|
+
for (const inc of includes) {
|
|
444
|
+
processExplicitPath(inc, activeCwd, scanResult);
|
|
445
|
+
}
|
|
446
|
+
}
|
|
447
|
+
|
|
218
448
|
let projectType = "Unknown Project Core";
|
|
219
449
|
if (scanResult.manifests.some(m => m.name === "package.json")) projectType = "Node.js (JavaScript/TypeScript)";
|
|
220
450
|
else if (scanResult.manifests.some(m => m.name === "requirements.txt" || m.name === "pyproject.toml")) projectType = "Python";
|
|
@@ -224,10 +454,38 @@ async function main() {
|
|
|
224
454
|
console.log(`\x1b[32mSuccessfully mapped codebase structure as:\x1b[0m ${projectType}`);
|
|
225
455
|
console.log(`Tree Depth loaded. Captured manifests: ${scanResult.manifests.map(m => m.name).join(", ") || "None"}\n`);
|
|
226
456
|
|
|
227
|
-
// CLI Selection Loop states
|
|
228
457
|
let selectedManifestPaths = scanResult.manifests.map(m => m.relativePath);
|
|
229
|
-
let selectedSnippetPaths = scanResult.snippets
|
|
458
|
+
let selectedSnippetPaths = getAutoSelectedSnippets(scanResult.snippets);
|
|
230
459
|
let selectedMarkdownPaths = scanResult.markdowns.map(md => md.relativePath);
|
|
460
|
+
|
|
461
|
+
if (options.include) {
|
|
462
|
+
const includes = getExplicitIncludes(options.include);
|
|
463
|
+
for (const inc of includes) {
|
|
464
|
+
try {
|
|
465
|
+
const absPath = path.resolve(activeCwd, inc);
|
|
466
|
+
if (fs.existsSync(absPath) && fs.statSync(absPath).isFile()) {
|
|
467
|
+
const relPath = path.relative(activeCwd, absPath);
|
|
468
|
+
if (scanResult.snippets.some(s => s.relativePath === relPath) && !selectedSnippetPaths.includes(relPath)) {
|
|
469
|
+
selectedSnippetPaths.push(relPath);
|
|
470
|
+
}
|
|
471
|
+
if (scanResult.manifests.some(m => m.relativePath === relPath) && !selectedManifestPaths.includes(relPath)) {
|
|
472
|
+
selectedManifestPaths.push(relPath);
|
|
473
|
+
}
|
|
474
|
+
if (scanResult.markdowns.some(md => md.relativePath === relPath) && !selectedMarkdownPaths.includes(relPath)) {
|
|
475
|
+
selectedMarkdownPaths.push(relPath);
|
|
476
|
+
}
|
|
477
|
+
}
|
|
478
|
+
} catch (_) {}
|
|
479
|
+
}
|
|
480
|
+
}
|
|
481
|
+
const gitInfo = getGitRemoteInfo() || {};
|
|
482
|
+
let githubUsername = options.githubUser || gitInfo.username || "";
|
|
483
|
+
let githubRepoUrl = options.repoUrl || gitInfo.repoUrl || "";
|
|
484
|
+
let twitterUrl = options.twitter || "";
|
|
485
|
+
let linkedinUrl = options.linkedin || "";
|
|
486
|
+
let logoPath = options.logo || (scanResult.detectedLogos[0] || "");
|
|
487
|
+
let screenshotPath = options.screenshot || (scanResult.detectedScreenshots[0] || "");
|
|
488
|
+
|
|
231
489
|
let addGuidelines = [];
|
|
232
490
|
let customInstructions = "";
|
|
233
491
|
let chosenModel = "gemini-3.5-flash";
|
|
@@ -237,9 +495,10 @@ async function main() {
|
|
|
237
495
|
while (true) {
|
|
238
496
|
const listChoices = [
|
|
239
497
|
{ name: "View scanned File Tree diagram", value: "view_tree" },
|
|
498
|
+
{ name: "Configure project metadata (GitHub, Twitter, LinkedIn)", value: "project_metadata" },
|
|
240
499
|
{ name: "Select configuration manifest files to analyze", value: "select_manifests" },
|
|
241
500
|
{ name: "Select code snippet guides to inspect", value: "select_snippets" },
|
|
242
|
-
{ name: "Select
|
|
501
|
+
{ name: "Select files to inspect", value: "select_files" },
|
|
243
502
|
{ name: "Select supplementary layout headings", value: "select_headings" },
|
|
244
503
|
{ name: "Enter custom text highlight instructions", value: "custom_notes" },
|
|
245
504
|
{ name: "GENERATE README DOCUMENT RIGHT NOW", value: "trigger_gen" },
|
|
@@ -270,6 +529,56 @@ async function main() {
|
|
|
270
529
|
continue;
|
|
271
530
|
}
|
|
272
531
|
|
|
532
|
+
if (activeChoice === "project_metadata") {
|
|
533
|
+
const metadataAnswers = await inquirer.prompt([
|
|
534
|
+
{
|
|
535
|
+
type: "input",
|
|
536
|
+
name: "username",
|
|
537
|
+
message: "Enter GitHub Username / Organisation:",
|
|
538
|
+
default: githubUsername
|
|
539
|
+
},
|
|
540
|
+
{
|
|
541
|
+
type: "input",
|
|
542
|
+
name: "repoUrl",
|
|
543
|
+
message: "Enter GitHub Repository URL:",
|
|
544
|
+
default: githubRepoUrl
|
|
545
|
+
},
|
|
546
|
+
{
|
|
547
|
+
type: "input",
|
|
548
|
+
name: "twitter",
|
|
549
|
+
message: "Enter Twitter Profile URL (optional):",
|
|
550
|
+
default: twitterUrl
|
|
551
|
+
},
|
|
552
|
+
{
|
|
553
|
+
type: "input",
|
|
554
|
+
name: "linkedin",
|
|
555
|
+
message: "Enter LinkedIn Profile URL (optional):",
|
|
556
|
+
default: linkedinUrl
|
|
557
|
+
},
|
|
558
|
+
{
|
|
559
|
+
type: "input",
|
|
560
|
+
name: "logo",
|
|
561
|
+
message: "Enter Project Logo Path (optional):",
|
|
562
|
+
default: logoPath
|
|
563
|
+
},
|
|
564
|
+
{
|
|
565
|
+
type: "input",
|
|
566
|
+
name: "screenshot",
|
|
567
|
+
message: "Enter Project Screenshot Path (optional):",
|
|
568
|
+
default: screenshotPath
|
|
569
|
+
}
|
|
570
|
+
]);
|
|
571
|
+
|
|
572
|
+
githubUsername = metadataAnswers.username.trim();
|
|
573
|
+
githubRepoUrl = metadataAnswers.repoUrl.trim();
|
|
574
|
+
twitterUrl = metadataAnswers.twitter.trim();
|
|
575
|
+
linkedinUrl = metadataAnswers.linkedin.trim();
|
|
576
|
+
logoPath = metadataAnswers.logo.trim();
|
|
577
|
+
screenshotPath = metadataAnswers.screenshot.trim();
|
|
578
|
+
console.log("\x1b[32m✔ Project metadata updated.\x1b[0m\n");
|
|
579
|
+
continue;
|
|
580
|
+
}
|
|
581
|
+
|
|
273
582
|
if (activeChoice === "select_manifests") {
|
|
274
583
|
if (scanResult.manifests.length === 0) {
|
|
275
584
|
console.log("\x1b[33mWarning: No manifest files (package.json, requirements.txt, setup.py, Cargo.toml etc) found to check.\x1b[0m\n");
|
|
@@ -308,22 +617,83 @@ async function main() {
|
|
|
308
617
|
continue;
|
|
309
618
|
}
|
|
310
619
|
|
|
311
|
-
if (activeChoice === "
|
|
312
|
-
|
|
313
|
-
|
|
314
|
-
|
|
315
|
-
|
|
316
|
-
|
|
620
|
+
if (activeChoice === "select_files") {
|
|
621
|
+
const subChoices = [
|
|
622
|
+
{ name: "Check / Select files to inspect", value: "select" },
|
|
623
|
+
{ name: "Add custom file or directory path...", value: "add" },
|
|
624
|
+
{ name: "Go back to Main Menu", value: "back" }
|
|
625
|
+
];
|
|
626
|
+
const subMenu = await inquirer.prompt([
|
|
317
627
|
{
|
|
318
|
-
type: "
|
|
319
|
-
name: "
|
|
320
|
-
message: "
|
|
321
|
-
choices:
|
|
628
|
+
type: "select",
|
|
629
|
+
name: "action",
|
|
630
|
+
message: "Select Files to Inspect — Choose a management action:",
|
|
631
|
+
choices: subChoices,
|
|
322
632
|
loop: false
|
|
323
633
|
}
|
|
324
634
|
]);
|
|
325
|
-
|
|
326
|
-
|
|
635
|
+
|
|
636
|
+
if (subMenu.action === "back") {
|
|
637
|
+
continue;
|
|
638
|
+
}
|
|
639
|
+
|
|
640
|
+
if (subMenu.action === "add") {
|
|
641
|
+
const addAsk = await inquirer.prompt([
|
|
642
|
+
{
|
|
643
|
+
type: "input",
|
|
644
|
+
name: "path",
|
|
645
|
+
message: "Enter the custom file or directory path to scan and inspect:"
|
|
646
|
+
}
|
|
647
|
+
]);
|
|
648
|
+
const customPath = addAsk.path.trim();
|
|
649
|
+
if (customPath) {
|
|
650
|
+
processExplicitPath(customPath, activeCwd, scanResult);
|
|
651
|
+
try {
|
|
652
|
+
const absPath = path.resolve(activeCwd, customPath);
|
|
653
|
+
if (fs.existsSync(absPath)) {
|
|
654
|
+
if (fs.statSync(absPath).isFile()) {
|
|
655
|
+
const relPath = path.relative(activeCwd, absPath);
|
|
656
|
+
const ext = path.extname(relPath).toLowerCase();
|
|
657
|
+
if (MANIFEST_NAMES.has(path.basename(relPath))) {
|
|
658
|
+
if (!selectedManifestPaths.includes(relPath)) selectedManifestPaths.push(relPath);
|
|
659
|
+
} else if (ext === ".md") {
|
|
660
|
+
if (!selectedMarkdownPaths.includes(relPath)) selectedMarkdownPaths.push(relPath);
|
|
661
|
+
} else {
|
|
662
|
+
if (!selectedSnippetPaths.includes(relPath)) selectedSnippetPaths.push(relPath);
|
|
663
|
+
}
|
|
664
|
+
}
|
|
665
|
+
console.log(`\x1b[32m✔ Successfully scanned and added path: ${customPath}\x1b[0m\n`);
|
|
666
|
+
}
|
|
667
|
+
} catch (err) {
|
|
668
|
+
console.error(`\x1b[31m[Error] Failed to resolve path: ${err.message}\x1b[0m\n`);
|
|
669
|
+
}
|
|
670
|
+
}
|
|
671
|
+
}
|
|
672
|
+
|
|
673
|
+
if (subMenu.action === "select") {
|
|
674
|
+
const choices = scanResult.markdowns.map(md => ({
|
|
675
|
+
name: md.relativePath,
|
|
676
|
+
value: md.relativePath,
|
|
677
|
+
checked: selectedMarkdownPaths.includes(md.relativePath)
|
|
678
|
+
}));
|
|
679
|
+
|
|
680
|
+
if (choices.length === 0) {
|
|
681
|
+
console.log("\x1b[33mNo reference files currently loaded. Choose 'Add custom file or directory path...' to add one.\x1b[0m\n");
|
|
682
|
+
continue;
|
|
683
|
+
}
|
|
684
|
+
|
|
685
|
+
const selectAsk = await inquirer.prompt([
|
|
686
|
+
{
|
|
687
|
+
type: "checkbox",
|
|
688
|
+
name: "paths",
|
|
689
|
+
message: "Check files to inject as key architectural sources:",
|
|
690
|
+
choices,
|
|
691
|
+
loop: false
|
|
692
|
+
}
|
|
693
|
+
]);
|
|
694
|
+
selectedMarkdownPaths = selectAsk.paths;
|
|
695
|
+
console.log(`\x1b[32m✔ Reference files updated. Total: ${selectedMarkdownPaths.length}\x1b[0m\n`);
|
|
696
|
+
}
|
|
327
697
|
continue;
|
|
328
698
|
}
|
|
329
699
|
|
|
@@ -402,12 +772,12 @@ The document MUST strictly follow this exact outline and formatting sequence:
|
|
|
402
772
|
|
|
403
773
|
1. Top Anchors & Shields:
|
|
404
774
|
- Add a top anchor: \`<a name="readme-top"></a>\` at the absolute beginning of the file.
|
|
405
|
-
- Centered shields/badges using raw Markdown with stylish links: Contributors, Forks, Stargazers, Issues, License, LinkedIn. Specify reference URL identifiers at the absolute bottom of the file (e.g. \`[contributors-shield]: ...\`).
|
|
775
|
+
- Centered shields/badges using raw Markdown with stylish links: Contributors, Forks, Stargazers, Issues, License, and (if provided) LinkedIn and Twitter. Specify reference URL identifiers at the absolute bottom of the file (e.g. \`[contributors-shield]: ...\`).
|
|
406
776
|
- Put badges cleanly on top.
|
|
407
777
|
|
|
408
778
|
2. Project Logo & Headers (Centered):
|
|
409
779
|
- Leave space for logo using \`<br />\` and then a centered div block containing:
|
|
410
|
-
- An image tag targeting a
|
|
780
|
+
- An image tag targeting the repository logo with dimensions 80x80. If a Logo Path is provided under Project Metadata, use it as the image src (e.g., \`assets/logo.png\`). If not provided, search the provided project structure (e.g., assets/, images/, or root) for any logo image file (like \`logo.png\`, \`logo.svg\`, \`logo.jpg\`, \`assets/logo.png\`). If found, use that file path as the src. If no logo file exists in the repository, use the unsplash placeholder URL: "https://images.unsplash.com/photo-1618401471353-b98aedd07871?q=80&w=120&auto=format&fit=crop".
|
|
411
781
|
- A centered \`<h3 align="center">Project Title</h3>\`
|
|
412
782
|
- A short, descriptive project subtitle.
|
|
413
783
|
- Links to Explore the Docs, View Demo, Report Bug, Request Feature centering on appropriate URLs.
|
|
@@ -417,7 +787,7 @@ The document MUST strictly follow this exact outline and formatting sequence:
|
|
|
417
787
|
|
|
418
788
|
4. About the Project:
|
|
419
789
|
- Place a section title \`## About The Project\` with a paragraph detailing what issues the library solves and why.
|
|
420
|
-
- Provide a
|
|
790
|
+
- Provide a screenshot element (\`[![Product Name Screen Shot][product-screenshot]](https://example.com)\`). If a Screenshot Path is provided under Project Metadata, ensure the \`[product-screenshot]\` reference at the bottom resolves to that file path (e.g., \`assets/screenshot.png\`). If not provided, search the project structure for any screenshot image file (like \`screenshot.png\`, \`screenshot.jpg\`, \`assets/screenshot.png\`). If found, ensure the \`[product-screenshot]\` reference at the bottom resolves to that file path (e.g., \`assets/screenshot.png\`). If no screenshot file is found, resolve the reference to the unsplash placeholder: "https://images.unsplash.com/photo-1618401471353-b98aedd07871?q=80&w=600&auto=format&fit=crop".
|
|
421
791
|
- Add a \`### Built With\` subsection with raw shields list linking to core framework technologies identified in the manifests or languages (e.g. Next.js, React, Python, Go, Rust, etc., with shields like \`* [![React][React.js]][React-url]\`).
|
|
422
792
|
- Add \`<p align="right">(<a href="#readme-top">back to top</a>)</p>\` at the end of this and ALL subsequent major sections.
|
|
423
793
|
|
|
@@ -449,6 +819,22 @@ The document MUST strictly follow this exact outline and formatting sequence:
|
|
|
449
819
|
|
|
450
820
|
${addGuidelines.length > 0 ? `Include these extra specific layout headings inside the layout: ${addGuidelines.map(sec => `- ${sec}`).join("\n")}` : ""}
|
|
451
821
|
|
|
822
|
+
Project Metadata for customization (use these when constructing URLs, shields, badges, and contact references):
|
|
823
|
+
- GitHub Username: ${githubUsername || "Not provided"}
|
|
824
|
+
- GitHub Repository URL: ${githubRepoUrl || "Not provided"}
|
|
825
|
+
- Twitter URL: ${twitterUrl || "Not provided"}
|
|
826
|
+
- LinkedIn URL: ${linkedinUrl || "Not provided"}
|
|
827
|
+
- Logo Path: ${logoPath || "Not provided"}
|
|
828
|
+
- Screenshot Path: ${screenshotPath || "Not provided"}
|
|
829
|
+
|
|
830
|
+
Instructions for metadata integration:
|
|
831
|
+
1. Construct GitHub shields (Contributors, Forks, Stars, Issues, License) and their URL references using the provided GitHub Username and Repository name (extracted from the Repo URL).
|
|
832
|
+
2. If a LinkedIn URL is provided, include a LinkedIn shield at the top (using badge image URL e.g. "https://img.shields.io/badge/-LinkedIn-black.svg?style=for-the-badge&logo=linkedin&colorB=555") and define its bottom link reference pointing to the LinkedIn URL. If not, omit the LinkedIn shield.
|
|
833
|
+
3. If a Twitter URL is provided, include a Twitter/X shield at the top (using badge image URL e.g. "https://img.shields.io/badge/-Twitter-black.svg?style=for-the-badge&logo=x&colorB=555" or "https://img.shields.io/badge/-Twitter-black.svg?style=for-the-badge&logo=twitter&colorB=555") and define its bottom link reference pointing to the Twitter URL. If not, omit the Twitter shield.
|
|
834
|
+
4. Under the Contact section, list the user's contact information using the provided Twitter and LinkedIn links.
|
|
835
|
+
5. If a Logo Path is provided, use it exactly as the src of the project logo image.
|
|
836
|
+
6. If a Screenshot Path is provided, ensure the bottom reference \`[product-screenshot]\` resolves to it.
|
|
837
|
+
|
|
452
838
|
Do not write markdown block fences inside the generated readme itself unless wrapping code examples. Do not add any conversational meta-talk. Output the raw Best-README-Template markdown directly.`;
|
|
453
839
|
|
|
454
840
|
const prompt = `Generate a README.md file based on the following codebase analysis:
|
|
@@ -462,6 +848,7 @@ ${snippetsBlock}
|
|
|
462
848
|
|
|
463
849
|
${markdownsBlock}
|
|
464
850
|
|
|
851
|
+
${githubUsername || githubRepoUrl || twitterUrl || linkedinUrl || logoPath || screenshotPath ? `Project Metadata:\n- GitHub Username: ${githubUsername || "N/A"}\n- GitHub Repository: ${githubRepoUrl || "N/A"}\n- Twitter: ${twitterUrl || "N/A"}\n- LinkedIn: ${linkedinUrl || "N/A"}\n- Logo Path: ${logoPath || "N/A"}\n- Screenshot Path: ${screenshotPath || "N/A"}\n` : ""}
|
|
465
852
|
${customInstructions ? `Custom User directives:\n${customInstructions}\n` : ""}
|
|
466
853
|
|
|
467
854
|
Generate the complete, polished Markdown output directly without any extra conversation.`;
|
|
@@ -597,6 +984,12 @@ async function runDirectScanAndGen() {
|
|
|
597
984
|
console.log("Running zero-config non-interactive codebase scans...");
|
|
598
985
|
const activeCwd = process.cwd();
|
|
599
986
|
|
|
987
|
+
const gitInfo = getGitRemoteInfo() || {};
|
|
988
|
+
const githubUsername = options.githubUser || gitInfo.username || "";
|
|
989
|
+
const githubRepoUrl = options.repoUrl || gitInfo.repoUrl || "";
|
|
990
|
+
const twitterUrl = options.twitter || "";
|
|
991
|
+
const linkedinUrl = options.linkedin || "";
|
|
992
|
+
|
|
600
993
|
let treeBody = "";
|
|
601
994
|
try {
|
|
602
995
|
treeBody = buildTreeString(activeCwd);
|
|
@@ -608,16 +1001,28 @@ async function runDirectScanAndGen() {
|
|
|
608
1001
|
const scanResult = {
|
|
609
1002
|
manifests: [],
|
|
610
1003
|
snippets: [],
|
|
611
|
-
|
|
1004
|
+
markdowns: [],
|
|
1005
|
+
languages: new Set(),
|
|
1006
|
+
detectedLogos: [],
|
|
1007
|
+
detectedScreenshots: []
|
|
612
1008
|
};
|
|
613
1009
|
|
|
614
1010
|
try {
|
|
615
1011
|
scanDirectoryFiles(activeCwd, "", scanResult);
|
|
1012
|
+
if (options.include) {
|
|
1013
|
+
const includes = getExplicitIncludes(options.include);
|
|
1014
|
+
for (const inc of includes) {
|
|
1015
|
+
processExplicitPath(inc, activeCwd, scanResult);
|
|
1016
|
+
}
|
|
1017
|
+
}
|
|
616
1018
|
} catch (err) {
|
|
617
1019
|
console.error(`\x1b[31mERROR: Detailed file scanning failed: ${err.message}\x1b[0m`);
|
|
618
1020
|
process.exit(1);
|
|
619
1021
|
}
|
|
620
1022
|
|
|
1023
|
+
const logoPath = options.logo || (scanResult.detectedLogos[0] || "");
|
|
1024
|
+
const screenshotPath = options.screenshot || (scanResult.detectedScreenshots[0] || "");
|
|
1025
|
+
|
|
621
1026
|
const apiKey = options.apiKey || process.env.GEMINI_API_KEY;
|
|
622
1027
|
if (!apiKey || !apiKey.trim()) {
|
|
623
1028
|
console.error("\x1b[31mERROR: Gemini API key is missing. Run with --api-key=KEY or set the GEMINI_API_KEY environment variable.\x1b[0m");
|
|
@@ -634,25 +1039,132 @@ async function runDirectScanAndGen() {
|
|
|
634
1039
|
}
|
|
635
1040
|
});
|
|
636
1041
|
|
|
1042
|
+
const baseSelectedSnippets = getAutoSelectedSnippets(scanResult.snippets);
|
|
1043
|
+
const selectedSnippetPaths = baseSelectedSnippets.slice(0, 15);
|
|
1044
|
+
|
|
1045
|
+
if (options.include) {
|
|
1046
|
+
const includes = getExplicitIncludes(options.include);
|
|
1047
|
+
for (const inc of includes) {
|
|
1048
|
+
try {
|
|
1049
|
+
const absPath = path.resolve(activeCwd, inc);
|
|
1050
|
+
if (fs.existsSync(absPath) && fs.statSync(absPath).isFile()) {
|
|
1051
|
+
const relPath = path.relative(activeCwd, absPath);
|
|
1052
|
+
if (scanResult.snippets.some(s => s.relativePath === relPath) && !selectedSnippetPaths.includes(relPath)) {
|
|
1053
|
+
selectedSnippetPaths.push(relPath);
|
|
1054
|
+
}
|
|
1055
|
+
}
|
|
1056
|
+
} catch (_) {}
|
|
1057
|
+
}
|
|
1058
|
+
}
|
|
1059
|
+
|
|
1060
|
+
const filteredSnippets = scanResult.snippets.filter(s => selectedSnippetPaths.includes(s.relativePath));
|
|
1061
|
+
|
|
637
1062
|
let manifestsBlock = "";
|
|
638
1063
|
scanResult.manifests.forEach(m => {
|
|
639
|
-
manifestsBlock += `\n---
|
|
1064
|
+
manifestsBlock += `\n--- Manifest File: ${m.relativePath} ---\n\`\`\`\n${m.content}\n\`\`\`\n`;
|
|
640
1065
|
});
|
|
641
1066
|
|
|
642
|
-
|
|
1067
|
+
let snippetsBlock = "";
|
|
1068
|
+
filteredSnippets.forEach(s => {
|
|
1069
|
+
snippetsBlock += `\n--- Code Snippet: ${s.relativePath} ---\n\`\`\`\n${s.content}\n\`\`\`\n`;
|
|
1070
|
+
});
|
|
1071
|
+
|
|
1072
|
+
let markdownsBlock = "";
|
|
1073
|
+
scanResult.markdowns.forEach(md => {
|
|
1074
|
+
markdownsBlock += `\n--- Reference Markdown File: ${md.relativePath} ---\n\`\`\`markdown\n${md.content}\n\`\`\`\n`;
|
|
1075
|
+
});
|
|
1076
|
+
|
|
1077
|
+
const systemInstruction = `Role: You are an expert technical writer and principal software architect.
|
|
1078
|
+
Task: Your job is to generate a comprehensive, highly polished, professional README.md file based only on the provided directory structure, project configuration files, and references, formatted EXACTLY using the Best-README-Template by @othneildrew.
|
|
1079
|
+
|
|
1080
|
+
Visual Layout / Structure Requirements (Derived from othneildrew/Best-README-Template):
|
|
1081
|
+
The document MUST strictly follow this exact outline and formatting sequence:
|
|
1082
|
+
|
|
1083
|
+
1. Top Anchors & Shields:
|
|
1084
|
+
- Add a top anchor: \`<a name="readme-top"></a>\` at the absolute beginning of the file.
|
|
1085
|
+
- Centered shields/badges using raw Markdown with stylish links: Contributors, Forks, Stargazers, Issues, License, and (if provided) LinkedIn and Twitter. Specify reference URL identifiers at the absolute bottom of the file (e.g. \`[contributors-shield]: ...\`).
|
|
1086
|
+
- Put badges cleanly on top.
|
|
1087
|
+
|
|
1088
|
+
2. Project Logo & Headers (Centered):
|
|
1089
|
+
- Leave space for logo using \`<br />\` and then a centered div block containing:
|
|
1090
|
+
- An image tag targeting the repository logo with dimensions 80x80. If a Logo Path is provided under Project Metadata, use it as the image src (e.g., \`assets/logo.png\`). If not provided, search the provided project structure (e.g., assets/, images/, or root) for any logo image file (like \`logo.png\`, \`logo.svg\`, \`logo.jpg\`, \`assets/logo.png\`). If found, use that file path as the src. If no logo file exists in the repository, use the unsplash placeholder URL: "https://images.unsplash.com/photo-1618401471353-b98aedd07871?q=80&w=120&auto=format&fit=crop".
|
|
1091
|
+
- A centered \`<h3 align="center">Project Title</h3>\`
|
|
1092
|
+
- A short, descriptive project subtitle.
|
|
1093
|
+
- Links to Explore the Docs, View Demo, Report Bug, Request Feature centering on appropriate URLs.
|
|
1094
|
+
|
|
1095
|
+
3. Interactive Detail-TOC (Table of Contents):
|
|
1096
|
+
- Add a table of contents enclosed in a Collapsible Details panel (\`<details><summary>Table of Contents</summary>...\` using list elements with anchors pointing to respective sections: About The Project, Getting Started (Prerequisites, Installation), Usage, Roadmap, Contributing, License, Contact, Acknowledgments).
|
|
1097
|
+
|
|
1098
|
+
4. About the Project:
|
|
1099
|
+
- Place a section title \`## About The Project\` with a paragraph detailing what issues the library solves and why.
|
|
1100
|
+
- Provide a screenshot element (\`[![Product Name Screen Shot][product-screenshot]](https://example.com)\`). If a Screenshot Path is provided under Project Metadata, ensure the \`[product-screenshot]\` reference at the bottom resolves to that file path (e.g., \`assets/screenshot.png\`). If not provided, search the project structure for any screenshot image file (like \`screenshot.png\`, \`screenshot.jpg\`, \`assets/screenshot.png\`). If found, ensure the \`[product-screenshot]\` reference at the bottom resolves to that file path (e.g., \`assets/screenshot.png\`). If no screenshot file is found, resolve the reference to the unsplash placeholder: "https://images.unsplash.com/photo-1618401471353-b98aedd07871?q=80&w=600&auto=format&fit=crop".
|
|
1101
|
+
- Add a \`### Built With\` subsection with raw shields list linking to core framework technologies identified in the manifests or languages (e.g. Next.js, React, Python, Go, Rust, etc., with shields like \`* [![React][React.js]][React-url]\`).
|
|
1102
|
+
- Add \`<p align="right">(<a href="#readme-top">back to top</a>)</p>\` at the end of this and ALL subsequent major sections.
|
|
1103
|
+
|
|
1104
|
+
5. Getting Started:
|
|
1105
|
+
- Place \`## Getting Started\` heading.
|
|
1106
|
+
- Include \`### Prerequisites\` listing key runtime versions or engines with raw commands (e.g., npm or pip).
|
|
1107
|
+
- Include \`### Installation\` with custom sequential steps: Clone repository, Install dependencies/packages using correct package manager commands, Setup key constants/configs.
|
|
1108
|
+
|
|
1109
|
+
6. Usage:
|
|
1110
|
+
- Place \`## Usage\` heading with direct code instructions, visual scripts invocation, and explanation.
|
|
1111
|
+
|
|
1112
|
+
7. Roadmap:
|
|
1113
|
+
- Place \`## Roadmap\` heading highlighting future planning milestones or upcoming revisions. Use standard markdown task lists (e.g., \`- [ ] Feature 1\`).
|
|
1114
|
+
|
|
1115
|
+
8. Contributing:
|
|
1116
|
+
- Place \`## Contributing\` heading detailing the exact five-step guidelines for forks, branching, commits, and pull request initiation.
|
|
1117
|
+
|
|
1118
|
+
9. License:
|
|
1119
|
+
- Place \`## License\` heading highlighting the standard licensing info (e.g. MIT License).
|
|
1120
|
+
|
|
1121
|
+
10. Contact:
|
|
1122
|
+
- Place \`## Contact\` heading listing author/maintainer contact points, project links, or email tags.
|
|
1123
|
+
|
|
1124
|
+
11. Acknowledgments:
|
|
1125
|
+
- Place \`## Acknowledgments\` heading listing standard resources (e.g., Shields.io, select libraries, or other credits).
|
|
1126
|
+
|
|
1127
|
+
12. Bottom Link References:
|
|
1128
|
+
- At the absolute bottom of the document, define ALL referenced links and shield assets used at the top and Built With section dynamically to keep the rest of the text bodies pristine (e.g., \`[contributors-shield]: ...\`).
|
|
1129
|
+
|
|
1130
|
+
Project Metadata for customization (use these when constructing URLs, shields, badges, and contact references):
|
|
1131
|
+
- GitHub Username: ${githubUsername || "Not provided"}
|
|
1132
|
+
- GitHub Repository URL: ${githubRepoUrl || "Not provided"}
|
|
1133
|
+
- Twitter URL: ${twitterUrl || "Not provided"}
|
|
1134
|
+
- LinkedIn URL: ${linkedinUrl || "Not provided"}
|
|
1135
|
+
- Logo Path: ${logoPath || "Not provided"}
|
|
1136
|
+
- Screenshot Path: ${screenshotPath || "Not provided"}
|
|
1137
|
+
|
|
1138
|
+
Instructions for metadata integration:
|
|
1139
|
+
1. Construct GitHub shields (Contributors, Forks, Stars, Issues, License) and their URL references using the provided GitHub Username and Repository name (extracted from the Repo URL).
|
|
1140
|
+
2. If a LinkedIn URL is provided, include a LinkedIn shield at the top (using badge image URL e.g. "https://img.shields.io/badge/-LinkedIn-black.svg?style=for-the-badge&logo=linkedin&colorB=555") and define its bottom link reference pointing to the LinkedIn URL. If not, omit the LinkedIn shield.
|
|
1141
|
+
3. If a Twitter URL is provided, include a Twitter/X shield at the top (using badge image URL e.g. "https://img.shields.io/badge/-Twitter-black.svg?style=for-the-badge&logo=x&colorB=555" or "https://img.shields.io/badge/-Twitter-black.svg?style=for-the-badge&logo=twitter&colorB=555") and define its bottom link reference pointing to the Twitter URL. If not, omit the Twitter shield.
|
|
1142
|
+
4. Under the Contact section, list the user's contact information using the provided Twitter and LinkedIn links.
|
|
1143
|
+
5. If a Logo Path is provided, use it exactly as the src of the project logo image.
|
|
1144
|
+
6. If a Screenshot Path is provided, ensure the bottom reference \`[product-screenshot]\` resolves to it.
|
|
1145
|
+
|
|
1146
|
+
Do not write markdown block fences inside the generated readme itself unless wrapping code examples. Do not add any conversational meta-talk. Output the raw Best-README-Template markdown directly.`;
|
|
1147
|
+
|
|
1148
|
+
const prompt = `Generate a README.md file based on the following codebase analysis:
|
|
643
1149
|
|
|
644
1150
|
Project Structure:
|
|
645
1151
|
${treeBody}
|
|
646
1152
|
|
|
647
1153
|
${manifestsBlock}
|
|
648
1154
|
|
|
1155
|
+
${snippetsBlock}
|
|
1156
|
+
|
|
1157
|
+
${markdownsBlock}
|
|
1158
|
+
|
|
1159
|
+
${githubUsername || githubRepoUrl || twitterUrl || linkedinUrl || logoPath || screenshotPath ? `Project Metadata:\n- GitHub Username: ${githubUsername || "N/A"}\n- GitHub Repository: ${githubRepoUrl || "N/A"}\n- Twitter: ${twitterUrl || "N/A"}\n- LinkedIn: ${linkedinUrl || "N/A"}\n- Logo Path: ${logoPath || "N/A"}\n- Screenshot Path: ${screenshotPath || "N/A"}\n` : ""}
|
|
1160
|
+
|
|
649
1161
|
Generate the complete, polished Markdown output directly without any extra conversation.`;
|
|
650
1162
|
|
|
651
1163
|
const response = await ai.models.generateContent({
|
|
652
1164
|
model: "gemini-3.5-flash",
|
|
653
1165
|
contents: prompt,
|
|
654
1166
|
config: {
|
|
655
|
-
systemInstruction
|
|
1167
|
+
systemInstruction,
|
|
656
1168
|
temperature: 0.2
|
|
657
1169
|
}
|
|
658
1170
|
});
|
package/package.json
CHANGED