devsplain 1.7.1 → 1.8.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
@@ -83,6 +83,9 @@ devsplain --config
83
83
 
84
84
  Your settings are stored securely in `~/.devsplainrc` (configured with `chmod 600` on POSIX systems to restrict read access).
85
85
 
86
+ > [!NOTE]
87
+ > **API Testing Notice:** `devsplain` supports OpenAI, Anthropic, Ollama, Groq, and Gemini endpoints, but the E2E test suite has currently only been aggressively verified against the **Groq** and **Gemini** APIs. If you encounter any unexpected parsing issues or edge-case errors with other providers, please open an issue or submit a PR!
88
+
86
89
  ---
87
90
 
88
91
  ## CLI Usage & Options
package/bin/cli.js CHANGED
@@ -566,10 +566,10 @@ Options:
566
566
  if (cliProvider) {
567
567
  config.provider = cliProvider;
568
568
  if (!cliModel) {
569
- config.model = cliProvider === 'gemini' ? 'gemini-2.0-flash' : 'llama-3.3-70b-versatile';
569
+ config.model = cliProvider === 'gemini' ? 'gemini-2.0-flash' : (cliProvider === 'claude' ? 'claude-3-5-sonnet-20240620' : 'llama-3.3-70b-versatile');
570
570
  }
571
571
  if (!cliBaseUrl) {
572
- config.baseUrl = cliProvider === 'gemini' ? null : (cliProvider === 'groq' ? 'https://api.groq.com/openai' : (cliProvider === 'openai' ? 'https://api.openai.com' : ''));
572
+ config.baseUrl = cliProvider === 'gemini' ? null : (cliProvider === 'groq' ? 'https://api.groq.com/openai' : (cliProvider === 'openai' ? 'https://api.openai.com' : (cliProvider === 'claude' ? 'https://api.anthropic.com' : '')));
573
573
  }
574
574
  }
575
575
  if (cliModel) config.model = cliModel;
@@ -5,7 +5,13 @@ const { spliceComments } = require('./cli');
5
5
 
6
6
  /** Main execution block [ds] */
7
7
  try {
8
- // Get the current git directory [ds]
8
+ // Allow users to completely bypass the AI hook using an environment variable [ds]
9
+ if (process.env.SKIP_DEVSPLAIN) {
10
+ console.log('[devsplain] SKIP_DEVSPLAIN is set. Bypassing AI generation.');
11
+ process.exit(0);
12
+ }
13
+
14
+ // Prevent the hook from firing during rebases, merges, or cherry-picks [ds]
9
15
  const gitDir = execSync('git rev-parse --git-dir', { encoding: 'utf8' }).trim();
10
16
  const isRebasing = fs.existsSync(path.join(gitDir, 'rebase-merge')) || fs.existsSync(path.join(gitDir, 'rebase-apply'));
11
17
  const isMerging = fs.existsSync(path.join(gitDir, 'MERGE_HEAD'));
@@ -57,6 +63,7 @@ try {
57
63
  if (args.includes('--full')) modeFlag = ' --full';
58
64
 
59
65
  let commentedAny = false;
66
+ const successfullyCommentedFiles = [];
60
67
 
61
68
  /** Iterate over files to comment [ds] */
62
69
  for (const file of filesToComment) {
@@ -90,6 +97,7 @@ try {
90
97
  const cliPath = path.join(__dirname, 'cli.js');
91
98
  execSync(`node "${cliPath}" "${file}" --force${modeFlag}`, { stdio: 'inherit' });
92
99
  commentedAny = true;
100
+ successfullyCommentedFiles.push(file);
93
101
  } catch (err) {
94
102
  console.warn(`[devsplain] Warning: Failed to comment ${file}: ${err.message}`);
95
103
  }
@@ -97,10 +105,18 @@ try {
97
105
 
98
106
  /** Stage and commit auto-generated comments if any [ds] */
99
107
  if (commentedAny) {
100
- const status = execSync('git diff --name-only', { encoding: 'utf8' }).trim();
101
- if (status.length > 0) {
108
+ // Only stage the exact files that the AI touched to avoid accidentally committing unstaged work [ds]
109
+ for (const file of successfullyCommentedFiles) {
110
+ try {
111
+ execSync(`git add "${file}"`);
112
+ } catch (addErr) {}
113
+ }
114
+
115
+ // Check if there are actually staged changes now [ds]
116
+ const stagedChanges = execSync('git diff --cached --name-only', { encoding: 'utf8' }).trim();
117
+ if (stagedChanges.length > 0) {
102
118
  console.log('[devsplain] Staging and committing auto-generated comments...');
103
- execSync('git commit -am "docs: auto-generated comments by devsplain" --no-verify', { stdio: 'inherit' });
119
+ execSync('git commit -m "docs: auto-generated comments by devsplain" --no-verify', { stdio: 'inherit' });
104
120
  console.log('[devsplain] Comments committed successfully! Rollback via: git reset --hard HEAD~1');
105
121
  }
106
122
  }
package/lib/config.js CHANGED
@@ -8,8 +8,8 @@ const configPath = path.join(os.homedir(), '.devsplainrc');
8
8
  async function getConfig(forceWizard = false) {
9
9
  if (process.env.DEVSPLAIN_API_KEY || process.env.DEVSPLAIN_PROVIDER) {
10
10
  const provider = process.env.DEVSPLAIN_PROVIDER || 'gemini';
11
- const model = process.env.DEVSPLAIN_MODEL || (provider === 'gemini' ? 'gemini-2.0-flash' : 'llama-3.3-70b-versatile');
12
- const baseUrl = process.env.DEVSPLAIN_BASE_URL || (provider === 'gemini' ? null : 'https://api.groq.com/openai');
11
+ const model = process.env.DEVSPLAIN_MODEL || (provider === 'gemini' ? 'gemini-2.0-flash' : (provider === 'claude' ? 'claude-3-5-sonnet-20240620' : 'llama-3.3-70b-versatile'));
12
+ const baseUrl = process.env.DEVSPLAIN_BASE_URL || (provider === 'gemini' ? null : (provider === 'claude' ? 'https://api.anthropic.com' : 'https://api.groq.com/openai'));
13
13
  return {
14
14
  provider,
15
15
  apiKey: process.env.DEVSPLAIN_API_KEY || '',
@@ -41,9 +41,10 @@ async function getConfig(forceWizard = false) {
41
41
  console.log("2. Gemini (Free Tier)");
42
42
  console.log("3. OpenAI (Paid)");
43
43
  console.log("4. Custom (Ollama, local, etc)");
44
+ console.log("5. Claude (Anthropic)");
44
45
 
45
46
  // Get user's selected AI provider option [ds]
46
- const choice = await askQuestion("Select (1-4): ");
47
+ const choice = await askQuestion("Select (1-5): ");
47
48
 
48
49
  // Handle selected AI provider option [ds]
49
50
  if (choice === '1') {
@@ -68,8 +69,14 @@ async function getConfig(forceWizard = false) {
68
69
  provider = 'custom';
69
70
  model = await askQuestion("Model name (e.g., llama3): ");
70
71
  baseUrl = await askQuestion("Base URL (e.g., http://localhost:11434): ");
72
+ } else if (choice === '5') {
73
+ provider = 'claude';
74
+ baseUrl = 'https://api.anthropic.com';
75
+ console.log("\nGet your Anthropic key here: https://console.anthropic.com/settings/keys");
76
+ const customModel = await askQuestion("Model name (press Enter for default 'claude-3-5-sonnet-20240620'): ");
77
+ model = customModel.trim() || 'claude-3-5-sonnet-20240620';
71
78
  } else {
72
- console.log("Invalid choice. Please select 1, 2, 3, or 4.");
79
+ console.log("Invalid choice. Please select 1, 2, 3, 4, or 5.");
73
80
  continue;
74
81
  }
75
82
 
package/lib/llm.js CHANGED
@@ -117,8 +117,36 @@ ${numberedCode}
117
117
  throw new Error(`API Error: ${data.error.message}`);
118
118
  }
119
119
  textResponse = data.candidates[0].content.parts[0].text;
120
+ } else if (config.provider === 'claude') {
121
+ const url = `${config.baseUrl}/v1/messages`;
122
+ let data;
123
+ try {
124
+ const response = await fetchWithRetry(url, {
125
+ method: 'POST',
126
+ headers: {
127
+ 'Content-Type': 'application/json',
128
+ 'x-api-key': config.apiKey,
129
+ 'anthropic-version': '2023-06-01'
130
+ },
131
+ body: JSON.stringify({
132
+ "model": config.model,
133
+ "max_tokens": 8192,
134
+ "messages": [{
135
+ "role": "user",
136
+ "content": prompt
137
+ }]
138
+ })
139
+ });
140
+ data = await response.json();
141
+ } catch (error) {
142
+ throw new Error("Network Error: Could not connect to the AI provider. Check your internet or API url.");
143
+ }
144
+ if (data.error) {
145
+ throw new Error(`API Error: ${data.error.message}`);
146
+ }
147
+ textResponse = data.content[0].text;
120
148
  }
121
- // Otherwise, use a different provider [ds]
149
+ // Otherwise, use an OpenAI-compatible provider [ds]
122
150
  else {
123
151
  const url = `${config.baseUrl}/v1/chat/completions`;
124
152
  let data;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "devsplain",
3
- "version": "1.7.1",
3
+ "version": "1.8.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",