commentme 1.0.2 â 1.0.3
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/bin/commentme.js +64 -3
- package/generate.js +295 -0
- package/package.json +1 -1
- package/utils/apiKeyManager.js +51 -0
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
|
@@ -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
|
+
}
|