commentme 1.0.2 → 1.0.4

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 CHANGED
@@ -35,6 +35,29 @@ commentme --skim "file-name"
35
35
  ### Restore comments to a file
36
36
  commentme --unskim "file-name"
37
37
 
38
+ ### AI Generation
39
+ Generate AI comments and JSDoc-style documentation for your code.
40
+ ```bash
41
+ commentme --generate "file-name"
42
+ ```
43
+ You can choose to generate comments per function, per class, or per line.
44
+
45
+ ### AI Explanation
46
+ Generate a full markdown explanation of a code file.
47
+ ```bash
48
+ commentme --explain "file-name"
49
+ ```
50
+
51
+ ### API Key Management
52
+ Set your own OpenRouter API key to use the AI features.
53
+ ```bash
54
+ # Set your API key
55
+ commentme --set-key
56
+
57
+ # Clear your saved API key
58
+ commentme --clear-key
59
+ ```
60
+
38
61
  ### Logout
39
62
  commentme --logout
40
63
 
@@ -43,6 +66,9 @@ commentme --logout
43
66
  - Clutter-free and smooth codebase
44
67
  - Redact comments from files while keeping references
45
68
  - Restore comments back to files whenever required
69
+ - **AI-powered documentation generation (Function/Class/Line level)**
70
+ - **Full code explanation generator (Markdown output)**
71
+ - **Secure API Key management for custom AI models**
46
72
  - User authentication and session management
47
73
  - Per-codebase comment organization
48
74
  - Dedicated website for more UI friendly & AI edits on comments
package/bin/commentme.js CHANGED
@@ -8,12 +8,16 @@ import { editComment } from "../editcoms.js";
8
8
  import { deleteComment } from "../deletecoms.js";
9
9
  import { removeCommentsFromFile as skim } from "../skimcoms.js";
10
10
  import { unskimComments as unskim } from "../unskimcoms.js";
11
+ import { generateCommentsPerFunc, generateCommentsPerClass, generateCommentsPerLine, generateExplanation } from "../generate.js";
11
12
  // import { connectDB, disconnectDB } from "../config/db.js";
12
13
  import { ensureAuth } from "../auth/authGuard.js";
13
14
  import { logout } from "../auth/logout.js";
15
+ import { saveApiKey, clearApiKey } from "../utils/apiKeyManager.js";
16
+ import { promptPassword } from "../utils/passwordPrompt.js";
14
17
  import dotenv from "dotenv";
15
18
  dotenv.config();
16
19
 
20
+
17
21
  function promptInput(defaultValue = "") {
18
22
  const rl = readline.createInterface({
19
23
  input: process.stdin,
@@ -31,6 +35,8 @@ function promptInput(defaultValue = "") {
31
35
  async function main() {
32
36
  const args = process.argv.slice(2);
33
37
  const command = args[0];
38
+ console.log("Args:", args);
39
+ console.log("Command:", command);
34
40
 
35
41
  try {
36
42
  // Show help without connecting to DB or requiring auth
@@ -45,6 +51,10 @@ Commands:
45
51
  commentme --delete line-7-7 <file> Delete a comment
46
52
  commentme --skim <file> Redact comments from a file and store them
47
53
  commentme --unskim <file> Restore comments to a file
54
+ commentme --generate <file> Generate AI comments and docs for a file
55
+ commentme --explain <file> Generate a full markdown explanation of a code file
56
+ commentme --set-key Set your own OpenRouter API key (stored securely)
57
+ commentme --clear-key Remove your saved API key
48
58
  commentme --logout Log out from your session
49
59
  commentme --help Show this help message
50
60
  `);
@@ -57,7 +67,7 @@ Commands:
57
67
 
58
68
  // 🔐 Skip auth ONLY for logout
59
69
  // 🔐 Skip auth for logout, login, and signup
60
- if (command !== "--logout" && command !== "--login" && command !== "--signup") {
70
+ if (command !== "--logout" && command !== "--login" && command !== "--signup" && command !== "--set-key" && command !== "--clear-key") {
61
71
  await ensureAuth();
62
72
  }
63
73
 
@@ -70,8 +80,6 @@ Commands:
70
80
  await import("../auth/signup.js").then(m => m.signup());
71
81
  break;
72
82
 
73
- case "--logout":
74
-
75
83
  case "--get":
76
84
  if (args[1] === "lines" && args[2]) {
77
85
  await getAllComments(args[2]);
@@ -111,6 +119,55 @@ Commands:
111
119
  await unskim(args[1]);
112
120
  break;
113
121
 
122
+ case "--generate":
123
+ if (!args[1]) throw new Error("Usage: commentme --generate <file>");
124
+ const fileToGenerate = args[1];
125
+
126
+ console.log(`
127
+ Select generation type:
128
+ 1. Generate comments per function + docs
129
+ 2. Generate comments per class + docs
130
+ 3. Generate comments per line + docs
131
+ `);
132
+
133
+ const choice = await promptInput("1");
134
+
135
+ switch (choice.trim()) {
136
+ case "1":
137
+ await generateCommentsPerFunc(fileToGenerate);
138
+ break;
139
+ case "2":
140
+ await generateCommentsPerClass(fileToGenerate);
141
+ break;
142
+ case "3":
143
+ await generateCommentsPerLine(fileToGenerate);
144
+ break;
145
+ default:
146
+ console.log("Invalid choice. Defaulting to Per Function.");
147
+ await generateCommentsPerFunc(fileToGenerate);
148
+ }
149
+ break;
150
+
151
+ case "--explain":
152
+ if (!args[1]) throw new Error("Usage: commentme --explain <file>");
153
+ await generateExplanation(args[1]);
154
+ break;
155
+
156
+ case "--set-key": {
157
+ console.log("Paste your OpenRouter API key (input is hidden):");
158
+ const key = await promptPassword("🔑 API Key: ");
159
+ if (!key || key.trim().length === 0) {
160
+ console.log("❌ No key provided. Aborted.");
161
+ } else {
162
+ saveApiKey(key.trim());
163
+ }
164
+ break;
165
+ }
166
+
167
+ case "--clear-key":
168
+ clearApiKey();
169
+ break;
170
+
114
171
  case "--logout":
115
172
  logout();
116
173
  break;
@@ -128,6 +185,10 @@ Commands:
128
185
  commentme --delete line-7-7 <file>
129
186
  commentme --skim <file>
130
187
  commentme --unskim <file>
188
+ commentme --generate <file>
189
+ commentme --explain <file>
190
+ commentme --set-key
191
+ commentme --clear-key
131
192
  commentme --logout
132
193
  `);
133
194
  }
package/generate.js ADDED
@@ -0,0 +1,295 @@
1
+ import fs from "fs";
2
+ import path from "path";
3
+ import { getCurrentUserId } from "./utils/currentUser.js";
4
+ import { getApiKey } from "./utils/apiKeyManager.js";
5
+
6
+ // ── Model pool for circuit breaker fallback ──
7
+ const FREE_MODELS = [
8
+ "google/gemma-3-27b-it:free",
9
+ "meta-llama/llama-3.2-3b-instruct:free",
10
+ "mistralai/mistral-7b-instruct:free",
11
+ "google/gemma-3-4b-it:free",
12
+ "deepseek/deepseek-r1-0528:free",
13
+ ];
14
+
15
+ const MAX_PASSES = 2; // full cycles through model list
16
+ const COOLDOWN_MS = 5000; // wait between passes
17
+
18
+ /**
19
+ * Resolve the API key: user config > env var > error
20
+ */
21
+ function resolveApiKey() {
22
+ // Priority 1: user's own key from ~/.commentme-config.json
23
+ const userKey = getApiKey();
24
+ if (userKey) return userKey;
25
+
26
+ // Priority 2: env var
27
+ if (process.env.OPENROUTER_API_KEY) return process.env.OPENROUTER_API_KEY;
28
+
29
+ // No key found
30
+ throw new Error(
31
+ "No API key found. Set one with:\n" +
32
+ " commentme --set-key (recommended — stores in ~/.commentme-config.json)\n" +
33
+ " or set OPENROUTER_API_KEY in your .env file"
34
+ );
35
+ }
36
+
37
+ async function callAI(prompt, code) {
38
+ const apiKey = resolveApiKey();
39
+
40
+ for (let pass = 0; pass < MAX_PASSES; pass++) {
41
+ if (pass > 0) {
42
+ console.log(`\nâŗ All models rate-limited. Waiting ${COOLDOWN_MS / 1000}s before retry (pass ${pass + 1}/${MAX_PASSES})...`);
43
+ await new Promise(r => setTimeout(r, COOLDOWN_MS));
44
+ }
45
+
46
+ for (const model of FREE_MODELS) {
47
+ console.log(`🔄 Trying model: ${model}...`);
48
+
49
+ const response = await fetch("https://openrouter.ai/api/v1/chat/completions", {
50
+ method: "POST",
51
+ headers: {
52
+ "Content-Type": "application/json",
53
+ "Authorization": `Bearer ${apiKey}`,
54
+ },
55
+ body: JSON.stringify({
56
+ model,
57
+ messages: [
58
+ {
59
+ role: "user",
60
+ content: `${prompt}\n\n---\n\nHere is the code:\n\n${code}`
61
+ }
62
+ ]
63
+ })
64
+ });
65
+
66
+ // Auth errors → fail immediately (wrong API key)
67
+ if (response.status === 401 || response.status === 403) {
68
+ const errorData = await response.json().catch(() => ({}));
69
+ throw new Error(`Authentication error (${response.status}): Check your API key. ${JSON.stringify(errorData)}`);
70
+ }
71
+
72
+ // Any other error (429, 404, 400, 503, 521, etc.) → skip to next model
73
+ if (!response.ok) {
74
+ const errorData = await response.json().catch(() => ({}));
75
+ const msg = errorData?.error?.message || `${response.status} ${response.statusText}`;
76
+ console.warn(`âš ī¸ ${model} failed (${response.status}: ${msg}), trying next model...`);
77
+ continue;
78
+ }
79
+
80
+ // Success!
81
+ const data = await response.json();
82
+ console.log(`✅ Success with model: ${model}`);
83
+ return data.choices[0]?.message?.content || "";
84
+ }
85
+ }
86
+
87
+ throw new Error(
88
+ "All models are rate-limited after " + MAX_PASSES + " passes. " +
89
+ "Please wait a few minutes and try again, or set your own API key with: commentme --set-key"
90
+ );
91
+ }
92
+
93
+
94
+ async function processAIResponse(filePath, responseContent) {
95
+ const parts = responseContent.split("#####DOCS_START#####");
96
+ let commentedCode = parts[0].trim();
97
+ const documentation = parts.length > 1 ? parts[1].trim() : "";
98
+
99
+ // If the AI returns markdown code blocks, strip them
100
+ if (commentedCode.startsWith("```") && commentedCode.endsWith("```")) {
101
+ const lines = commentedCode.split("\n");
102
+ // Remove first and last lines
103
+ if (lines.length >= 2) {
104
+ commentedCode = lines.slice(1, -1).join("\n");
105
+ }
106
+ }
107
+
108
+ // Write commented code back to file
109
+ fs.writeFileSync(filePath, commentedCode, "utf8");
110
+ console.log(`✅ Comments added to ${filePath}`);
111
+
112
+ // Write documentation to a new file
113
+ if (documentation) {
114
+ const ext = path.extname(filePath);
115
+ const baseName = path.basename(filePath, ext);
116
+ const docPath = path.join(path.dirname(filePath), `${baseName}_docs.md`);
117
+ fs.writeFileSync(docPath, documentation, "utf8");
118
+ console.log(`✅ Documentation generated at ${docPath}`);
119
+ }
120
+ }
121
+
122
+ export async function generateCommentsPerFunc(filePath, codebase = null) {
123
+ if (!fs.existsSync(filePath)) {
124
+ throw new Error(`File not found: ${filePath}`);
125
+ }
126
+
127
+ // Use filename as codebase if not provided
128
+ if (!codebase) {
129
+ codebase = path.basename(filePath);
130
+ }
131
+
132
+ // Check authentication (keeping original logic)
133
+ try {
134
+ getCurrentUserId();
135
+ } catch (e) {
136
+ console.error(e.message);
137
+ return;
138
+ }
139
+
140
+ const code = fs.readFileSync(filePath, "utf8");
141
+ console.log(`âŗ Generating function comments for ${filePath}...`);
142
+
143
+ const prompt = `
144
+ You are an expert AI coding assistant. behavior:
145
+ 1. Analyze the provided ${codebase ? codebase : "code"} and take in the reference of the comments if the file already consist comments as understanding the codebase through comments might make you understand the flow as well as the functionality.
146
+ 2. Add meaningful JSDoc-style or block comments above EVERY function definition explaining what it does, its parameters, and return value.
147
+ 3. Keep the original code EXACTLY as is, only adding comments. Do NOT remove existing comments unless they are redundant.
148
+ 4. Generate a comprehensive markdown documentation summarizing each function.
149
+ 5. Output the result in two parts separated by the delimiter "#####DOCS_START#####".
150
+ Part 1: The full code with added comments.
151
+ Part 2: The markdown documentation.
152
+ `;
153
+
154
+ try {
155
+ const response = await callAI(prompt, code);
156
+ await processAIResponse(filePath, response);
157
+ console.log(`✨ Successfully generated function comments for ${filePath}`);
158
+ } catch (error) {
159
+ console.error("❌ Error generating comments:", error.message);
160
+ }
161
+ }
162
+
163
+ export async function generateCommentsPerClass(filePath, codebase = null) {
164
+ if (!fs.existsSync(filePath)) {
165
+ throw new Error(`File not found: ${filePath}`);
166
+ }
167
+
168
+ if (!codebase) {
169
+ codebase = path.basename(filePath);
170
+ }
171
+
172
+ try {
173
+ getCurrentUserId();
174
+ } catch (e) {
175
+ console.error(e.message);
176
+ return;
177
+ }
178
+
179
+ const code = fs.readFileSync(filePath, "utf8");
180
+ console.log(`âŗ Generating class comments for ${filePath}...`);
181
+
182
+ const prompt = `
183
+ You are an expert AI coding assistant. behavior:
184
+ 1. Analyze the provided ${codebase ? codebase : "code"}.
185
+ 2. Add meaningful JSDoc-style or block comments above EVERY class definition explaining its purpose, constructor, and methods.
186
+ 3. Keep the original code EXACTLY as is, only adding comments.
187
+ 4. Generate a comprehensive markdown documentation summarizing each class.
188
+ 5. Output the result in two parts separated by the delimiter "#####DOCS_START#####".
189
+ Part 1: The full code with added comments.
190
+ Part 2: The markdown documentation.
191
+ `;
192
+
193
+ try {
194
+ const response = await callAI(prompt, code);
195
+ await processAIResponse(filePath, response);
196
+ console.log(`✨ Successfully generated function comments for ${filePath}`);
197
+ } catch (error) {
198
+ console.error("❌ Error generating comments:", error.message);
199
+ }
200
+ }
201
+
202
+ export async function generateCommentsPerLine(filePath, codebase = null) {
203
+ if (!fs.existsSync(filePath)) {
204
+ throw new Error(`File not found: ${filePath}`);
205
+ }
206
+
207
+ if (!codebase) {
208
+ codebase = path.basename(filePath);
209
+ }
210
+
211
+ try {
212
+ getCurrentUserId();
213
+ } catch (e) {
214
+ console.error(e.message);
215
+ return;
216
+ }
217
+
218
+ const code = fs.readFileSync(filePath, "utf8");
219
+ console.log(`âŗ Generating line-by-line comments for ${filePath}...`);
220
+
221
+ const prompt = `
222
+ You are an expert AI coding assistant. behavior:
223
+ 1. Analyze the provided ${codebase ? codebase : "code"}.
224
+ 2. Add concise inline comments (// ...) for significant lines of code explaining what they do. Avoid obvious comments.
225
+ 3. Keep the original code logic EXACTLY as is, only adding comments.
226
+ 4. Generate a comprehensive markdown documentation summarizing the flow of the code line-by-line or block-by-block.
227
+ 5. Output the result in two parts separated by the delimiter "#####DOCS_START#####".
228
+ Part 1: The full code with added comments.
229
+ Part 2: The markdown documentation.
230
+ `;
231
+
232
+ try {
233
+ const response = await callAI(prompt, code);
234
+ await processAIResponse(filePath, response);
235
+ console.log(`✨ Successfully generated function comments for ${filePath}`);
236
+ } catch (error) {
237
+ console.error("❌ Error generating comments:", error.message);
238
+ }
239
+ }
240
+
241
+ export async function generateExplanation(filePath) {
242
+ if (!fs.existsSync(filePath)) {
243
+ throw new Error(`File not found: ${filePath}`);
244
+ }
245
+
246
+ try {
247
+ getCurrentUserId();
248
+ } catch (e) {
249
+ console.error(e.message);
250
+ return;
251
+ }
252
+
253
+ const code = fs.readFileSync(filePath, "utf8");
254
+ const fileName = path.basename(filePath);
255
+ console.log(`âŗ Generating explanation for ${filePath}...`);
256
+
257
+ const prompt = `
258
+ You are an expert AI coding assistant and technical writer. Your task:
259
+ 1. Read the provided code file "${fileName}" carefully, including ALL existing comments.
260
+ 2. Use the existing comments as references to understand the purpose, flow, and functionality of the code.
261
+ 3. Generate a comprehensive, well-structured Markdown document that explains the ENTIRE file.
262
+
263
+ The markdown document MUST include:
264
+ - **Overview**: A brief summary of what this file does and its role in the project.
265
+ - **Dependencies / Imports**: List and explain each import and why it's needed.
266
+ - **Architecture & Flow**: Describe the overall execution flow from top to bottom — how the different parts connect and interact.
267
+ - **Functions / Classes**: For each function or class, explain:
268
+ - Purpose
269
+ - Parameters and return values
270
+ - Internal logic (step by step)
271
+ - How it relates to other functions in the file
272
+ - **Key Logic & Patterns**: Highlight any notable patterns, algorithms, error handling strategies, or design decisions.
273
+ - **Summary**: A concise wrap-up of the file's responsibilities.
274
+
275
+ IMPORTANT:
276
+ - Output ONLY the markdown documentation, nothing else.
277
+ - Do NOT include the original code in your output.
278
+ - Do NOT wrap the output in a code block.
279
+ - Reference line numbers or function names from the code when explaining.
280
+ - Make it readable for a developer who has never seen this codebase before.
281
+ `;
282
+
283
+ try {
284
+ const response = await callAI(prompt, code);
285
+
286
+ const ext = path.extname(filePath);
287
+ const baseName = path.basename(filePath, ext);
288
+ const docPath = path.join(path.dirname(filePath), `${baseName}_explained.md`);
289
+
290
+ fs.writeFileSync(docPath, response.trim(), "utf8");
291
+ console.log(`✅ Explanation generated at ${docPath}`);
292
+ } catch (error) {
293
+ console.error("❌ Error generating explanation:", error.message);
294
+ }
295
+ }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "commentme",
3
- "version": "1.0.2",
3
+ "version": "1.0.4",
4
4
  "description": "A CLI tool to manage, redact and unredact your comments keeping the codebase clutterfree.",
5
5
  "type": "module",
6
6
  "bin": {
@@ -0,0 +1,51 @@
1
+ import fs from "fs";
2
+ import path from "path";
3
+ import os from "os";
4
+
5
+ const CONFIG_PATH = path.join(os.homedir(), ".commentme-config.json");
6
+
7
+ export function saveApiKey(key) {
8
+ const config = loadConfig();
9
+ config.apiKey = key;
10
+ fs.writeFileSync(CONFIG_PATH, JSON.stringify(config, null, 2), { mode: 0o600 });
11
+
12
+
13
+ fs.chmodSync(CONFIG_PATH, 0o600);
14
+
15
+ const masked = key.length > 4 ? "****" + key.slice(-4) : "****";
16
+ console.log(`✅ API key saved (${masked})`);
17
+ console.log(` Stored at: ${CONFIG_PATH} (owner-read-only)`);
18
+ }
19
+
20
+ export function getApiKey() {
21
+ const config = loadConfig();
22
+ return config.apiKey || null;
23
+ }
24
+
25
+ export function clearApiKey() {
26
+ if (fs.existsSync(CONFIG_PATH)) {
27
+ const config = loadConfig();
28
+ delete config.apiKey;
29
+
30
+
31
+ if (Object.keys(config).length === 0) {
32
+ fs.unlinkSync(CONFIG_PATH);
33
+ } else {
34
+ fs.writeFileSync(CONFIG_PATH, JSON.stringify(config, null, 2), { mode: 0o600 });
35
+ }
36
+
37
+ console.log("✅ API key cleared.");
38
+ } else {
39
+ console.log("â„šī¸ No saved API key found.");
40
+ }
41
+ }
42
+
43
+
44
+ function loadConfig() {
45
+ if (!fs.existsSync(CONFIG_PATH)) return {};
46
+ try {
47
+ return JSON.parse(fs.readFileSync(CONFIG_PATH, "utf8"));
48
+ } catch {
49
+ return {};
50
+ }
51
+ }