dave-code 1.0.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/bin/cliMenu.js ADDED
@@ -0,0 +1,496 @@
1
+ /**
2
+ * ── DESIGN PHILOSOPHY / 视觉设计与交互规范 ──
3
+ * 为了向用户提供极致、顺滑的交互体验,Dave Code 规定:
4
+ * 1. 所有耗时的 API 请求、大文件处理 and 本地工具调用,都必须具有动画化(Animated)或动态(Dynamic)的终端 UI。
5
+ * 2. 严禁使用静态、刷屏式的连续 Log 输出。对于多步骤操作(如分片消化),应使用单行原地更新(In-place update)的进度条和加载动画。
6
+ * 3. 使用富文本终端色彩(如精心调配的 HSL/ANSI 颜色)和微型字符动画(如 Spinner)来提供实时的视觉反馈。
7
+ * 后续维护与功能续写时,必须严格遵守此设计理念。
8
+ */
9
+
10
+ import readline from 'readline';
11
+
12
+ export function selectMenu(title, description, options, defaultIndex = 0) {
13
+ return new Promise((resolve) => {
14
+ let cursor = defaultIndex;
15
+ const stdout = process.stdout;
16
+ const stdin = process.stdin;
17
+
18
+ readline.emitKeypressEvents(stdin);
19
+ const oldRawMode = stdin.isRawMode;
20
+ if (stdin.setRawMode) {
21
+ stdin.setRawMode(true);
22
+ }
23
+ stdin.resume();
24
+
25
+ // Hide terminal cursor
26
+ stdout.write('\x1b[?25l');
27
+
28
+ function draw() {
29
+ stdout.write(`\n\x1b[1;36m${title}\x1b[0m\n`);
30
+ stdout.write(`\x1b[90m${description}\x1b[0m\n\n`);
31
+
32
+ for (let i = 0; i < options.length; i++) {
33
+ const opt = options[i];
34
+ if (i === cursor) {
35
+ stdout.write(` \x1b[32m›\x1b[0m \x1b[1;32m${i + 1}. ${opt.name}\x1b[0m \x1b[90m${opt.desc}\x1b[0m\n`);
36
+ } else {
37
+ stdout.write(` ${i + 1}. ${opt.name} \x1b[90m${opt.desc}\x1b[0m\n`);
38
+ }
39
+ }
40
+ stdout.write('\n\x1b[90mUse Arrow Keys (↑/↓) to navigate, Enter to select, Esc to cancel\x1b[0m\n');
41
+ }
42
+
43
+ function clearLines(count) {
44
+ for (let i = 0; i < count; i++) {
45
+ stdout.write('\x1b[1A\x1b[2K');
46
+ }
47
+ }
48
+
49
+ const totalLines = options.length + 6;
50
+ draw();
51
+
52
+ function onKeypress(str, key) {
53
+ if (!key) return;
54
+
55
+ if (key.name === 'up') {
56
+ cursor = (cursor - 1 + options.length) % options.length;
57
+ clearLines(totalLines);
58
+ draw();
59
+ } else if (key.name === 'down') {
60
+ cursor = (cursor + 1) % options.length;
61
+ clearLines(totalLines);
62
+ draw();
63
+ } else if (key.name === 'return') {
64
+ cleanup();
65
+ stdout.write('\x1b[?25h'); // Show cursor
66
+ resolve(cursor);
67
+ } else if (key.name === 'escape' || (key.ctrl && key.name === 'c')) {
68
+ cleanup();
69
+ stdout.write('\x1b[?25h');
70
+ resolve(-1);
71
+ }
72
+ }
73
+
74
+ function cleanup() {
75
+ stdin.removeListener('keypress', onKeypress);
76
+ if (stdin.setRawMode) {
77
+ stdin.setRawMode(oldRawMode);
78
+ }
79
+ stdin.pause();
80
+ }
81
+
82
+ stdin.on('keypress', onKeypress);
83
+ });
84
+ }
85
+
86
+ export function secureInputPrompt(promptText) {
87
+ return new Promise((resolve) => {
88
+ const stdin = process.stdin;
89
+ const stdout = process.stdout;
90
+
91
+ stdout.write(promptText);
92
+
93
+ readline.emitKeypressEvents(stdin);
94
+ const oldRawMode = stdin.isRawMode;
95
+ if (stdin.setRawMode) {
96
+ stdin.setRawMode(true);
97
+ }
98
+ stdin.resume();
99
+
100
+ let value = '';
101
+
102
+ function onKeypress(str, key) {
103
+ if (!key) return;
104
+
105
+ if (key.name === 'return') {
106
+ cleanup();
107
+ stdout.write('\n');
108
+ resolve(value.trim());
109
+ } else if (key.name === 'backspace') {
110
+ if (value.length > 0) {
111
+ value = value.slice(0, -1);
112
+ stdout.write('\b \b');
113
+ }
114
+ } else if (key.ctrl && key.name === 'c') {
115
+ cleanup();
116
+ stdout.write('\n');
117
+ resolve('');
118
+ } else if (str && str.length === 1 && !key.ctrl && !key.meta) {
119
+ value += str;
120
+ stdout.write('*');
121
+ }
122
+ }
123
+
124
+ function cleanup() {
125
+ stdin.removeListener('keypress', onKeypress);
126
+ if (stdin.setRawMode) {
127
+ stdin.setRawMode(oldRawMode);
128
+ }
129
+ stdin.pause();
130
+ }
131
+
132
+ stdin.on('keypress', onKeypress);
133
+ });
134
+ }
135
+
136
+ export function textInputPrompt(promptText) {
137
+ return new Promise((resolve) => {
138
+ const rl = readline.createInterface({
139
+ input: process.stdin,
140
+ output: process.stdout
141
+ });
142
+ rl.question(promptText, (answer) => {
143
+ rl.close();
144
+ resolve(answer.trim());
145
+ });
146
+ });
147
+ }
148
+
149
+ export function chatInputPrompt(placeholder, currentLang) {
150
+ return new Promise((resolve) => {
151
+ const stdin = process.stdin;
152
+ const stdout = process.stdout;
153
+
154
+ const footerText = currentLang === 'cn'
155
+ ? '? 获取帮助 · Ctrl+C 退出'
156
+ : '? for help · Ctrl+C to exit';
157
+
158
+ stdout.write('\x1b[90m───────────────────────────────────────────────────────────────────────────────────────────────\x1b[0m\n');
159
+ stdout.write(`› \x1b[90m${placeholder}\x1b[0m\n`);
160
+ stdout.write(`\x1b[90m${footerText}\x1b[0m`);
161
+
162
+ stdout.write('\x1b[1A\x1b[3G');
163
+
164
+ readline.emitKeypressEvents(stdin);
165
+ const oldRawMode = stdin.isRawMode;
166
+ if (stdin.setRawMode) {
167
+ stdin.setRawMode(true);
168
+ }
169
+ stdin.resume();
170
+
171
+ let chars = [];
172
+ let cursorIndex = 0;
173
+
174
+ function getStringWidth(str) {
175
+ const cleanStr = str.replace(/\x1b\[[0-9;]*[a-zA-Z]/g, '');
176
+ let width = 0;
177
+ for (let i = 0; i < cleanStr.length; i++) {
178
+ const code = cleanStr.charCodeAt(i);
179
+ if (
180
+ (code >= 0x4e00 && code <= 0x9fff) ||
181
+ (code >= 0x3400 && code <= 0x4dbf) ||
182
+ (code >= 0x3000 && code <= 0x303f) ||
183
+ (code >= 0xff00 && code <= 0xffef)
184
+ ) {
185
+ width += 2;
186
+ } else {
187
+ width += 1;
188
+ }
189
+ }
190
+ return width;
191
+ }
192
+
193
+ function redraw() {
194
+ stdout.write('\x1b[1G');
195
+ stdout.write('\x1b[J');
196
+
197
+ const valueStr = chars.join('');
198
+ if (valueStr.length === 0) {
199
+ stdout.write(`› \x1b[90m${placeholder}\x1b[0m\n`);
200
+ } else {
201
+ stdout.write(`› ${valueStr}\n`);
202
+ }
203
+
204
+ stdout.write(`\x1b[90m${footerText}\x1b[0m`);
205
+ stdout.write('\x1b[1A');
206
+
207
+ const leftPart = chars.slice(0, cursorIndex).join('');
208
+ const cursorCol = 3 + (chars.length === 0 ? 0 : getStringWidth(leftPart));
209
+ stdout.write(`\x1b[${cursorCol}G`);
210
+ }
211
+
212
+ function onKeypress(str, key) {
213
+ if (!key) return;
214
+
215
+ if (key.name === 'return') {
216
+ cleanup();
217
+ stdout.write('\x1b[1B\x1b[1G\x1b[2K\r');
218
+ resolve(chars.join('').trim());
219
+ } else if (key.name === 'backspace') {
220
+ if (cursorIndex > 0) {
221
+ chars.splice(cursorIndex - 1, 1);
222
+ cursorIndex--;
223
+ redraw();
224
+ }
225
+ } else if (key.name === 'delete') {
226
+ if (cursorIndex < chars.length) {
227
+ chars.splice(cursorIndex, 1);
228
+ redraw();
229
+ }
230
+ } else if (key.name === 'left') {
231
+ if (cursorIndex > 0) {
232
+ cursorIndex--;
233
+ redraw();
234
+ }
235
+ } else if (key.name === 'right') {
236
+ if (cursorIndex < chars.length) {
237
+ cursorIndex++;
238
+ redraw();
239
+ }
240
+ } else if (key.name === 'home') {
241
+ cursorIndex = 0;
242
+ redraw();
243
+ } else if (key.name === 'end') {
244
+ cursorIndex = chars.length;
245
+ redraw();
246
+ } else if (key.ctrl && key.name === 'c') {
247
+ cleanup();
248
+ stdout.write('\x1b[1B\x1b[1G\x1b[2K\r\n');
249
+ resolve('/exit');
250
+ } else if (str && str.length >= 1 && !key.ctrl && !key.meta) {
251
+ if (str.startsWith('\x1b')) return;
252
+ chars.splice(cursorIndex, 0, ...str);
253
+ cursorIndex += [...str].length;
254
+ redraw();
255
+ }
256
+ }
257
+
258
+ function cleanup() {
259
+ stdin.removeListener('keypress', onKeypress);
260
+ if (stdin.setRawMode) {
261
+ stdin.setRawMode(oldRawMode);
262
+ }
263
+ stdin.pause();
264
+ }
265
+
266
+ stdin.on('keypress', onKeypress);
267
+ });
268
+ }
269
+
270
+ export function waitForEnter(promptText) {
271
+ return new Promise((resolve) => {
272
+ const stdin = process.stdin;
273
+ const stdout = process.stdout;
274
+
275
+ stdout.write(promptText);
276
+
277
+ readline.emitKeypressEvents(stdin);
278
+ const oldRawMode = stdin.isRawMode;
279
+ if (stdin.setRawMode) {
280
+ stdin.setRawMode(true);
281
+ }
282
+ stdin.resume();
283
+
284
+ function onKeypress(str, key) {
285
+ if (key && key.name === 'return') {
286
+ cleanup();
287
+ stdout.write('\n');
288
+ resolve();
289
+ }
290
+ }
291
+
292
+ function cleanup() {
293
+ stdin.removeListener('keypress', onKeypress);
294
+ if (stdin.setRawMode) {
295
+ stdin.setRawMode(oldRawMode);
296
+ }
297
+ stdin.pause();
298
+ }
299
+
300
+ stdin.on('keypress', onKeypress);
301
+ });
302
+ }
303
+
304
+ export function startSpinner(prefixText) {
305
+ const frames = ['⠋', '⠙', '⠹', '⠸', '⠼', '⠴', '⠦', '⠧', '⠇', '⠏'];
306
+ let i = 0;
307
+
308
+ process.stdout.write('\x1b[?25l');
309
+ process.stdout.write(`\r\x1b[K\x1b[36m${prefixText} ${frames[i]}\x1b[0m`);
310
+
311
+ const interval = setInterval(() => {
312
+ i = (i + 1) % frames.length;
313
+ process.stdout.write(`\r\x1b[K\x1b[36m${prefixText} ${frames[i]}\x1b[0m`);
314
+ }, 80);
315
+
316
+ return () => {
317
+ clearInterval(interval);
318
+ process.stdout.write('\r\x1b[K');
319
+ process.stdout.write('\x1b[?25h');
320
+ };
321
+ }
322
+
323
+ export function selectEffortMenu(currentEffort, currentLang) {
324
+ const levels = ['low', 'medium', 'high', 'xhigh', 'max', 'ultracode'];
325
+ let cursor = levels.indexOf(currentEffort);
326
+ if (cursor === -1) cursor = 2; // Default to 'high'
327
+
328
+ return new Promise((resolve) => {
329
+ const stdout = process.stdout;
330
+ const stdin = process.stdin;
331
+
332
+ readline.emitKeypressEvents(stdin);
333
+ const oldRawMode = stdin.isRawMode;
334
+ if (stdin.setRawMode) {
335
+ stdin.setRawMode(true);
336
+ }
337
+ stdin.resume();
338
+
339
+ // Hide terminal cursor
340
+ stdout.write('\x1b[?25l');
341
+
342
+ const title = currentLang === 'cn' ? '调整努力模式 (Adjust Effort Mode)' : 'Adjust Effort Mode';
343
+ const description = currentLang === 'cn'
344
+ ? '选择模型的思考努力程度。努力程度越高,循环上限和阅读长度越大;ultracode 模式会执行大文件消化工作流以完全读完。'
345
+ : 'Select thinking effort level. Higher levels increase loop limits and file read sizes; ultracode triggers digestion workflows to read large files fully.';
346
+
347
+ const footer = currentLang === 'cn'
348
+ ? '使用左右方向键 (←/→) 移动滑块, 回车 (Enter) 确认, Esc/Ctrl+C 取消'
349
+ : 'Use Arrow Keys (←/→) to navigate, Enter to select, Esc/Ctrl+C to cancel';
350
+
351
+ function draw() {
352
+ stdout.write(`\n\x1b[1;36m${title}\x1b[0m\n`);
353
+ stdout.write(`\x1b[90m${description}\x1b[0m\n\n`);
354
+
355
+ stdout.write(`Faster Smarter\n`);
356
+ stdout.write(`───────────────────────────────────────────────────┋───────────────────────\n`);
357
+
358
+ // Calculate Pointer line (triangle)
359
+ const centers = [2, 12, 23, 36, 46, 60];
360
+ const selectedCenter = centers[cursor];
361
+
362
+ let pointerChar = '▲';
363
+ // Triangle color: purple if ultracode, otherwise yellow
364
+ let pointerColor = cursor === 5 ? '\x1b[1;38;2;140;90;240m' : '\x1b[1;33m';
365
+
366
+ let rawPointerLine = ' '.repeat(selectedCenter) + '▲' + ' '.repeat(76 - selectedCenter - 1);
367
+ let rawPointerLineChars = [...rawPointerLine];
368
+ rawPointerLineChars[50] = '┋';
369
+
370
+ let coloredPointerLine = '';
371
+ for (let i = 0; i < rawPointerLineChars.length; i++) {
372
+ if (i === selectedCenter) {
373
+ coloredPointerLine += pointerColor + '▲\x1b[0m';
374
+ } else if (i === 50) {
375
+ coloredPointerLine += '\x1b[90m┋\x1b[0m';
376
+ } else {
377
+ coloredPointerLine += rawPointerLineChars[i];
378
+ }
379
+ }
380
+ stdout.write(`${coloredPointerLine}\n`);
381
+
382
+ // Calculate Labels line
383
+ const optColors = levels.map((lvl, idx) => {
384
+ if (idx === cursor) {
385
+ return idx === 5 ? '\x1b[1;38;2;140;90;240m' : '\x1b[1;33m';
386
+ }
387
+ return '\x1b[90m';
388
+ });
389
+
390
+ stdout.write(` `);
391
+ stdout.write(`${optColors[0]}low\x1b[0m` + ` `);
392
+ stdout.write(`${optColors[1]}medium\x1b[0m` + ` `);
393
+ stdout.write(`${optColors[2]}high\x1b[0m` + ` `);
394
+ stdout.write(`${optColors[3]}xhigh\x1b[0m` + ` `);
395
+ stdout.write(`${optColors[4]}max\x1b[0m` + ` \x1b[90m┋\x1b[0m `);
396
+ stdout.write(`${optColors[5]}ultracode\x1b[0m\n`);
397
+
398
+ // Print subtext line (spacing with divider)
399
+ stdout.write(' '.repeat(50) + '\x1b[90m┋\x1b[0m\n\n');
400
+
401
+ stdout.write(`\x1b[90m${footer}\x1b[0m\n`);
402
+ }
403
+
404
+ function clearLines(count) {
405
+ for (let i = 0; i < count; i++) {
406
+ stdout.write('\x1b[1A\x1b[2K');
407
+ }
408
+ }
409
+
410
+ const totalLines = 10;
411
+ draw();
412
+
413
+ function onKeypress(str, key) {
414
+ if (!key) return;
415
+
416
+ if (key.name === 'left') {
417
+ if (cursor > 0) {
418
+ cursor--;
419
+ clearLines(totalLines);
420
+ draw();
421
+ }
422
+ } else if (key.name === 'right') {
423
+ if (cursor < 5) {
424
+ cursor++;
425
+ clearLines(totalLines);
426
+ draw();
427
+ }
428
+ } else if (key.name === 'return') {
429
+ cleanup();
430
+ stdout.write('\x1b[?25h'); // Show cursor
431
+ resolve(levels[cursor]);
432
+ } else if (key.name === 'escape' || (key.ctrl && key.name === 'c')) {
433
+ cleanup();
434
+ stdout.write('\x1b[?25h');
435
+ resolve(null);
436
+ }
437
+ }
438
+
439
+ function cleanup() {
440
+ stdin.removeListener('keypress', onKeypress);
441
+ if (stdin.setRawMode) {
442
+ stdin.setRawMode(oldRawMode);
443
+ }
444
+ stdin.pause();
445
+ }
446
+
447
+ stdin.on('keypress', onKeypress);
448
+ });
449
+ }
450
+
451
+ export function confirmPrompt(promptText) {
452
+ return new Promise((resolve) => {
453
+ const stdin = process.stdin;
454
+ const stdout = process.stdout;
455
+
456
+ stdout.write(promptText);
457
+
458
+ readline.emitKeypressEvents(stdin);
459
+ const oldRawMode = stdin.isRawMode;
460
+ if (stdin.setRawMode) {
461
+ stdin.setRawMode(true);
462
+ }
463
+ stdin.resume();
464
+
465
+ function onKeypress(str, key) {
466
+ if (!key) return;
467
+
468
+ const char = (str || '').toLowerCase();
469
+ if (char === 'y') {
470
+ cleanup();
471
+ stdout.write('y\n');
472
+ resolve(true);
473
+ } else if (char === 'n' || key.name === 'return' || key.name === 'escape') {
474
+ cleanup();
475
+ stdout.write('n\n');
476
+ resolve(false);
477
+ } else if (key.ctrl && key.name === 'c') {
478
+ cleanup();
479
+ stdout.write('\n');
480
+ resolve(false);
481
+ }
482
+ }
483
+
484
+ function cleanup() {
485
+ stdin.removeListener('keypress', onKeypress);
486
+ if (stdin.setRawMode) {
487
+ stdin.setRawMode(oldRawMode);
488
+ }
489
+ stdin.pause();
490
+ }
491
+
492
+ stdin.on('keypress', onKeypress);
493
+ });
494
+ }
495
+
496
+
@@ -0,0 +1,220 @@
1
+ /**
2
+ * ── DESIGN PHILOSOPHY / 视觉设计与交互规范 ──
3
+ * 为了向用户提供极致、顺滑的交互体验,Dave Code 规定:
4
+ * 1. 所有耗时的 API 请求、大文件处理和本地工具调用,都必须具有动画化(Animated)或动态(Dynamic)的终端 UI。
5
+ * 2. 严禁使用静态、刷屏式的连续 Log 输出。对于多步骤操作(如分片消化),应使用单行原地更新(In-place update)的进度条和加载动画。
6
+ * 3. 使用富文本终端色彩(如精心调配的 HSL/ANSI 颜色)和微型字符动画(如 Spinner)来提供实时的视觉反馈。
7
+ * 后续维护与功能续写时,必须严格遵守此设计理念。
8
+ */
9
+
10
+ import fs from 'fs';
11
+ import path from 'path';
12
+ import os from 'os';
13
+ import { exec } from 'child_process';
14
+
15
+ export let CONFIG_FILE = path.join(os.homedir(), '.dave-code-config.json');
16
+
17
+ export function setConfigFilePathForTesting(filePath) {
18
+ CONFIG_FILE = filePath;
19
+ }
20
+
21
+ const TEMPLATE_CONFIG = [
22
+ {
23
+ "model": "deepseek-chat",
24
+ "apiKey": "YOUR_API_KEY",
25
+ "apiBase": "https://api.deepseek.com/v1",
26
+ "proxyUrl": ""
27
+ }
28
+ ];
29
+
30
+ export function loadConfig() {
31
+ try {
32
+ if (fs.existsSync(CONFIG_FILE)) {
33
+ const data = fs.readFileSync(CONFIG_FILE, 'utf8');
34
+ const parsed = JSON.parse(data);
35
+ return Array.isArray(parsed) ? parsed : [];
36
+ }
37
+ } catch (e) {
38
+ // Ignore and fallback to empty array
39
+ }
40
+ return [];
41
+ }
42
+
43
+ export function saveConfig(config) {
44
+ try {
45
+ const data = Array.isArray(config) ? config : [];
46
+ fs.writeFileSync(CONFIG_FILE, JSON.stringify(data, null, 2), 'utf8');
47
+ return true;
48
+ } catch (e) {
49
+ return false;
50
+ }
51
+ }
52
+
53
+ export function getProfiles() {
54
+ return loadConfig();
55
+ }
56
+
57
+ export function getActiveProfile() {
58
+ const profiles = getProfiles();
59
+ return profiles.length > 0 ? profiles[0] : null;
60
+ }
61
+
62
+ export function setActiveProfile(modelId) {
63
+ const profiles = getProfiles();
64
+ const idx = profiles.findIndex(p => p.model === modelId);
65
+ if (idx !== -1) {
66
+ const [active] = profiles.splice(idx, 1);
67
+ profiles.unshift(active); // Move to the first position
68
+ saveConfig(profiles);
69
+ return true;
70
+ }
71
+ return false;
72
+ }
73
+
74
+ export function updateProfile(modelId, updatedProfile) {
75
+ const profiles = getProfiles();
76
+ const idx = profiles.findIndex(p => p.model === modelId);
77
+ if (idx !== -1) {
78
+ profiles[idx] = { ...profiles[idx], ...updatedProfile };
79
+ saveConfig(profiles);
80
+ return true;
81
+ }
82
+ return false;
83
+ }
84
+
85
+ export function deleteProfile(modelId) {
86
+ const profiles = getProfiles();
87
+ const idx = profiles.findIndex(p => p.model === modelId);
88
+ if (idx !== -1) {
89
+ profiles.splice(idx, 1);
90
+ saveConfig(profiles);
91
+ return true;
92
+ }
93
+ return false;
94
+ }
95
+
96
+ export function resetConfig() {
97
+ try {
98
+ if (fs.existsSync(CONFIG_FILE)) {
99
+ fs.unlinkSync(CONFIG_FILE);
100
+ return true;
101
+ }
102
+ } catch (e) {
103
+ // Ignore
104
+ }
105
+ return false;
106
+ }
107
+
108
+ export function openConfigFileInEditor() {
109
+ let shouldWriteTemplate = false;
110
+
111
+ if (!fs.existsSync(CONFIG_FILE)) {
112
+ shouldWriteTemplate = true;
113
+ } else {
114
+ try {
115
+ const data = fs.readFileSync(CONFIG_FILE, 'utf8').trim();
116
+ if (data === '') {
117
+ shouldWriteTemplate = true;
118
+ } else {
119
+ const parsed = JSON.parse(data);
120
+ if (Array.isArray(parsed)) {
121
+ if (parsed.length === 0) {
122
+ shouldWriteTemplate = true;
123
+ } else {
124
+ const hasBlank = parsed.some(p => p && p.apiKey === 'YOUR_API_KEY');
125
+ // If they don't have a blank template AND don't have any configs at all, we write.
126
+ // But since parsed.length > 0, they have some configs. We don't overwrite if they have active configurations.
127
+ }
128
+ } else {
129
+ shouldWriteTemplate = true;
130
+ }
131
+ }
132
+ } catch (e) {
133
+ shouldWriteTemplate = true;
134
+ }
135
+ }
136
+
137
+ if (shouldWriteTemplate) {
138
+ saveConfig(TEMPLATE_CONFIG);
139
+ }
140
+
141
+ if (process.env.NODE_ENV === 'test') {
142
+ return; // Skip launching editor during automated test runs to avoid EBUSY lock issues
143
+ }
144
+
145
+ const command = process.platform === 'win32'
146
+ ? `start "" "${CONFIG_FILE}"`
147
+ : (process.platform === 'darwin' ? `open "${CONFIG_FILE}"` : `xdg-open "${CONFIG_FILE}"`);
148
+
149
+ exec(command, (err) => {
150
+ if (err) {
151
+ if (process.platform === 'win32') {
152
+ exec(`notepad "${CONFIG_FILE}"`);
153
+ }
154
+ }
155
+ });
156
+ }
157
+
158
+ export function getModel() {
159
+ const active = getActiveProfile();
160
+ return active ? active.model : '';
161
+ }
162
+
163
+ export function getApiKey() {
164
+ const active = getActiveProfile();
165
+ return active ? active.apiKey : '';
166
+ }
167
+
168
+ export function getApiBase() {
169
+ const active = getActiveProfile();
170
+ return active ? active.apiBase : '';
171
+ }
172
+
173
+ export const EFFORT_PRESETS = {
174
+ low: {
175
+ maxLoops: 3,
176
+ maxReadLines: 150,
177
+ label: 'low'
178
+ },
179
+ medium: {
180
+ maxLoops: 6,
181
+ maxReadLines: 300,
182
+ label: 'medium'
183
+ },
184
+ high: {
185
+ maxLoops: 12,
186
+ maxReadLines: 600,
187
+ label: 'high'
188
+ },
189
+ xhigh: {
190
+ maxLoops: 25,
191
+ maxReadLines: 1200,
192
+ label: 'xhigh'
193
+ },
194
+ max: {
195
+ maxLoops: 50,
196
+ maxReadLines: 2500,
197
+ label: 'max'
198
+ },
199
+ ultracode: {
200
+ maxLoops: 150,
201
+ maxReadLines: 8000,
202
+ label: 'ultracode',
203
+ useWorkflows: true
204
+ }
205
+ };
206
+
207
+ export function getActiveEffort() {
208
+ const active = getActiveProfile();
209
+ return active ? (active.effort || 'high') : 'high';
210
+ }
211
+
212
+ export function setActiveEffort(effort) {
213
+ const active = getActiveProfile();
214
+ if (active) {
215
+ updateProfile(active.model, { effort });
216
+ return true;
217
+ }
218
+ return false;
219
+ }
220
+