devsplain 1.1.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 CHANGED
@@ -1,5 +1,7 @@
1
1
  # devsplain
2
2
 
3
+ ![devsplain demo](sample.gif)
4
+
3
5
  An agent-agnostic CLI tool that automatically adds JSDoc and inline comments to your code using free LLMs.
4
6
 
5
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.
@@ -20,27 +22,42 @@ Simply point `devsplain` at any file you want to comment:
20
22
  devsplain src/utils.js
21
23
  ```
22
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
+
23
35
  ### Modes
24
36
 
25
37
  You can control exactly how aggressive the AI is with its comments using flags:
26
38
 
27
39
  - `--light`: Only adds JSDoc blocks above functions. Keeps the inside of your functions completely untouched.
28
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!
29
43
  - **(Default)**: A balanced mix of JSDoc headers and sparse inline comments for complex logic.
30
44
 
31
- Example:
45
+ **Usage Examples:**
32
46
 
33
47
  ```bash
34
48
  devsplain src/utils.js --light
49
+ devsplain src/ --full
50
+ devsplain legacy_code.js --clean
51
+ devsplain lib/ --dry-run
35
52
  ```
36
53
 
37
- ### Samples in this Repository
54
+ ### Supported Languages
38
55
 
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):
56
+ Because `devsplain` uses LLMs, it natively understands almost every language syntax. It currently processes the following extensions:
40
57
 
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.
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`
44
61
 
45
62
  ## Agent Agnostic (Bring Your Own LLM)
46
63
 
@@ -53,6 +70,12 @@ To see exactly what `devsplain` can do, the source code of this very repository
53
70
 
54
71
  _(Your configuration is safely stored in `~/.devsplainrc` on your machine)._
55
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
+
56
79
  ## License
57
80
 
58
81
  MIT
package/bin/cli.js CHANGED
@@ -2,49 +2,55 @@
2
2
 
3
3
  /**
4
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.
5
+ * @module llm
6
+ * @module config
7
+ * @module fs
8
+ * @module path
9
+ * @module readline
8
10
  */
9
11
  const { getComments } = require('../lib/llm.js');
10
12
  const { getConfig } = require('../lib/config.js');
11
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 });
12
17
 
13
18
  /**
14
- * Get the filepath from the command line arguments.
15
- * @type {string}
16
- */
17
- const filepath = process.argv[2];
18
-
19
- /**
20
- * Get additional command line arguments.
21
- * @type {array}
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
22
  */
23
- const args = process.argv.slice(3);
23
+ const askQuestion = (query) => new Promise((resolve) => rl.question(query, resolve));
24
24
 
25
25
  /**
26
- * Set the default mode.
26
+ * Get the filepath and arguments from the command line.
27
27
  * @type {string}
28
28
  */
29
+ const filepath = process.argv[2];
30
+ const args = process.argv.slice(3);
29
31
  let mode = 'default';
30
32
 
31
33
  /**
32
- * Check for mode flags in the command line arguments and update the mode accordingly.
34
+ * Determine the mode based on the command line arguments.
35
+ * Supported modes are: 'light', 'full', and 'clean'.
33
36
  */
34
37
  if (args.includes('--light')) mode = 'light';
35
38
  if (args.includes('--full')) mode = 'full';
39
+ if (args.includes('--clean')) mode = 'clean';
40
+ const isDryRun = args.includes('--dry-run');
36
41
 
37
42
  /**
38
- * Check if a filepath was provided.
43
+ * Check if a filepath was provided, if not display usage information.
39
44
  */
40
45
  if (!filepath) {
41
- console.log("usage: devsplain <file>");
46
+ console.log("usage: devsplain <file-or-directory>");
42
47
  process.exit(1);
43
- } else {
44
- /**
45
- * Main execution block.
46
- * @async
47
- */
48
+ }
49
+ else if (!fs.existsSync(filepath)) {
50
+ console.log(`Error: The path '${filepath}' does not exist.`);
51
+ process.exit(1);
52
+ }
53
+ else {
48
54
  (async () => {
49
55
  /**
50
56
  * Get the configuration.
@@ -53,25 +59,138 @@ if (!filepath) {
53
59
  const config = await getConfig();
54
60
 
55
61
  /**
56
- * Read the file at the specified filepath.
57
- * @type {string}
62
+ * Process a path, either a file or directory.
63
+ * @param {string} targetPath - The path to process.
64
+ * @returns {Promise<void>}
58
65
  */
59
- const data = fs.readFileSync(filepath, 'utf-8');
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);
60
72
 
61
- console.log("Analyzing File...");
62
- console.log(`Analyzing File in ${mode} mode...`);
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
+ ];
63
88
 
64
- /**
65
- * Get comments for the code in the file.
66
- * @type {string}
67
- */
68
- const commentedCode = await getComments(data, 'javascript', config, mode);
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
+ }
69
189
 
70
190
  /**
71
- * Write the commented code back to the file.
191
+ * Start processing the provided filepath.
72
192
  */
73
- fs.writeFileSync(filepath, commentedCode);
74
-
75
- console.log(`Successfully commented ${filepath}`);
193
+ await processPath(filepath);
194
+ console.log("\n All done!");
76
195
  })();
77
196
  }
package/lib/config.js CHANGED
@@ -8,61 +8,72 @@ const rl = readline.createInterface({
8
8
  input: process.stdin,
9
9
  output: process.stdout
10
10
  });
11
- const askQuestion = (query) => new Promise((resolve) => rl.question(query, resolve))
12
11
 
13
12
  /**
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.
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>}
17
22
  */
18
23
  async function getConfig() {
19
24
  if (!fs.existsSync(configPath)) {
20
25
  let baseUrl = "";
21
26
  let model = "";
22
27
  let provider = "";
28
+
23
29
  console.log("Which AI Provider Do You want to use?");
24
30
  console.log("1. Groq (Free, Fast, Llama-3)");
25
31
  console.log("2. Gemini (Free Tier)");
26
32
  console.log("3. OpenAI (Paid)");
27
33
  console.log("4. Custom (Ollama, local, etc)");
34
+
28
35
  const choice = await askQuestion("Select (1-4): ");
36
+
29
37
  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') {
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') {
36
43
  provider = 'gemini';
37
44
  model = 'gemini-2.0-flash';
38
45
  baseUrl = null;
39
- console.log("\nGet your free Gemini key here: https://aistudio.google.com/apikey")
40
- }
41
- else if (choice === '3') {
46
+ console.log("\nGet your free Gemini key here: https://aistudio.google.com/apikey");
47
+ } else if (choice === '3') {
42
48
  provider = 'openai';
43
49
  model = 'gpt-4o';
44
50
  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') {
51
+ console.log("\nGet your OpenAI key here: https://platform.openai.com/api-keys");
52
+ } else if (choice === '4') {
48
53
  provider = 'custom';
49
54
  model = await askQuestion("Model name (e.g., llama3): ");
50
55
  baseUrl = await askQuestion("Base URL (e.g., http://localhost:11434): ");
51
56
  }
57
+
52
58
  const apiKey = await askQuestion("Paste your API key (leave blank for local models): ");
53
- rl.close()
59
+
60
+ rl.close();
61
+
54
62
  const config = {
55
63
  provider: provider,
56
64
  apiKey: apiKey,
57
65
  model: model,
58
66
  baseUrl: baseUrl
59
67
  };
60
- fs.writeFileSync(configPath, JSON.stringify(config, null, 2))
61
- return config
62
- }
63
- else {
68
+
69
+ fs.writeFileSync(configPath, JSON.stringify(config, null, 2));
70
+
71
+ return config;
72
+ } else {
64
73
  rl.close();
74
+
65
75
  const rawData = fs.readFileSync(configPath, 'utf8');
76
+
66
77
  return JSON.parse(rawData);
67
78
  }
68
79
  }
package/lib/llm.js CHANGED
@@ -1,97 +1,140 @@
1
1
  /**
2
- * Asynchronously retrieves comments for a given piece of code.
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.
3
5
  *
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.
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.
9
11
  */
10
12
  async function getComments(code, language, config, mode = 'default') {
11
- // First, we need to determine the instruction based on the mode
13
+ // Initialize the instruction based on the mode
12
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.";
13
- // If the mode is 'light', we update the instruction accordingly
15
+
16
+ // Check if the mode is 'light' and update the instruction accordingly
14
17
  if (mode === 'light') {
18
+ // In 'light' mode, only JSDoc comments are added above functions
15
19
  instruction = "Add ONLY JSDoc/docstrings above functions. Do NOT add any inline comments inside the functions. Keep it extremely minimal.";
16
20
  }
17
- // If the mode is 'full', we update the instruction to include detailed inline comments
21
+ // Check if the mode is 'full' and update the instruction accordingly
18
22
  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.";
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.";
20
30
  }
21
31
 
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
- `;
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
+ }
30
53
 
31
- // Next, we check the provider in the config to determine which API to use
54
+ // Check if the provider is 'gemini' and proceed accordingly
32
55
  if (config.provider === 'gemini') {
33
- // If the provider is 'gemini', we use the Google API
56
+ // Construct the URL for the API request
34
57
  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
- });
58
+ let data;
45
59
 
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);
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
+ }
50
78
 
51
- // If there's an error in the response, we log the error and exit the process
79
+ // Check if there is an error in the API response
52
80
  if (data.error) {
81
+ // Log the API error and exit the process
53
82
  console.error("\n API Error:", data.error.message);
54
83
  process.exit(1);
55
84
  }
56
85
 
57
- // We extract the commented code from the response
86
+ // Extract the generated text from the API response
58
87
  let text = data.candidates[0].content.parts[0].text;
59
- // We remove any unnecessary characters from the commented code
88
+ // Remove any unnecessary code blocks and trim the text
60
89
  text = text.replace(/^```[\w]*\n/m, '').replace(/```$/m, '').trim();
61
- // Finally, we return the commented code
90
+ // Return the generated text
62
91
  return text;
63
92
  }
64
- // If the provider is not 'gemini', we use a different API
93
+ // If the provider is not 'gemini', proceed with the default API request
65
94
  else {
95
+ // Construct the URL for the API request
66
96
  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);
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
+ }
80
122
 
81
- // If there's an error in the response, we log the error and exit the process
123
+ // Check if there is an error in the API response
82
124
  if (data.error) {
125
+ // Log the API error and exit the process
83
126
  console.error("\n API Error:", data.error.message);
84
127
  process.exit(1);
85
128
  }
86
129
 
87
- // We extract the commented code from the response
130
+ // Extract the generated text from the API response
88
131
  let text = data.choices[0].message.content;
89
- // We remove any unnecessary characters from the commented code
132
+ // Remove any unnecessary code blocks and trim the text
90
133
  text = text.replace(/^```[\w]*\n/m, '').replace(/```$/m, '').trim();
91
- // Finally, we return the commented code
134
+ // Return the generated text
92
135
  return text;
93
136
  }
94
137
  }
95
138
 
96
- // We export the getComments function
139
+ // Export the getComments function as a module
97
140
  module.exports = { getComments };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "devsplain",
3
- "version": "1.1.0",
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,11 +28,16 @@
28
28
  ],
29
29
  "repository": {
30
30
  "type": "git",
31
- "url": "git+https://github.com/mwahaj36/devsplain.git"},
31
+ "url": "git+https://github.com/mwahaj36/devsplain.git"
32
+ },
32
33
  "bugs": {
33
- "url": "https://github.com/mwahaj36/devsplain/issues" },
34
- "homepage": "https://github.com/mwahaj36/devsplain#readme",
34
+ "url": "https://github.com/mwahaj36/devsplain/issues"
35
+ },
36
+ "homepage": "https://github.com/mwahaj36/devsplain#readme",
35
37
  "scripts": {
36
- "test": "echo \"Error: no test specified\" && exit 1"
38
+ "test": "jest"
39
+ },
40
+ "devDependencies": {
41
+ "jest": "^30.4.2"
37
42
  }
38
43
  }