devsplain 1.0.0 → 1.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md ADDED
@@ -0,0 +1,58 @@
1
+ # devsplain
2
+
3
+ An agent-agnostic CLI tool that automatically adds JSDoc and inline comments to your code using free LLMs.
4
+
5
+ Tired of writing documentation? Let AI do the explaining for you. `devsplain` reads your code and intelligently injects standard JSDoc headers and inline comments.
6
+
7
+ ## Installation
8
+
9
+ Install it globally via npm:
10
+
11
+ ```bash
12
+ npm install -g devsplain
13
+ ```
14
+
15
+ ## Usage
16
+
17
+ Simply point `devsplain` at any file you want to comment:
18
+
19
+ ```bash
20
+ devsplain src/utils.js
21
+ ```
22
+
23
+ ### Modes
24
+
25
+ You can control exactly how aggressive the AI is with its comments using flags:
26
+
27
+ - `--light`: Only adds JSDoc blocks above functions. Keeps the inside of your functions completely untouched.
28
+ - `--full`: Highly aggressive. Explains complex logic line-by-line inside your functions.
29
+ - **(Default)**: A balanced mix of JSDoc headers and sparse inline comments for complex logic.
30
+
31
+ Example:
32
+
33
+ ```bash
34
+ devsplain src/utils.js --light
35
+ ```
36
+
37
+ ### Samples in this Repository
38
+
39
+ To see exactly what `devsplain` can do, the source code of this very repository was commented using the tool itself (using the Groq provider):
40
+
41
+ - `bin/cli.js`: Commented using the **Default** mode.
42
+ - `lib/config.js`: Commented using the **--light** mode.
43
+ - `lib/llm.js`: Commented using the **--full** mode.
44
+
45
+ ## Agent Agnostic (Bring Your Own LLM)
46
+
47
+ `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:
48
+
49
+ 1. **Groq** (Recommended) - Instant, free Llama-3 endpoints.
50
+ 2. **Gemini** - Google's free tier endpoints.
51
+ 3. **OpenAI** - Standard paid ChatGPT models.
52
+ 4. **Custom** - Point it at ANY OpenAI-compatible endpoint (e.g., local models via Ollama or LMStudio).
53
+
54
+ _(Your configuration is safely stored in `~/.devsplainrc` on your machine)._
55
+
56
+ ## License
57
+
58
+ MIT
package/bin/cli.js CHANGED
@@ -1,59 +1,77 @@
1
1
  #!/usr/bin/env node
2
+
2
3
  /**
3
- * Requires the necessary modules for the script to run.
4
- * @requires ../lib/llm.js - provides the getComments function
5
- * @requires ../lib/config.js - provides the getConfig function
6
- * @requires fs - provides file system functionality
4
+ * Import required modules.
5
+ * @module llm - Provides functionality for getting comments.
6
+ * @module config - Provides functionality for getting configuration.
7
+ * @module fs - Provides functionality for interacting with the file system.
7
8
  */
8
- const { getComments } = require('../lib/llm.js')
9
- const { getConfig } = require('../lib/config.js')
9
+ const { getComments } = require('../lib/llm.js');
10
+ const { getConfig } = require('../lib/config.js');
10
11
  const fs = require('fs');
11
12
 
12
13
  /**
13
- * Retrieves the file path from the command line arguments.
14
+ * Get the filepath from the command line arguments.
14
15
  * @type {string}
15
16
  */
16
- const filepath = process.argv[2]
17
+ const filepath = process.argv[2];
18
+
19
+ /**
20
+ * Get additional command line arguments.
21
+ * @type {array}
22
+ */
23
+ const args = process.argv.slice(3);
24
+
25
+ /**
26
+ * Set the default mode.
27
+ * @type {string}
28
+ */
29
+ let mode = 'default';
30
+
31
+ /**
32
+ * Check for mode flags in the command line arguments and update the mode accordingly.
33
+ */
34
+ if (args.includes('--light')) mode = 'light';
35
+ if (args.includes('--full')) mode = 'full';
17
36
 
18
37
  /**
19
- * Checks if a file path was provided.
20
- * If not, it logs the usage message and exits the process.
38
+ * Check if a filepath was provided.
21
39
  */
22
40
  if (!filepath) {
23
- console.log("usage: commie <file>")
24
- process.exit(1)
25
- }
26
- else {
41
+ console.log("usage: devsplain <file>");
42
+ process.exit(1);
43
+ } else {
27
44
  /**
28
- * Defines an asynchronous function to comment the code in the provided file.
45
+ * Main execution block.
29
46
  * @async
30
47
  */
31
48
  (async () => {
32
49
  /**
33
- * Retrieves the configuration settings.
50
+ * Get the configuration.
34
51
  * @type {object}
35
52
  */
36
53
  const config = await getConfig();
37
-
54
+
38
55
  /**
39
- * Reads the contents of the file at the provided file path.
56
+ * Read the file at the specified filepath.
40
57
  * @type {string}
41
58
  */
42
59
  const data = fs.readFileSync(filepath, 'utf-8');
43
-
44
- // Log a message to indicate that the file is being analyzed
60
+
45
61
  console.log("Analyzing File...");
46
-
62
+ console.log(`Analyzing File in ${mode} mode...`);
63
+
47
64
  /**
48
- * Retrieves the commented code using the getComments function.
65
+ * Get comments for the code in the file.
49
66
  * @type {string}
50
67
  */
51
- const commentedCode = await getComments(data, 'javascript', config)
52
-
53
- // Write the commented code back to the file
68
+ const commentedCode = await getComments(data, 'javascript', config, mode);
69
+
70
+ /**
71
+ * Write the commented code back to the file.
72
+ */
54
73
  fs.writeFileSync(filepath, commentedCode);
55
-
56
- // Log a success message with the file path
74
+
57
75
  console.log(`Successfully commented ${filepath}`);
58
76
  })();
59
77
  }
package/lib/config.js CHANGED
@@ -1,36 +1,70 @@
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(), '.commierc');
6
-
7
- const rl=readline.createInterface({
8
- input:process.stdin,
9
- output:process.stdout
10
- });
11
- const askQuestion=(query)=>new Promise((resolve)=>rl.question(query,resolve))
12
-
13
-
14
-
15
- async function getConfig(){
16
- if(!fs.existsSync(configPath)){
17
- console.log("Please Enter your api key");
18
- const answer=await askQuestion("Paste it here: ");
19
- rl.close()
20
- const config = {
21
- provider: 'groq',
22
- apiKey: answer,
23
- model: 'llama-3.3-70b-versatile',
24
- baseUrl: 'https://api.groq.com/openai'
25
- };
26
- fs.writeFileSync(configPath,JSON.stringify(config, null,2))
27
- return config
28
- }
29
- else {
30
- rl.close();
31
- const rawData = fs.readFileSync(configPath, 'utf8');
32
- return JSON.parse(rawData);
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
+ const askQuestion = (query) => new Promise((resolve) => rl.question(query, resolve))
12
+
13
+ /**
14
+ * Retrieves the configuration for the application.
15
+ * If the configuration file does not exist, it will prompt the user to create one.
16
+ * @returns {Promise<Object>} The configuration object.
17
+ */
18
+ async function getConfig() {
19
+ if (!fs.existsSync(configPath)) {
20
+ let baseUrl = "";
21
+ let model = "";
22
+ let provider = "";
23
+ console.log("Which AI Provider Do You want to use?");
24
+ console.log("1. Groq (Free, Fast, Llama-3)");
25
+ console.log("2. Gemini (Free Tier)");
26
+ console.log("3. OpenAI (Paid)");
27
+ console.log("4. Custom (Ollama, local, etc)");
28
+ const choice = await askQuestion("Select (1-4): ");
29
+ if (choice === '1') {
30
+ provider = 'groq',
31
+ model = 'llama-3.3-70b-versatile',
32
+ baseUrl = 'https://api.groq.com/openai',
33
+ console.log("\nGet your free Groq key here: https://console.groq.com/keys")
34
+ }
35
+ else if (choice === '2') {
36
+ provider = 'gemini';
37
+ model = 'gemini-2.0-flash';
38
+ baseUrl = null;
39
+ console.log("\nGet your free Gemini key here: https://aistudio.google.com/apikey")
40
+ }
41
+ else if (choice === '3') {
42
+ provider = 'openai';
43
+ model = 'gpt-4o';
44
+ baseUrl = 'https://api.openai.com';
45
+ console.log("\nGet your OpenAI key here: https://platform.openai.com/api-keys")
46
+ }
47
+ else if (choice === '4') {
48
+ provider = 'custom';
49
+ model = await askQuestion("Model name (e.g., llama3): ");
50
+ baseUrl = await askQuestion("Base URL (e.g., http://localhost:11434): ");
51
+ }
52
+ const apiKey = await askQuestion("Paste your API key (leave blank for local models): ");
53
+ rl.close()
54
+ const config = {
55
+ provider: provider,
56
+ apiKey: apiKey,
57
+ model: model,
58
+ baseUrl: baseUrl
59
+ };
60
+ fs.writeFileSync(configPath, JSON.stringify(config, null, 2))
61
+ return config
62
+ }
63
+ else {
64
+ rl.close();
65
+ const rawData = fs.readFileSync(configPath, 'utf8');
66
+ return JSON.parse(rawData);
67
+ }
68
+ }
69
+
36
70
  module.exports = { getConfig };
package/lib/llm.js CHANGED
@@ -1,53 +1,97 @@
1
- async function getComments(code,language,config){
2
- const prompt=
3
- `
4
- Add comments to this ${language} code. add JSDoc/docstrings block above functions and brief inline comments for complex logic. Return ONLY the commented code. NO MARKDOWN. NO EXPLANATION. NO PERSONAL MESSAGE.
5
- ${code}
6
- `
7
-
8
- if(config.provider==='gemini'){
9
- const url=`https://generativelanguage.googleapis.com/v1beta/models/${config.model}:generateContent?key=${config.apiKey}`;
10
- const response = await fetch(url,
11
- {
12
- method:'POST',
13
- headers:{'Content-Type':'application/json'},
14
- body:JSON.stringify({
15
- "contents":[{"parts":[{"text":prompt}]}]
16
- })
17
- }
18
- );
19
- const data=await response.json();
20
- console.log("DEBUG API RESPONSE:", data);
21
- if (data.error) {
22
- console.error("\n API Error:", data.error.message);
23
- process.exit(1);
24
- }
25
- let text = data.candidates[0].content.parts[0].text;
26
- text = text.replace(/^```[\w]*\n/m, '').replace(/```$/m, '').trim();
27
- return text;
28
- }
29
- else{
30
- const url=`${config.baseUrl}/v1/chat/completions`;
31
- const response=await fetch(url,{
32
- method:'POST',
33
- headers:{'Content-Type':'application/json','Authorization':`Bearer ${config.apiKey}`},
34
- body:JSON.stringify({ "model": config.model, "messages": [{ "role": "user", "content": prompt }]
35
- })
36
-
37
- })
38
- const data = await response.json();
39
- console.log("DEBUG API RESPONSE:", data);
40
- if (data.error) {
41
- console.error("\n API Error:", data.error.message);
42
- process.exit(1);
43
- }
44
- let text = data.choices[0].message.content;
45
- text = text.replace(/^```[\w]*\n/m, '').replace(/```$/m, '').trim();
46
- return text;
47
-
48
-
49
- }
50
-
51
- }
52
-
53
- module.exports={getComments};
1
+ /**
2
+ * Asynchronously retrieves comments for a given piece of code.
3
+ *
4
+ * @param {string} code The code for which to retrieve comments.
5
+ * @param {string} language The programming language of the code.
6
+ * @param {object} config An object containing configuration settings for the API request.
7
+ * @param {string} [mode='default'] The mode in which to retrieve comments. Can be 'light', 'full', or 'default'.
8
+ * @returns {Promise<string>} A promise that resolves to the commented code.
9
+ */
10
+ async function getComments(code, language, config, mode = 'default') {
11
+ // First, we need to determine the instruction based on the mode
12
+ 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.";
13
+ // If the mode is 'light', we update the instruction accordingly
14
+ if (mode === 'light') {
15
+ instruction = "Add ONLY JSDoc/docstrings above functions. Do NOT add any inline comments inside the functions. Keep it extremely minimal.";
16
+ }
17
+ // If the mode is 'full', we update the instruction to include detailed inline comments
18
+ else if (mode === 'full') {
19
+ instruction = "Add highly detailed JSDoc/docstrings above functions, and add detailed inline comments explaining almost every single line of logic.";
20
+ }
21
+
22
+ // Now, we create a prompt based on the language and instruction
23
+ const prompt = `
24
+ // This is a prompt to add comments to the provided code
25
+ Add comments to this ${language} code. ${instruction}
26
+ // The commented code should be returned without any markdown, explanations, or personal messages
27
+ Return ONLY the commented code. NO MARKDOWN. NO EXPLANATION. NO PERSONAL MESSAGE.
28
+ ${code}
29
+ `;
30
+
31
+ // Next, we check the provider in the config to determine which API to use
32
+ if (config.provider === 'gemini') {
33
+ // If the provider is 'gemini', we use the Google API
34
+ const url = `https://generativelanguage.googleapis.com/v1beta/models/${config.model}:generateContent?key=${config.apiKey}`;
35
+ // We make a POST request to the API with the prompt
36
+ const response = await fetch(url, {
37
+ method: 'POST',
38
+ // We set the content type to application/json
39
+ headers: { 'Content-Type': 'application/json' },
40
+ // We stringified the prompt as JSON
41
+ body: JSON.stringify({
42
+ "contents": [{ "parts": [{ "text": prompt }] }]
43
+ })
44
+ });
45
+
46
+ // We parse the response as JSON
47
+ const data = await response.json();
48
+ // For debugging purposes, we log the API response
49
+ console.log("DEBUG API RESPONSE:", data);
50
+
51
+ // If there's an error in the response, we log the error and exit the process
52
+ if (data.error) {
53
+ console.error("\n API Error:", data.error.message);
54
+ process.exit(1);
55
+ }
56
+
57
+ // We extract the commented code from the response
58
+ let text = data.candidates[0].content.parts[0].text;
59
+ // We remove any unnecessary characters from the commented code
60
+ text = text.replace(/^```[\w]*\n/m, '').replace(/```$/m, '').trim();
61
+ // Finally, we return the commented code
62
+ return text;
63
+ }
64
+ // If the provider is not 'gemini', we use a different API
65
+ else {
66
+ const url = `${config.baseUrl}/v1/chat/completions`;
67
+ // We make a POST request to the API with the prompt
68
+ const response = await fetch(url, {
69
+ method: 'POST',
70
+ // We set the content type to application/json and include the API key in the authorization header
71
+ headers: { 'Content-Type': 'application/json', 'Authorization': `Bearer ${config.apiKey}` },
72
+ // We stringified the prompt as JSON
73
+ body: JSON.stringify({ "model": config.model, "messages": [{ "role": "user", "content": prompt }] })
74
+ });
75
+
76
+ // We parse the response as JSON
77
+ const data = await response.json();
78
+ // For debugging purposes, we log the API response
79
+ console.log("DEBUG API RESPONSE:", data);
80
+
81
+ // If there's an error in the response, we log the error and exit the process
82
+ if (data.error) {
83
+ console.error("\n API Error:", data.error.message);
84
+ process.exit(1);
85
+ }
86
+
87
+ // We extract the commented code from the response
88
+ let text = data.choices[0].message.content;
89
+ // We remove any unnecessary characters from the commented code
90
+ text = text.replace(/^```[\w]*\n/m, '').replace(/```$/m, '').trim();
91
+ // Finally, we return the commented code
92
+ return text;
93
+ }
94
+ }
95
+
96
+ // We export the getComments function
97
+ module.exports = { getComments };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "devsplain",
3
- "version": "1.0.0",
3
+ "version": "1.1.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,12 +28,10 @@
28
28
  ],
29
29
  "repository": {
30
30
  "type": "git",
31
- "url": "git+https://github.com/mwahaj36/commie.git"
32
- },
31
+ "url": "git+https://github.com/mwahaj36/devsplain.git"},
33
32
  "bugs": {
34
- "url": "https://github.com/mwahaj36/commie/issues"
35
- },
36
- "homepage": "https://github.com/mwahaj36/commie#readme",
33
+ "url": "https://github.com/mwahaj36/devsplain/issues" },
34
+ "homepage": "https://github.com/mwahaj36/devsplain#readme",
37
35
  "scripts": {
38
36
  "test": "echo \"Error: no test specified\" && exit 1"
39
37
  }