devsplain 2.1.1 → 2.2.1
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/cli.js +56 -11
- package/lib/config.js +284 -207
- package/package.json +1 -1
package/bin/cli.js
CHANGED
|
@@ -10,7 +10,7 @@ const { execSync } = require('child_process');
|
|
|
10
10
|
let rl;
|
|
11
11
|
let askQuestion;
|
|
12
12
|
|
|
13
|
-
//
|
|
13
|
+
// Determines whether the current Git repository has uncommitted changes [ds]
|
|
14
14
|
function isGitDirty() {
|
|
15
15
|
try {
|
|
16
16
|
const gitDir = execSync('git rev-parse --is-inside-work-tree', { stdio: ['ignore', 'pipe', 'ignore'], encoding: 'utf8' }).trim();
|
|
@@ -23,7 +23,7 @@ function isGitDirty() {
|
|
|
23
23
|
return false;
|
|
24
24
|
}
|
|
25
25
|
|
|
26
|
-
// Checks
|
|
26
|
+
// Checks whether a given line of code is inside a string literal [ds]
|
|
27
27
|
function isLineInsideString(lines, targetLineIndex, ext = '') {
|
|
28
28
|
const isPython = ext.toLowerCase() === '.py';
|
|
29
29
|
const isHTML = ['.html', '.vue', '.svelte'].includes(ext.toLowerCase());
|
|
@@ -236,7 +236,7 @@ function isLineInsideString(lines, targetLineIndex, ext = '') {
|
|
|
236
236
|
return inBacktick || inTripleDouble || inTripleSingle || inSingle || inDouble || inCppRawString || inRegex;
|
|
237
237
|
}
|
|
238
238
|
|
|
239
|
-
// Analyzes comments
|
|
239
|
+
// Analyzes comments within a given list of code lines [ds]
|
|
240
240
|
function analyzeComments(lines, ext = '') {
|
|
241
241
|
const isPython = ext.toLowerCase() === '.py';
|
|
242
242
|
const isHTML = ['.html', '.vue', '.svelte'].includes(ext.toLowerCase());
|
|
@@ -477,7 +477,7 @@ function analyzeComments(lines, ext = '') {
|
|
|
477
477
|
return analysis;
|
|
478
478
|
}
|
|
479
479
|
|
|
480
|
-
//
|
|
480
|
+
// Inserts or removes comments from code based on the specified mode [ds]
|
|
481
481
|
function spliceComments(data, comments, mode = 'default', ext = '') {
|
|
482
482
|
const hasCRLF = data.includes('\r\n');
|
|
483
483
|
const lineEnding = hasCRLF ? '\r\n' : '\n';
|
|
@@ -530,7 +530,23 @@ function spliceComments(data, comments, mode = 'default', ext = '') {
|
|
|
530
530
|
if (lineAnalysis.isPureComment) {
|
|
531
531
|
finalDeletions.add(lineNum);
|
|
532
532
|
} else if (lineAnalysis.commentStartIndex !== -1) {
|
|
533
|
-
|
|
533
|
+
const idx = lineAnalysis.commentStartIndex;
|
|
534
|
+
const remainder = lineStr.slice(idx);
|
|
535
|
+
let newText = lineStr.slice(0, idx).trimEnd();
|
|
536
|
+
|
|
537
|
+
if (remainder.startsWith('/*')) {
|
|
538
|
+
const endIdx = remainder.indexOf('*/');
|
|
539
|
+
if (endIdx !== -1) {
|
|
540
|
+
newText = lineStr.slice(0, idx) + remainder.slice(endIdx + 2);
|
|
541
|
+
}
|
|
542
|
+
} else if (remainder.startsWith('<!--')) {
|
|
543
|
+
const endIdx = remainder.indexOf('-->');
|
|
544
|
+
if (endIdx !== -1) {
|
|
545
|
+
newText = lineStr.slice(0, idx) + remainder.slice(endIdx + 3);
|
|
546
|
+
}
|
|
547
|
+
}
|
|
548
|
+
|
|
549
|
+
annotated[i].text = newText.trimEnd();
|
|
534
550
|
}
|
|
535
551
|
} else if (mode === 'clean') {
|
|
536
552
|
const isDsBlockLine = dsBlocks.has(lineNum);
|
|
@@ -542,7 +558,23 @@ function spliceComments(data, comments, mode = 'default', ext = '') {
|
|
|
542
558
|
}
|
|
543
559
|
} else if (lineAnalysis.commentStartIndex !== -1) {
|
|
544
560
|
if (isDsBlockLine || hasDsInline) {
|
|
545
|
-
|
|
561
|
+
const idx = lineAnalysis.commentStartIndex;
|
|
562
|
+
const remainder = lineStr.slice(idx);
|
|
563
|
+
let newText = lineStr.slice(0, idx).trimEnd();
|
|
564
|
+
|
|
565
|
+
if (remainder.startsWith('/*')) {
|
|
566
|
+
const endIdx = remainder.indexOf('*/');
|
|
567
|
+
if (endIdx !== -1) {
|
|
568
|
+
newText = lineStr.slice(0, idx) + remainder.slice(endIdx + 2);
|
|
569
|
+
}
|
|
570
|
+
} else if (remainder.startsWith('<!--')) {
|
|
571
|
+
const endIdx = remainder.indexOf('-->');
|
|
572
|
+
if (endIdx !== -1) {
|
|
573
|
+
newText = lineStr.slice(0, idx) + remainder.slice(endIdx + 3);
|
|
574
|
+
}
|
|
575
|
+
}
|
|
576
|
+
|
|
577
|
+
annotated[i].text = newText.trimEnd();
|
|
546
578
|
}
|
|
547
579
|
}
|
|
548
580
|
}
|
|
@@ -643,7 +675,23 @@ function spliceComments(data, comments, mode = 'default', ext = '') {
|
|
|
643
675
|
const isDsBlockLine = dsBlocks.has(origIdx + 1);
|
|
644
676
|
const hasDsInline = originalLine.includes('[ds]');
|
|
645
677
|
if (mode === 'prune' || (mode === 'clean' && (hasDsInline || isDsBlockLine))) {
|
|
646
|
-
const
|
|
678
|
+
const idx = lineAnalysis.commentStartIndex;
|
|
679
|
+
const remainder = originalLine.slice(idx);
|
|
680
|
+
let expectedStripped = originalLine.slice(0, idx).trimEnd();
|
|
681
|
+
|
|
682
|
+
if (remainder.startsWith('/*')) {
|
|
683
|
+
const endIdx = remainder.indexOf('*/');
|
|
684
|
+
if (endIdx !== -1) {
|
|
685
|
+
expectedStripped = originalLine.slice(0, idx) + remainder.slice(endIdx + 2);
|
|
686
|
+
}
|
|
687
|
+
} else if (remainder.startsWith('<!--')) {
|
|
688
|
+
const endIdx = remainder.indexOf('-->');
|
|
689
|
+
if (endIdx !== -1) {
|
|
690
|
+
expectedStripped = originalLine.slice(0, idx) + remainder.slice(endIdx + 3);
|
|
691
|
+
}
|
|
692
|
+
}
|
|
693
|
+
expectedStripped = expectedStripped.trimEnd();
|
|
694
|
+
|
|
647
695
|
if (text === expectedStripped) {
|
|
648
696
|
return true;
|
|
649
697
|
}
|
|
@@ -669,7 +717,7 @@ function spliceComments(data, comments, mode = 'default', ext = '') {
|
|
|
669
717
|
return annotated.map(line => line.text).join(lineEnding);
|
|
670
718
|
}
|
|
671
719
|
|
|
672
|
-
// Runs the
|
|
720
|
+
// Runs the command-line interface for the application [ds]
|
|
673
721
|
async function runCLI() {
|
|
674
722
|
rl = readline.createInterface({ input: process.stdin, output: process.stdout });
|
|
675
723
|
askQuestion = (query) => new Promise((resolve) => rl.question(query, resolve));
|
|
@@ -823,7 +871,6 @@ Options:
|
|
|
823
871
|
return false;
|
|
824
872
|
}
|
|
825
873
|
|
|
826
|
-
// Processes a given file path, either a file or directory [ds]
|
|
827
874
|
async function processPath(targetPath) {
|
|
828
875
|
const stats = fs.statSync(targetPath);
|
|
829
876
|
|
|
@@ -881,7 +928,6 @@ Options:
|
|
|
881
928
|
fs.renameSync(tempPath, targetPath);
|
|
882
929
|
console.log(` Successfully saved ${targetPath}`);
|
|
883
930
|
} else {
|
|
884
|
-
// Asks the user if they want to save the commented code to a file [ds]
|
|
885
931
|
console.log(` Skipped ${targetPath}`);
|
|
886
932
|
}
|
|
887
933
|
} else {
|
|
@@ -914,7 +960,6 @@ Options:
|
|
|
914
960
|
rl.close();
|
|
915
961
|
}
|
|
916
962
|
|
|
917
|
-
// Starts the CLI application if this script is run directly [ds]
|
|
918
963
|
if (require.main === module) {
|
|
919
964
|
runCLI().catch(err => {
|
|
920
965
|
console.error(err);
|
package/lib/config.js
CHANGED
|
@@ -1,207 +1,284 @@
|
|
|
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
|
-
|
|
8
|
-
function askSecret(query) {
|
|
9
|
-
return new Promise((resolve) => {
|
|
10
|
-
const rl = readline.createInterface({
|
|
11
|
-
input: process.stdin,
|
|
12
|
-
output: process.stdout
|
|
13
|
-
});
|
|
14
|
-
process.stdout.write(query);
|
|
15
|
-
process.stdin.setRawMode(true);
|
|
16
|
-
let key = '';
|
|
17
|
-
const onData = (char) => {
|
|
18
|
-
char = char.toString();
|
|
19
|
-
if (char === '\n' || char === '\r') {
|
|
20
|
-
process.stdin.setRawMode(false);
|
|
21
|
-
process.stdin.removeListener('data', onData);
|
|
22
|
-
process.stdout.write('\n');
|
|
23
|
-
rl.close();
|
|
24
|
-
resolve(key);
|
|
25
|
-
} else if (char === '\u0003') { // Ctrl+C
|
|
26
|
-
process.exit();
|
|
27
|
-
} else {
|
|
28
|
-
key += char;
|
|
29
|
-
process.stdout.write('*');
|
|
30
|
-
}
|
|
31
|
-
};
|
|
32
|
-
process.stdin.on('data', onData);
|
|
33
|
-
});
|
|
34
|
-
}
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
if (
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
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
|
+
// Asks for secret input from the user [ds]
|
|
8
|
+
function askSecret(query) {
|
|
9
|
+
return new Promise((resolve) => {
|
|
10
|
+
const rl = readline.createInterface({
|
|
11
|
+
input: process.stdin,
|
|
12
|
+
output: process.stdout
|
|
13
|
+
});
|
|
14
|
+
process.stdout.write(query);
|
|
15
|
+
process.stdin.setRawMode(true);
|
|
16
|
+
let key = '';
|
|
17
|
+
const onData = (char) => {
|
|
18
|
+
char = char.toString();
|
|
19
|
+
if (char === '\n' || char === '\r') {
|
|
20
|
+
process.stdin.setRawMode(false);
|
|
21
|
+
process.stdin.removeListener('data', onData);
|
|
22
|
+
process.stdout.write('\n');
|
|
23
|
+
rl.close();
|
|
24
|
+
resolve(key);
|
|
25
|
+
} else if (char === '\u0003') { // Ctrl+C
|
|
26
|
+
process.exit();
|
|
27
|
+
} else {
|
|
28
|
+
key += char;
|
|
29
|
+
process.stdout.write('*');
|
|
30
|
+
}
|
|
31
|
+
};
|
|
32
|
+
process.stdin.on('data', onData);
|
|
33
|
+
});
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
// Migrates old config to the new format [ds]
|
|
37
|
+
function migrateConfig(oldConfig) {
|
|
38
|
+
if (oldConfig.provider && !oldConfig.providers) {
|
|
39
|
+
return {
|
|
40
|
+
activeProvider: oldConfig.provider,
|
|
41
|
+
providers: {
|
|
42
|
+
[oldConfig.provider]: {
|
|
43
|
+
apiKey: oldConfig.apiKey || '',
|
|
44
|
+
model: oldConfig.model || '',
|
|
45
|
+
baseUrl: oldConfig.baseUrl || null,
|
|
46
|
+
autoPrune: oldConfig.autoPrune || false
|
|
47
|
+
}
|
|
48
|
+
}
|
|
49
|
+
};
|
|
50
|
+
}
|
|
51
|
+
return oldConfig;
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
// Gets the config, either from environment variables, the config file, or by prompting the user [ds]
|
|
55
|
+
async function getConfig(forceWizard = false) {
|
|
56
|
+
if (process.env.DEVSPLAIN_API_KEY || process.env.DEVSPLAIN_PROVIDER) {
|
|
57
|
+
const provider = process.env.DEVSPLAIN_PROVIDER || 'gemini';
|
|
58
|
+
const model = process.env.DEVSPLAIN_MODEL || (provider === 'gemini' ? 'gemini-2.0-flash' : (provider === 'claude' ? 'claude-3-5-sonnet-20240620' : 'llama-3.3-70b-versatile'));
|
|
59
|
+
const baseUrl = process.env.DEVSPLAIN_BASE_URL || (provider === 'gemini' ? null : (provider === 'claude' ? 'https://api.anthropic.com' : 'https://api.groq.com/openai'));
|
|
60
|
+
return {
|
|
61
|
+
provider,
|
|
62
|
+
apiKey: process.env.DEVSPLAIN_API_KEY || '',
|
|
63
|
+
model,
|
|
64
|
+
baseUrl
|
|
65
|
+
};
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
let fileConfig = null;
|
|
69
|
+
if (fs.existsSync(configPath)) {
|
|
70
|
+
try {
|
|
71
|
+
const rawData = fs.readFileSync(configPath, 'utf8');
|
|
72
|
+
fileConfig = migrateConfig(JSON.parse(rawData));
|
|
73
|
+
} catch (e) {
|
|
74
|
+
// Ignored, file might be corrupted
|
|
75
|
+
}
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
if (!fileConfig || forceWizard) {
|
|
79
|
+
let rl = readline.createInterface({
|
|
80
|
+
input: process.stdin,
|
|
81
|
+
output: process.stdout
|
|
82
|
+
});
|
|
83
|
+
let askQuestion = (query) => new Promise((resolve) => rl.question(query, resolve));
|
|
84
|
+
|
|
85
|
+
let config = fileConfig || { activeProvider: '', providers: {} };
|
|
86
|
+
let confirmed = false;
|
|
87
|
+
|
|
88
|
+
while (!confirmed) {
|
|
89
|
+
const savedProviders = Object.keys(config.providers);
|
|
90
|
+
let providerToConfig = null;
|
|
91
|
+
let wantToUpdate = true;
|
|
92
|
+
let isNewProvider = false;
|
|
93
|
+
|
|
94
|
+
if (savedProviders.length > 0) {
|
|
95
|
+
console.log("\nSaved Providers:");
|
|
96
|
+
savedProviders.forEach((p, i) => {
|
|
97
|
+
const isActive = config.activeProvider === p ? ' (active)' : '';
|
|
98
|
+
console.log(`${i + 1}. ${p}${isActive}`);
|
|
99
|
+
});
|
|
100
|
+
console.log(`\n${savedProviders.length + 1}. Add/Configure a different provider`);
|
|
101
|
+
|
|
102
|
+
const c = await askQuestion(`Select (1-${savedProviders.length + 1}): `);
|
|
103
|
+
const idx = parseInt(c) - 1;
|
|
104
|
+
|
|
105
|
+
if (idx >= 0 && idx < savedProviders.length) {
|
|
106
|
+
providerToConfig = savedProviders[idx];
|
|
107
|
+
const update = await askQuestion(`Do you want to update the API key or model for ${providerToConfig}? (y/N): `);
|
|
108
|
+
if (update.trim().toLowerCase() !== 'y' && update.trim().toLowerCase() !== 'yes') {
|
|
109
|
+
wantToUpdate = false;
|
|
110
|
+
}
|
|
111
|
+
} else if (idx === savedProviders.length) {
|
|
112
|
+
isNewProvider = true;
|
|
113
|
+
} else {
|
|
114
|
+
console.log("Invalid choice.");
|
|
115
|
+
continue;
|
|
116
|
+
}
|
|
117
|
+
} else {
|
|
118
|
+
isNewProvider = true;
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
let baseUrl = "";
|
|
122
|
+
let model = "";
|
|
123
|
+
let provider = "";
|
|
124
|
+
let apiKey = '';
|
|
125
|
+
let autoPrune = false;
|
|
126
|
+
|
|
127
|
+
if (wantToUpdate) {
|
|
128
|
+
if (isNewProvider || !providerToConfig) {
|
|
129
|
+
console.log("\nWhich AI Provider Do You want to use?");
|
|
130
|
+
console.log("1. Groq (Free, Fast, Llama-3)");
|
|
131
|
+
console.log("2. Gemini (Free Tier)");
|
|
132
|
+
console.log("3. OpenAI (Paid)");
|
|
133
|
+
console.log("4. Custom (Ollama, local, etc)");
|
|
134
|
+
console.log("5. Claude (Anthropic)");
|
|
135
|
+
|
|
136
|
+
const choice = await askQuestion("Select (1-5): ");
|
|
137
|
+
|
|
138
|
+
if (choice === '1') {
|
|
139
|
+
provider = 'groq';
|
|
140
|
+
baseUrl = 'https://api.groq.com/openai';
|
|
141
|
+
console.log("\nGet your free Groq key here: https://console.groq.com/keys");
|
|
142
|
+
const customModel = await askQuestion("Model name (press Enter for default 'llama-3.3-70b-versatile'): ");
|
|
143
|
+
model = customModel.trim() || 'llama-3.3-70b-versatile';
|
|
144
|
+
} else if (choice === '2') {
|
|
145
|
+
provider = 'gemini';
|
|
146
|
+
baseUrl = null;
|
|
147
|
+
console.log("\nGet your free Gemini key here: https://aistudio.google.com/apikey");
|
|
148
|
+
const customModel = await askQuestion("Model name (press Enter for default 'gemini-2.0-flash'): ");
|
|
149
|
+
model = customModel.trim() || 'gemini-2.0-flash';
|
|
150
|
+
} else if (choice === '3') {
|
|
151
|
+
provider = 'openai';
|
|
152
|
+
baseUrl = 'https://api.openai.com';
|
|
153
|
+
console.log("\nGet your OpenAI key here: https://platform.openai.com/api-keys");
|
|
154
|
+
const customModel = await askQuestion("Model name (press Enter for default 'gpt-4o'): ");
|
|
155
|
+
model = customModel.trim() || 'gpt-4o';
|
|
156
|
+
} else if (choice === '4') {
|
|
157
|
+
provider = 'custom';
|
|
158
|
+
while (true) {
|
|
159
|
+
model = (await askQuestion("Model name (e.g., llama3): ")).trim();
|
|
160
|
+
if (model) break;
|
|
161
|
+
console.log("Model name cannot be empty.");
|
|
162
|
+
}
|
|
163
|
+
while (true) {
|
|
164
|
+
baseUrl = (await askQuestion("Base URL (e.g., http://localhost:11434): ")).trim();
|
|
165
|
+
if (baseUrl) break;
|
|
166
|
+
console.log("Base URL cannot be empty.");
|
|
167
|
+
}
|
|
168
|
+
} else if (choice === '5') {
|
|
169
|
+
provider = 'claude';
|
|
170
|
+
baseUrl = 'https://api.anthropic.com';
|
|
171
|
+
console.log("\nGet your Anthropic key here: https://console.anthropic.com/settings/keys");
|
|
172
|
+
const customModel = await askQuestion("Model name (press Enter for default 'claude-3-5-sonnet-20240620'): ");
|
|
173
|
+
model = customModel.trim() || 'claude-3-5-sonnet-20240620';
|
|
174
|
+
} else {
|
|
175
|
+
console.log("Invalid choice. Please select 1, 2, 3, 4, or 5.");
|
|
176
|
+
continue;
|
|
177
|
+
}
|
|
178
|
+
} else {
|
|
179
|
+
provider = providerToConfig;
|
|
180
|
+
const old = config.providers[provider];
|
|
181
|
+
baseUrl = old.baseUrl;
|
|
182
|
+
|
|
183
|
+
let defaultModel = old.model;
|
|
184
|
+
const customModel = await askQuestion(`Model name (press Enter for default '${defaultModel}'): `);
|
|
185
|
+
model = customModel.trim() || defaultModel;
|
|
186
|
+
|
|
187
|
+
if (provider === 'custom') {
|
|
188
|
+
const customBase = await askQuestion(`Base URL (press Enter for default '${baseUrl}'): `);
|
|
189
|
+
baseUrl = customBase.trim() || baseUrl;
|
|
190
|
+
}
|
|
191
|
+
}
|
|
192
|
+
|
|
193
|
+
while (true) {
|
|
194
|
+
const promptMsg = provider === 'custom'
|
|
195
|
+
? "Paste your API key (leave blank for local models): "
|
|
196
|
+
: "Paste your API key: ";
|
|
197
|
+
|
|
198
|
+
if (process.stdin.isTTY) {
|
|
199
|
+
rl.close();
|
|
200
|
+
apiKey = await askSecret(promptMsg);
|
|
201
|
+
|
|
202
|
+
rl = readline.createInterface({
|
|
203
|
+
input: process.stdin,
|
|
204
|
+
output: process.stdout
|
|
205
|
+
});
|
|
206
|
+
askQuestion = (query) => new Promise((resolve) => rl.question(query, resolve));
|
|
207
|
+
} else {
|
|
208
|
+
apiKey = await askQuestion(promptMsg);
|
|
209
|
+
}
|
|
210
|
+
|
|
211
|
+
apiKey = apiKey.trim();
|
|
212
|
+
if (provider === 'custom' || apiKey) {
|
|
213
|
+
break;
|
|
214
|
+
}
|
|
215
|
+
console.log(`API key is required for provider '${provider}'.`);
|
|
216
|
+
}
|
|
217
|
+
|
|
218
|
+
while (true) {
|
|
219
|
+
const pruneAns = (await askQuestion("Do you want devsplain to aggressively prune (overwrite) existing human/AI comments? (y/n, default: n): ")).trim().toLowerCase();
|
|
220
|
+
if (pruneAns === '' || pruneAns === 'n' || pruneAns === 'no') {
|
|
221
|
+
autoPrune = false;
|
|
222
|
+
break;
|
|
223
|
+
} else if (pruneAns === 'y' || pruneAns === 'yes') {
|
|
224
|
+
autoPrune = true;
|
|
225
|
+
break;
|
|
226
|
+
}
|
|
227
|
+
console.log("Invalid choice. Please enter 'y' or 'n'.");
|
|
228
|
+
}
|
|
229
|
+
|
|
230
|
+
console.log("\n--- Configuration Summary ---");
|
|
231
|
+
console.log(`Provider: ${provider}`);
|
|
232
|
+
console.log(`Model: ${model}`);
|
|
233
|
+
console.log(`Base URL: ${baseUrl || 'N/A'}`);
|
|
234
|
+
console.log(`API Key: ${apiKey ? apiKey.substring(0, 4) + '*'.repeat(Math.max(0, apiKey.length - 4)) : 'None'}`);
|
|
235
|
+
console.log(`Auto-Prune: ${autoPrune ? 'Yes' : 'No'}`);
|
|
236
|
+
console.log("-----------------------------\n");
|
|
237
|
+
|
|
238
|
+
while (true) {
|
|
239
|
+
const confirm = (await askQuestion("Does this look correct? (y/n, default: y): ")).trim().toLowerCase();
|
|
240
|
+
if (confirm === '' || confirm === 'y' || confirm === 'yes') {
|
|
241
|
+
config.activeProvider = provider;
|
|
242
|
+
config.providers[provider] = {
|
|
243
|
+
apiKey,
|
|
244
|
+
model,
|
|
245
|
+
baseUrl,
|
|
246
|
+
autoPrune
|
|
247
|
+
};
|
|
248
|
+
confirmed = true;
|
|
249
|
+
break;
|
|
250
|
+
} else if (confirm === 'n' || confirm === 'no') {
|
|
251
|
+
break; // Start over loop
|
|
252
|
+
}
|
|
253
|
+
console.log("Invalid choice. Please enter 'y' or 'n'.");
|
|
254
|
+
}
|
|
255
|
+
} else {
|
|
256
|
+
// User just selected an existing provider and didn't want to update it
|
|
257
|
+
config.activeProvider = providerToConfig;
|
|
258
|
+
console.log(`\nSwitched active provider to ${config.activeProvider}.`);
|
|
259
|
+
confirmed = true;
|
|
260
|
+
}
|
|
261
|
+
}
|
|
262
|
+
|
|
263
|
+
rl.close();
|
|
264
|
+
|
|
265
|
+
fs.writeFileSync(configPath, JSON.stringify(config, null, 2));
|
|
266
|
+
try {
|
|
267
|
+
if (process.platform !== 'win32') {
|
|
268
|
+
fs.chmodSync(configPath, 0o600);
|
|
269
|
+
}
|
|
270
|
+
} catch (chmodErr) {}
|
|
271
|
+
|
|
272
|
+
return {
|
|
273
|
+
provider: config.activeProvider,
|
|
274
|
+
...config.providers[config.activeProvider]
|
|
275
|
+
};
|
|
276
|
+
} else {
|
|
277
|
+
return {
|
|
278
|
+
provider: fileConfig.activeProvider,
|
|
279
|
+
...fileConfig.providers[fileConfig.activeProvider]
|
|
280
|
+
};
|
|
281
|
+
}
|
|
282
|
+
}
|
|
283
|
+
|
|
284
|
+
module.exports = { getConfig };
|
package/package.json
CHANGED