devsplain 1.0.0 → 1.2.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +81 -0
- package/bin/cli.js +172 -35
- package/lib/config.js +80 -35
- package/lib/llm.js +140 -53
- package/package.json +8 -5
package/README.md
ADDED
|
@@ -0,0 +1,81 @@
|
|
|
1
|
+
# devsplain
|
|
2
|
+
|
|
3
|
+

|
|
4
|
+
|
|
5
|
+
An agent-agnostic CLI tool that automatically adds JSDoc and inline comments to your code using free LLMs.
|
|
6
|
+
|
|
7
|
+
Tired of writing documentation? Let AI do the explaining for you. `devsplain` reads your code and intelligently injects standard JSDoc headers and inline comments.
|
|
8
|
+
|
|
9
|
+
## Installation
|
|
10
|
+
|
|
11
|
+
Install it globally via npm:
|
|
12
|
+
|
|
13
|
+
```bash
|
|
14
|
+
npm install -g devsplain
|
|
15
|
+
```
|
|
16
|
+
|
|
17
|
+
## Usage
|
|
18
|
+
|
|
19
|
+
Simply point `devsplain` at any file you want to comment:
|
|
20
|
+
|
|
21
|
+
```bash
|
|
22
|
+
devsplain src/utils.js
|
|
23
|
+
```
|
|
24
|
+
|
|
25
|
+
### Directory Support (Bulk Processing)
|
|
26
|
+
|
|
27
|
+
You don't have to go file-by-file! Point `devsplain` at an entire directory, and it will recursively crawl through your codebase and comment everything.
|
|
28
|
+
|
|
29
|
+
```bash
|
|
30
|
+
devsplain src/
|
|
31
|
+
```
|
|
32
|
+
|
|
33
|
+
_Note: `devsplain` is smart. It automatically ignores junk folders like `node_modules`, `.git`, `dist`, `venv`, and `.next` to save you time and API tokens._
|
|
34
|
+
|
|
35
|
+
### Modes
|
|
36
|
+
|
|
37
|
+
You can control exactly how aggressive the AI is with its comments using flags:
|
|
38
|
+
|
|
39
|
+
- `--light`: Only adds JSDoc blocks above functions. Keeps the inside of your functions completely untouched.
|
|
40
|
+
- `--full`: Highly aggressive. Explains complex logic line-by-line inside your functions.
|
|
41
|
+
- `--clean`: A code scrubber. Removes ALL existing comments from the code, leaving it completely bare.
|
|
42
|
+
- `--dry-run`: Interactive preview. Prints the AI's output to the terminal and waits for your approval before saving to the file. Extremely safe for testing!
|
|
43
|
+
- **(Default)**: A balanced mix of JSDoc headers and sparse inline comments for complex logic.
|
|
44
|
+
|
|
45
|
+
**Usage Examples:**
|
|
46
|
+
|
|
47
|
+
```bash
|
|
48
|
+
devsplain src/utils.js --light
|
|
49
|
+
devsplain src/ --full
|
|
50
|
+
devsplain legacy_code.js --clean
|
|
51
|
+
devsplain lib/ --dry-run
|
|
52
|
+
```
|
|
53
|
+
|
|
54
|
+
### Supported Languages
|
|
55
|
+
|
|
56
|
+
Because `devsplain` uses LLMs, it natively understands almost every language syntax. It currently processes the following extensions:
|
|
57
|
+
|
|
58
|
+
- **Web**: `.js`, `.jsx`, `.ts`, `.tsx`, `.html`, `.css`, `.scss`, `.vue`, `.svelte`
|
|
59
|
+
- **Backend**: `.py`, `.java`, `.c`, `.cpp`, `.cs`, `.go`, `.rb`, `.php`, `.rs`
|
|
60
|
+
- **Mobile/Scripts**: `.swift`, `.kt`, `.dart`, `.sh`
|
|
61
|
+
|
|
62
|
+
## Agent Agnostic (Bring Your Own LLM)
|
|
63
|
+
|
|
64
|
+
`devsplain` doesn't lock you into one ecosystem. On your first run, an interactive Setup Wizard will ask you which engine you want to use:
|
|
65
|
+
|
|
66
|
+
1. **Groq** (Recommended) - Instant, free Llama-3 endpoints.
|
|
67
|
+
2. **Gemini** - Google's free tier endpoints.
|
|
68
|
+
3. **OpenAI** - Standard paid ChatGPT models.
|
|
69
|
+
4. **Custom** - Point it at ANY OpenAI-compatible endpoint (e.g., local models via Ollama or LMStudio).
|
|
70
|
+
|
|
71
|
+
_(Your configuration is safely stored in `~/.devsplainrc` on your machine)._
|
|
72
|
+
|
|
73
|
+
---
|
|
74
|
+
|
|
75
|
+
### Disclaimer
|
|
76
|
+
|
|
77
|
+
**Use at your own risk.** `devsplain` uses AI to physically overwrite your source files. While the prompts are heavily engineered to prevent code refactoring or corruption, AI is non-deterministic. **Always ensure your code is committed to Git (Version Control) before running `devsplain` on an entire directory!** We are not responsible for corrupted or lost code.
|
|
78
|
+
|
|
79
|
+
## License
|
|
80
|
+
|
|
81
|
+
MIT
|
package/bin/cli.js
CHANGED
|
@@ -1,59 +1,196 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
|
+
|
|
2
3
|
/**
|
|
3
|
-
*
|
|
4
|
-
* @
|
|
5
|
-
* @
|
|
6
|
-
* @
|
|
4
|
+
* Import required modules.
|
|
5
|
+
* @module llm
|
|
6
|
+
* @module config
|
|
7
|
+
* @module fs
|
|
8
|
+
* @module path
|
|
9
|
+
* @module readline
|
|
7
10
|
*/
|
|
8
|
-
const { getComments } = require('../lib/llm.js')
|
|
9
|
-
const { getConfig } = require('../lib/config.js')
|
|
11
|
+
const { getComments } = require('../lib/llm.js');
|
|
12
|
+
const { getConfig } = require('../lib/config.js');
|
|
10
13
|
const fs = require('fs');
|
|
14
|
+
const path = require('path');
|
|
15
|
+
const readline = require('readline');
|
|
16
|
+
const rl = readline.createInterface({ input: process.stdin, output: process.stdout });
|
|
17
|
+
|
|
18
|
+
/**
|
|
19
|
+
* Asks a question and returns the answer as a promise.
|
|
20
|
+
* @param {string} query - The question to be asked.
|
|
21
|
+
* @returns {Promise<string>} The answer to the question.
|
|
22
|
+
*/
|
|
23
|
+
const askQuestion = (query) => new Promise((resolve) => rl.question(query, resolve));
|
|
11
24
|
|
|
12
25
|
/**
|
|
13
|
-
*
|
|
26
|
+
* Get the filepath and arguments from the command line.
|
|
14
27
|
* @type {string}
|
|
15
28
|
*/
|
|
16
|
-
const filepath = process.argv[2]
|
|
29
|
+
const filepath = process.argv[2];
|
|
30
|
+
const args = process.argv.slice(3);
|
|
31
|
+
let mode = 'default';
|
|
17
32
|
|
|
18
33
|
/**
|
|
19
|
-
*
|
|
20
|
-
*
|
|
34
|
+
* Determine the mode based on the command line arguments.
|
|
35
|
+
* Supported modes are: 'light', 'full', and 'clean'.
|
|
36
|
+
*/
|
|
37
|
+
if (args.includes('--light')) mode = 'light';
|
|
38
|
+
if (args.includes('--full')) mode = 'full';
|
|
39
|
+
if (args.includes('--clean')) mode = 'clean';
|
|
40
|
+
const isDryRun = args.includes('--dry-run');
|
|
41
|
+
|
|
42
|
+
/**
|
|
43
|
+
* Check if a filepath was provided, if not display usage information.
|
|
21
44
|
*/
|
|
22
45
|
if (!filepath) {
|
|
23
|
-
console.log("usage:
|
|
24
|
-
process.exit(1)
|
|
25
|
-
}
|
|
46
|
+
console.log("usage: devsplain <file-or-directory>");
|
|
47
|
+
process.exit(1);
|
|
48
|
+
}
|
|
49
|
+
else if (!fs.existsSync(filepath)) {
|
|
50
|
+
console.log(`Error: The path '${filepath}' does not exist.`);
|
|
51
|
+
process.exit(1);
|
|
52
|
+
}
|
|
26
53
|
else {
|
|
27
|
-
/**
|
|
28
|
-
* Defines an asynchronous function to comment the code in the provided file.
|
|
29
|
-
* @async
|
|
30
|
-
*/
|
|
31
54
|
(async () => {
|
|
32
55
|
/**
|
|
33
|
-
*
|
|
56
|
+
* Get the configuration.
|
|
34
57
|
* @type {object}
|
|
35
58
|
*/
|
|
36
59
|
const config = await getConfig();
|
|
37
|
-
|
|
60
|
+
|
|
38
61
|
/**
|
|
39
|
-
*
|
|
40
|
-
* @
|
|
62
|
+
* Process a path, either a file or directory.
|
|
63
|
+
* @param {string} targetPath - The path to process.
|
|
64
|
+
* @returns {Promise<void>}
|
|
41
65
|
*/
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
66
|
+
async function processPath(targetPath) {
|
|
67
|
+
/**
|
|
68
|
+
* Get the stats of the target path.
|
|
69
|
+
* @type {fs.Stats}
|
|
70
|
+
*/
|
|
71
|
+
const stats = fs.statSync(targetPath);
|
|
72
|
+
|
|
73
|
+
/**
|
|
74
|
+
* If the target path is a directory, process its contents.
|
|
75
|
+
*/
|
|
76
|
+
if (stats.isDirectory()) {
|
|
77
|
+
const folderName = path.basename(targetPath);
|
|
78
|
+
/**
|
|
79
|
+
* List of ignored folders.
|
|
80
|
+
* @type {string[]}
|
|
81
|
+
*/
|
|
82
|
+
const ignoredFolders = [
|
|
83
|
+
'node_modules', '.git', 'dist', 'build', 'out',
|
|
84
|
+
'.next', '.nuxt', '.svelte-kit',
|
|
85
|
+
'venv', 'env', '.venv',
|
|
86
|
+
'.vscode', '.idea', 'coverage'
|
|
87
|
+
];
|
|
88
|
+
|
|
89
|
+
/**
|
|
90
|
+
* Check if the folder should be ignored.
|
|
91
|
+
*/
|
|
92
|
+
if (ignoredFolders.includes(folderName)) {
|
|
93
|
+
// Folder will be skipped
|
|
94
|
+
return;
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
console.log(`\n Scanning directory: ${targetPath}`);
|
|
98
|
+
/**
|
|
99
|
+
* Read the contents of the directory.
|
|
100
|
+
* @type {string[]}
|
|
101
|
+
*/
|
|
102
|
+
const items = fs.readdirSync(targetPath);
|
|
103
|
+
|
|
104
|
+
/**
|
|
105
|
+
* Process each item in the directory.
|
|
106
|
+
*/
|
|
107
|
+
for (const item of items) {
|
|
108
|
+
const fullPath = path.join(targetPath, item);
|
|
109
|
+
await processPath(fullPath);
|
|
110
|
+
}
|
|
111
|
+
}
|
|
112
|
+
/**
|
|
113
|
+
* If the target path is a file, process it.
|
|
114
|
+
*/
|
|
115
|
+
else if (stats.isFile()) {
|
|
116
|
+
/**
|
|
117
|
+
* Get the file extension.
|
|
118
|
+
* @type {string}
|
|
119
|
+
*/
|
|
120
|
+
const ext = path.extname(targetPath).toLowerCase();
|
|
121
|
+
/**
|
|
122
|
+
* List of valid file extensions.
|
|
123
|
+
* @type {string[]}
|
|
124
|
+
*/
|
|
125
|
+
const validExtensions = [
|
|
126
|
+
'.js', '.jsx', '.ts', '.tsx', '.html', '.css', '.scss', '.vue', '.svelte',
|
|
127
|
+
'.py', '.java', '.c', '.cpp', '.cs', '.go', '.rb', '.php', '.rs',
|
|
128
|
+
'.swift', '.kt', '.dart', '.sh'
|
|
129
|
+
];
|
|
130
|
+
|
|
131
|
+
/**
|
|
132
|
+
* Check if the file extension is valid.
|
|
133
|
+
*/
|
|
134
|
+
if (!validExtensions.includes(ext)) {
|
|
135
|
+
// File type is not supported, skipping
|
|
136
|
+
return;
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
const filename = path.basename(targetPath);
|
|
140
|
+
/**
|
|
141
|
+
* Read the file contents.
|
|
142
|
+
* @type {string}
|
|
143
|
+
*/
|
|
144
|
+
const data = fs.readFileSync(targetPath, 'utf-8');
|
|
145
|
+
|
|
146
|
+
/**
|
|
147
|
+
* Check if the file is empty.
|
|
148
|
+
*/
|
|
149
|
+
if (data.trim() === '') {
|
|
150
|
+
console.log(` Skipping ${filename} (Empty File)`);
|
|
151
|
+
return;
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
console.log(` Analyzing ${filename} in ${mode} mode...`);
|
|
155
|
+
/**
|
|
156
|
+
* Get the commented code.
|
|
157
|
+
* @type {string}
|
|
158
|
+
*/
|
|
159
|
+
const commentedCode = await getComments(data, filename, config, mode);
|
|
160
|
+
|
|
161
|
+
/**
|
|
162
|
+
* If in dry run mode, display a preview of the commented code.
|
|
163
|
+
*/
|
|
164
|
+
if (isDryRun) {
|
|
165
|
+
console.log(`\n --- DRY RUN PREVIEW: ${filename} ---`);
|
|
166
|
+
console.log(commentedCode);
|
|
167
|
+
console.log(`---------------------------------------\n`);
|
|
168
|
+
|
|
169
|
+
/**
|
|
170
|
+
* Ask the user if they want to save the commented code.
|
|
171
|
+
*/
|
|
172
|
+
const answer = await askQuestion("Type 'write' to save to file, or press any key to discard ");
|
|
173
|
+
|
|
174
|
+
if (answer.toLowerCase() == 'write') {
|
|
175
|
+
fs.writeFileSync(targetPath, commentedCode);
|
|
176
|
+
console.log(` Successfully saved ${targetPath}`);
|
|
177
|
+
} else {
|
|
178
|
+
console.log(` Skipped ${targetPath}`);
|
|
179
|
+
}
|
|
180
|
+
} else {
|
|
181
|
+
/**
|
|
182
|
+
* Write the commented code to the file.
|
|
183
|
+
*/
|
|
184
|
+
fs.writeFileSync(targetPath, commentedCode);
|
|
185
|
+
console.log(` Successfully commented ${targetPath}`);
|
|
186
|
+
}
|
|
187
|
+
}
|
|
188
|
+
}
|
|
189
|
+
|
|
47
190
|
/**
|
|
48
|
-
*
|
|
49
|
-
* @type {string}
|
|
191
|
+
* Start processing the provided filepath.
|
|
50
192
|
*/
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
// Write the commented code back to the file
|
|
54
|
-
fs.writeFileSync(filepath, commentedCode);
|
|
55
|
-
|
|
56
|
-
// Log a success message with the file path
|
|
57
|
-
console.log(`Successfully commented ${filepath}`);
|
|
193
|
+
await processPath(filepath);
|
|
194
|
+
console.log("\n All done!");
|
|
58
195
|
})();
|
|
59
196
|
}
|
package/lib/config.js
CHANGED
|
@@ -1,36 +1,81 @@
|
|
|
1
|
-
const fs = require('fs');
|
|
2
|
-
const path = require('path');
|
|
3
|
-
const os = require('os');
|
|
4
|
-
const readline = require('readline');
|
|
5
|
-
const configPath = path.join(os.homedir(), '.
|
|
6
|
-
|
|
7
|
-
const rl=readline.createInterface({
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
1
|
+
const fs = require('fs');
|
|
2
|
+
const path = require('path');
|
|
3
|
+
const os = require('os');
|
|
4
|
+
const readline = require('readline');
|
|
5
|
+
const configPath = path.join(os.homedir(), '.devsplainrc');
|
|
6
|
+
|
|
7
|
+
const rl = readline.createInterface({
|
|
8
|
+
input: process.stdin,
|
|
9
|
+
output: process.stdout
|
|
10
|
+
});
|
|
11
|
+
|
|
12
|
+
/**
|
|
13
|
+
* Asks a question via the readline interface.
|
|
14
|
+
* @param {string} query
|
|
15
|
+
* @returns {Promise<string>}
|
|
16
|
+
*/
|
|
17
|
+
const askQuestion = (query) => new Promise((resolve) => rl.question(query, resolve));
|
|
18
|
+
|
|
19
|
+
/**
|
|
20
|
+
* Retrieves the configuration, creating a new one if it does not exist.
|
|
21
|
+
* @returns {Promise<Object>}
|
|
22
|
+
*/
|
|
23
|
+
async function getConfig() {
|
|
24
|
+
if (!fs.existsSync(configPath)) {
|
|
25
|
+
let baseUrl = "";
|
|
26
|
+
let model = "";
|
|
27
|
+
let provider = "";
|
|
28
|
+
|
|
29
|
+
console.log("Which AI Provider Do You want to use?");
|
|
30
|
+
console.log("1. Groq (Free, Fast, Llama-3)");
|
|
31
|
+
console.log("2. Gemini (Free Tier)");
|
|
32
|
+
console.log("3. OpenAI (Paid)");
|
|
33
|
+
console.log("4. Custom (Ollama, local, etc)");
|
|
34
|
+
|
|
35
|
+
const choice = await askQuestion("Select (1-4): ");
|
|
36
|
+
|
|
37
|
+
if (choice === '1') {
|
|
38
|
+
provider = 'groq';
|
|
39
|
+
model = 'llama-3.3-70b-versatile';
|
|
40
|
+
baseUrl = 'https://api.groq.com/openai';
|
|
41
|
+
console.log("\nGet your free Groq key here: https://console.groq.com/keys");
|
|
42
|
+
} else if (choice === '2') {
|
|
43
|
+
provider = 'gemini';
|
|
44
|
+
model = 'gemini-2.0-flash';
|
|
45
|
+
baseUrl = null;
|
|
46
|
+
console.log("\nGet your free Gemini key here: https://aistudio.google.com/apikey");
|
|
47
|
+
} else if (choice === '3') {
|
|
48
|
+
provider = 'openai';
|
|
49
|
+
model = 'gpt-4o';
|
|
50
|
+
baseUrl = 'https://api.openai.com';
|
|
51
|
+
console.log("\nGet your OpenAI key here: https://platform.openai.com/api-keys");
|
|
52
|
+
} else if (choice === '4') {
|
|
53
|
+
provider = 'custom';
|
|
54
|
+
model = await askQuestion("Model name (e.g., llama3): ");
|
|
55
|
+
baseUrl = await askQuestion("Base URL (e.g., http://localhost:11434): ");
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
const apiKey = await askQuestion("Paste your API key (leave blank for local models): ");
|
|
59
|
+
|
|
60
|
+
rl.close();
|
|
61
|
+
|
|
62
|
+
const config = {
|
|
63
|
+
provider: provider,
|
|
64
|
+
apiKey: apiKey,
|
|
65
|
+
model: model,
|
|
66
|
+
baseUrl: baseUrl
|
|
67
|
+
};
|
|
68
|
+
|
|
69
|
+
fs.writeFileSync(configPath, JSON.stringify(config, null, 2));
|
|
70
|
+
|
|
71
|
+
return config;
|
|
72
|
+
} else {
|
|
73
|
+
rl.close();
|
|
74
|
+
|
|
75
|
+
const rawData = fs.readFileSync(configPath, 'utf8');
|
|
76
|
+
|
|
77
|
+
return JSON.parse(rawData);
|
|
78
|
+
}
|
|
79
|
+
}
|
|
80
|
+
|
|
36
81
|
module.exports = { getConfig };
|
package/lib/llm.js
CHANGED
|
@@ -1,53 +1,140 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
1
|
+
/**
|
|
2
|
+
* This function generates commented code based on the given parameters.
|
|
3
|
+
* It sends a request to the specified AI provider with the provided code, language, and configuration.
|
|
4
|
+
* The function returns the commented code as a string.
|
|
5
|
+
*
|
|
6
|
+
* @param {string} code - The code that needs to be commented.
|
|
7
|
+
* @param {string} language - The programming language of the code.
|
|
8
|
+
* @param {object} config - The configuration object containing the provider and API details.
|
|
9
|
+
* @param {string} [mode='default'] - The mode of commenting. Default modes are 'default', 'light', 'full', and 'clean'.
|
|
10
|
+
* @returns {Promise<string>} The commented code.
|
|
11
|
+
*/
|
|
12
|
+
async function getComments(code, language, config, mode = 'default') {
|
|
13
|
+
// Initialize the instruction based on the mode
|
|
14
|
+
let instruction = "Add JSDoc/docstrings block above functions. Do NOT add inline comments unless the logic is extremely complex or highly non-obvious. Keep the code clean and readable.";
|
|
15
|
+
|
|
16
|
+
// Check if the mode is 'light' and update the instruction accordingly
|
|
17
|
+
if (mode === 'light') {
|
|
18
|
+
// In 'light' mode, only JSDoc comments are added above functions
|
|
19
|
+
instruction = "Add ONLY JSDoc/docstrings above functions. Do NOT add any inline comments inside the functions. Keep it extremely minimal.";
|
|
20
|
+
}
|
|
21
|
+
// Check if the mode is 'full' and update the instruction accordingly
|
|
22
|
+
else if (mode === 'full') {
|
|
23
|
+
// In 'full' mode, detailed JSDoc comments and inline comments are added
|
|
24
|
+
instruction = "Add highly detailed JSDoc/docstrings above functions, and add detailed inline comments explaining almost every single line of logic. Do NOT add comments inside string literals, template literals, or multiline strings.";
|
|
25
|
+
}
|
|
26
|
+
// Check if the mode is 'clean' and update the instruction accordingly
|
|
27
|
+
else if (mode === 'clean') {
|
|
28
|
+
// In 'clean' mode, all comments are removed from the code
|
|
29
|
+
instruction = "Remove ALL comments (both block/JSDoc and inline comments) from this code. Return only the raw, uncommented code. Do NOT alter the code logic or formatting.";
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
// Initialize the prompt based on the mode
|
|
33
|
+
let prompt = "";
|
|
34
|
+
// Check if the mode is 'clean' and update the prompt accordingly
|
|
35
|
+
if (mode === 'clean') {
|
|
36
|
+
// In 'clean' mode, the prompt asks to remove all comments
|
|
37
|
+
prompt = `
|
|
38
|
+
Remove ALL comments (both block/JSDoc and inline comments) from the following ${language} code.
|
|
39
|
+
Return ONLY the raw, uncommented code. NO MARKDOWN. NO EXPLANATION. NO PERSONAL MESSAGE.
|
|
40
|
+
Do NOT alter the code logic or formatting in any way.
|
|
41
|
+
${code}
|
|
42
|
+
`;
|
|
43
|
+
} else {
|
|
44
|
+
// In other modes, the prompt asks to add comments to the code
|
|
45
|
+
prompt = `
|
|
46
|
+
Add comments to the code in this file (${language}). ${instruction}
|
|
47
|
+
If the code already contains comments, completely REPLACE them with your own. Do not leave duplicate or messy comments behind.
|
|
48
|
+
Return ONLY the commented code. NO MARKDOWN. NO EXPLANATION. NO PERSONAL MESSAGE.
|
|
49
|
+
Do NOT alter, refactor, or format the existing code in any way. Only add comments. IF ANYTHING WRONG HIGHLIGHT IN COMMENT BUT DO NOT CHANGE
|
|
50
|
+
${code}
|
|
51
|
+
`;
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
// Check if the provider is 'gemini' and proceed accordingly
|
|
55
|
+
if (config.provider === 'gemini') {
|
|
56
|
+
// Construct the URL for the API request
|
|
57
|
+
const url = `https://generativelanguage.googleapis.com/v1beta/models/${config.model}:generateContent?key=${config.apiKey}`;
|
|
58
|
+
let data;
|
|
59
|
+
|
|
60
|
+
try {
|
|
61
|
+
// Send a POST request to the API with the prompt
|
|
62
|
+
const response = await fetch(url, {
|
|
63
|
+
method: 'POST',
|
|
64
|
+
headers: {
|
|
65
|
+
'Content-Type': 'application/json'
|
|
66
|
+
},
|
|
67
|
+
body: JSON.stringify({
|
|
68
|
+
"contents": [{ "parts": [{ "text": prompt }] }]
|
|
69
|
+
})
|
|
70
|
+
});
|
|
71
|
+
// Parse the response as JSON
|
|
72
|
+
data = await response.json();
|
|
73
|
+
} catch (error) {
|
|
74
|
+
// Log any network errors and exit the process
|
|
75
|
+
console.log("\n Network Error: Could not connect to the AI provider. Check your internet or API url.");
|
|
76
|
+
process.exit(1);
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
// Check if there is an error in the API response
|
|
80
|
+
if (data.error) {
|
|
81
|
+
// Log the API error and exit the process
|
|
82
|
+
console.error("\n API Error:", data.error.message);
|
|
83
|
+
process.exit(1);
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
// Extract the generated text from the API response
|
|
87
|
+
let text = data.candidates[0].content.parts[0].text;
|
|
88
|
+
// Remove any unnecessary code blocks and trim the text
|
|
89
|
+
text = text.replace(/^```[\w]*\n/m, '').replace(/```$/m, '').trim();
|
|
90
|
+
// Return the generated text
|
|
91
|
+
return text;
|
|
92
|
+
}
|
|
93
|
+
// If the provider is not 'gemini', proceed with the default API request
|
|
94
|
+
else {
|
|
95
|
+
// Construct the URL for the API request
|
|
96
|
+
const url = `${config.baseUrl}/v1/chat/completions`;
|
|
97
|
+
let data;
|
|
98
|
+
|
|
99
|
+
try {
|
|
100
|
+
// Send a POST request to the API with the prompt
|
|
101
|
+
const response = await fetch(url, {
|
|
102
|
+
method: 'POST',
|
|
103
|
+
headers: {
|
|
104
|
+
'Content-Type': 'application/json',
|
|
105
|
+
'Authorization': `Bearer ${config.apiKey}`
|
|
106
|
+
},
|
|
107
|
+
body: JSON.stringify({
|
|
108
|
+
"model": config.model,
|
|
109
|
+
"messages": [{
|
|
110
|
+
"role": "user",
|
|
111
|
+
"content": prompt
|
|
112
|
+
}]
|
|
113
|
+
})
|
|
114
|
+
});
|
|
115
|
+
// Parse the response as JSON
|
|
116
|
+
data = await response.json();
|
|
117
|
+
} catch (error) {
|
|
118
|
+
// Log any network errors and exit the process
|
|
119
|
+
console.log("\n Network Error: Could not connect to the AI provider. Check your internet or API url.");
|
|
120
|
+
process.exit(1);
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
// Check if there is an error in the API response
|
|
124
|
+
if (data.error) {
|
|
125
|
+
// Log the API error and exit the process
|
|
126
|
+
console.error("\n API Error:", data.error.message);
|
|
127
|
+
process.exit(1);
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
// Extract the generated text from the API response
|
|
131
|
+
let text = data.choices[0].message.content;
|
|
132
|
+
// Remove any unnecessary code blocks and trim the text
|
|
133
|
+
text = text.replace(/^```[\w]*\n/m, '').replace(/```$/m, '').trim();
|
|
134
|
+
// Return the generated text
|
|
135
|
+
return text;
|
|
136
|
+
}
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
// Export the getComments function as a module
|
|
140
|
+
module.exports = { getComments };
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "devsplain",
|
|
3
|
-
"version": "1.
|
|
3
|
+
"version": "1.2.0",
|
|
4
4
|
"description": "An agent-agnostic CLI tool that automatically adds JSDoc and inline comments to your code using free LLMs.",
|
|
5
5
|
"author": "mwahaj36",
|
|
6
6
|
"license": "MIT",
|
|
@@ -28,13 +28,16 @@
|
|
|
28
28
|
],
|
|
29
29
|
"repository": {
|
|
30
30
|
"type": "git",
|
|
31
|
-
"url": "git+https://github.com/mwahaj36/
|
|
31
|
+
"url": "git+https://github.com/mwahaj36/devsplain.git"
|
|
32
32
|
},
|
|
33
33
|
"bugs": {
|
|
34
|
-
"url": "https://github.com/mwahaj36/
|
|
34
|
+
"url": "https://github.com/mwahaj36/devsplain/issues"
|
|
35
35
|
},
|
|
36
|
-
"homepage": "https://github.com/mwahaj36/
|
|
36
|
+
"homepage": "https://github.com/mwahaj36/devsplain#readme",
|
|
37
37
|
"scripts": {
|
|
38
|
-
"test": "
|
|
38
|
+
"test": "jest"
|
|
39
|
+
},
|
|
40
|
+
"devDependencies": {
|
|
41
|
+
"jest": "^30.4.2"
|
|
39
42
|
}
|
|
40
43
|
}
|