@rahul05ranjan/dhruv-cli 1.2.4 → 1.4.6

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.
Files changed (103) hide show
  1. package/.github/ENTERPRISE.md +275 -0
  2. package/.github/ISSUE_TEMPLATE/bug_report.md +45 -17
  3. package/.github/ISSUE_TEMPLATE/documentation_issue.md +61 -0
  4. package/.github/ISSUE_TEMPLATE/feature_request.md +61 -9
  5. package/.github/ISSUE_TEMPLATE/security_vulnerability.md +74 -0
  6. package/.github/dependabot.yml +42 -2
  7. package/.github/pull_request_template.md +13 -0
  8. package/.github/workflows/build-publish.yml +154 -0
  9. package/.github/workflows/ci.yml +251 -18
  10. package/.github/workflows/contribution.yml +169 -27
  11. package/.github/workflows/dependabot-auto-merge.yml +61 -2
  12. package/.github/workflows/deploy.yml +336 -0
  13. package/.github/workflows/monitoring.yml +270 -0
  14. package/.github/workflows/release.yml +229 -0
  15. package/.github/workflows/security.yml +198 -0
  16. package/.releaserc.json +50 -0
  17. package/AGENTS.md +13 -0
  18. package/CHANGELOG.md +8 -0
  19. package/PUBLISHING_FIX.md +92 -0
  20. package/__tests__/core.test.ts +318 -0
  21. package/__tests__/setup.ts +61 -0
  22. package/__tests__/workflows.test.ts +95 -0
  23. package/dist/commands/explain.js +13 -44
  24. package/dist/commands/fix.js +13 -38
  25. package/dist/commands/generate.js +45 -54
  26. package/dist/commands/health.d.ts +1 -0
  27. package/dist/commands/health.js +376 -0
  28. package/dist/commands/init.js +47 -42
  29. package/dist/commands/menu.js +90 -2
  30. package/dist/commands/metrics.d.ts +1 -0
  31. package/dist/commands/metrics.js +51 -0
  32. package/dist/commands/optimize.js +42 -34
  33. package/dist/commands/review.js +52 -48
  34. package/dist/commands/security-check.js +52 -42
  35. package/dist/commands/status.d.ts +1 -0
  36. package/dist/commands/status.js +45 -0
  37. package/dist/commands/suggest.js +13 -39
  38. package/dist/config/config.js +30 -2
  39. package/dist/core/ai.d.ts +77 -2
  40. package/dist/core/ai.js +207 -30
  41. package/dist/core/command-runner.d.ts +17 -0
  42. package/dist/core/command-runner.js +78 -0
  43. package/dist/core/logger.d.ts +40 -0
  44. package/dist/core/logger.js +138 -0
  45. package/dist/core/metrics.d.ts +34 -0
  46. package/dist/core/metrics.js +206 -0
  47. package/dist/core/prompts.d.ts +1 -0
  48. package/dist/core/prompts.js +121 -0
  49. package/dist/core/security.d.ts +34 -0
  50. package/dist/core/security.js +197 -0
  51. package/dist/index.js +40 -2
  52. package/dist/utils/ux.d.ts +3 -0
  53. package/dist/utils/ux.js +15 -0
  54. package/docs/agents/domain.md +51 -0
  55. package/docs/agents/issue-tracker.md +45 -0
  56. package/docs/agents/triage-labels.md +15 -0
  57. package/docs/api/.nojekyll +1 -0
  58. package/docs/api/assets/hierarchy.js +1 -0
  59. package/docs/api/assets/highlight.css +71 -0
  60. package/docs/api/assets/icons.js +18 -0
  61. package/docs/api/assets/icons.svg +1 -0
  62. package/docs/api/assets/main.js +60 -0
  63. package/docs/api/assets/navigation.js +1 -0
  64. package/docs/api/assets/search.js +1 -0
  65. package/docs/api/assets/style.css +1633 -0
  66. package/docs/api/hierarchy.html +1 -0
  67. package/docs/api/index.html +39 -0
  68. package/docs/api/modules.html +1 -0
  69. package/eslint.config.js +170 -0
  70. package/jest.config.json +37 -0
  71. package/lighthouserc.json +22 -0
  72. package/logs/.8a99b6cf655346317fdbf29f4fffcf91131432f3-audit.json +15 -0
  73. package/logs/.eee104bf8fff5ecd38a6a2842df260de6470a7c3-audit.json +15 -0
  74. package/package.json +62 -8
  75. package/src/commands/explain.ts +13 -42
  76. package/src/commands/fix.ts +13 -31
  77. package/src/commands/generate.ts +47 -46
  78. package/src/commands/health.ts +440 -0
  79. package/src/commands/init.ts +48 -42
  80. package/src/commands/menu.ts +86 -2
  81. package/src/commands/metrics.ts +65 -0
  82. package/src/commands/optimize.ts +40 -28
  83. package/src/commands/review.ts +54 -40
  84. package/src/commands/security-check.ts +54 -34
  85. package/src/commands/status.ts +47 -0
  86. package/src/commands/suggest.ts +13 -32
  87. package/src/config/config.ts +35 -2
  88. package/src/core/ai.ts +237 -26
  89. package/src/core/command-runner.ts +105 -0
  90. package/src/core/logger.ts +194 -0
  91. package/src/core/metrics.ts +232 -0
  92. package/src/core/prompts.ts +128 -0
  93. package/src/core/security.ts +243 -0
  94. package/src/index.ts +47 -2
  95. package/src/utils/ux.ts +18 -0
  96. package/test-suite.sh +147 -0
  97. package/tsconfig.json +3 -2
  98. package/typedoc.json +44 -0
  99. package/types/global.d.ts +13 -0
  100. package/validate-workflows.sh +270 -0
  101. package/.eslintignore +0 -1
  102. package/.eslintrc.cjs +0 -43
  103. package/src/core/ai.test.js +0 -40
@@ -1,62 +1,53 @@
1
- import { askOllama } from '../core/ai.js';
2
- import chalk from 'chalk';
3
- import { loadConfig } from '../config/config.js';
4
1
  import fs from 'fs';
5
- import { highlightCode, printError, printSuccess } from '../utils/ux.js';
2
+ import { runCommand } from '../core/command-runner.js';
3
+ import { getSystemMessage } from '../core/prompts.js';
4
+ import { printError, printSuccess } from '../utils/ux.js';
5
+ function buildPrompt(type, content) {
6
+ if (type === 'tests' || type === 'test') {
7
+ return `Generate comprehensive unit tests for the following JavaScript code. Use Jest or Mocha syntax. Only return the test code without explanations:\n\n${content}`;
8
+ }
9
+ if (type === 'documentation' || type === 'docs') {
10
+ return `Generate JSDoc documentation for the following code:\n\n${content}`;
11
+ }
12
+ return `Generate ${type} for this code:\n\n${content}`;
13
+ }
14
+ /** Extracts test code from the response: a fenced block if present, else the raw response. */
15
+ function extractTestCode(response) {
16
+ const fenced = response.match(/```(?:javascript|js)?\s*\n([\s\S]*?)```/);
17
+ if (fenced?.[1])
18
+ return fenced[1].trim();
19
+ return response
20
+ .replace(/^.*?(?=const|describe|test|it\s*\()/s, '')
21
+ .replace(/```[a-z]*\n?/g, '')
22
+ .trim();
23
+ }
6
24
  export async function generate(type, target) {
7
- const config = loadConfig();
8
- let content = '';
9
- if (fs.existsSync(target)) {
10
- content = fs.readFileSync(target, 'utf-8');
25
+ if (!fs.existsSync(target)) {
26
+ printError(`Target file "${target}" does not exist.`);
27
+ return;
11
28
  }
12
- let streamed = '';
13
- try {
14
- process.stdout.write(chalk.green('Generated code: '));
15
- await askOllama({
16
- prompt: `Generate only a valid JavaScript ${type} file for this code, no explanations, no Markdown, just the code.\n${content}`,
17
- model: config.model,
18
- onToken: (token) => {
19
- streamed += token;
20
- process.stdout.write(chalk.cyan(token));
21
- }
22
- });
23
- process.stdout.write('\n');
24
- // Robust code extraction for tests
25
- let codeToSave = streamed;
26
- if (type === 'tests' && target && typeof streamed === 'string') {
27
- const codeBlockMatch = streamed.match(/```(?:[a-z]*)?\n([\s\S]*?)```/);
28
- if (codeBlockMatch && codeBlockMatch[1]) {
29
- codeToSave = codeBlockMatch[1].trim();
30
- }
31
- else {
32
- // Remove Markdown, explanations, and keep only lines that look like code
33
- codeToSave = streamed
34
- .replace(/```[a-z]*\n|```/g, '') // remove code block markers
35
- .split('\n')
36
- .filter(line => line.trim() && !/^\s*#|^\s*\/\//.test(line) && /[;{}()=]/.test(line))
37
- .join('\n')
38
- .trim();
29
+ const content = fs.readFileSync(target, 'utf-8');
30
+ await runCommand({
31
+ name: 'generate',
32
+ input: { type, target },
33
+ header: `šŸ”Ø Generating ${type}: `,
34
+ buildRequest: (input, model) => ({
35
+ prompt: buildPrompt(input.type, content),
36
+ systemMessage: getSystemMessage('generate'),
37
+ model,
38
+ }),
39
+ onComplete: (response) => {
40
+ if (type !== 'tests' && type !== 'test')
41
+ return;
42
+ const codeToSave = extractTestCode(response);
43
+ if (!codeToSave) {
44
+ printError('No valid test code generated.');
45
+ return;
39
46
  }
40
47
  const testFile = target.replace(/\.[^.]+$/, '.test.js');
41
48
  fs.writeFileSync(testFile, codeToSave);
42
49
  printSuccess(`Test file saved: ${testFile}`);
43
- }
44
- if (typeof highlightCode === 'function' && streamed.match(/```[a-z]*[\s\S]*?```/)) {
45
- const codeBlocks = streamed.match(/```([a-z]*)\n([\s\S]*?)```/g) || [];
46
- for (const block of codeBlocks) {
47
- const [, lang, code] = block.match(/```([a-z]*)\n([\s\S]*?)```/) || [];
48
- if (code)
49
- try {
50
- console.log(highlightCode(code, lang || 'js'));
51
- }
52
- catch (err) {
53
- console.error('Highlight error:', err);
54
- }
55
- }
56
- }
57
- }
58
- catch (err) {
59
- printError('Failed to generate code.');
60
- console.error(chalk.red(err.message));
61
- }
50
+ },
51
+ footer: `šŸ” Want a review? Try: dhruv review ${target}`,
52
+ });
62
53
  }
@@ -0,0 +1 @@
1
+ export declare function health(): Promise<void>;
@@ -0,0 +1,376 @@
1
+ import chalk from 'chalk';
2
+ import { loadConfig } from '../config/config.js';
3
+ import { printError } from '../utils/ux.js';
4
+ import { logger } from '../core/logger.js';
5
+ import { metricsCollector } from '../core/metrics.js';
6
+ import { securityManager } from '../core/security.js';
7
+ import fs from 'fs';
8
+ import path from 'path';
9
+ import os from 'os';
10
+ export async function health() {
11
+ console.log(chalk.blue.bold('šŸ” Dhruv CLI Health Check\n'));
12
+ const results = [];
13
+ const startTime = Date.now();
14
+ try {
15
+ // System Information
16
+ const systemInfo = getSystemInfo();
17
+ console.log(chalk.cyan('šŸ“Š System Information:'));
18
+ Object.entries(systemInfo).forEach(([key, value]) => {
19
+ console.log(` ${key}: ${chalk.yellow(value)}`);
20
+ });
21
+ console.log();
22
+ // Configuration Check
23
+ results.push(...await checkConfiguration());
24
+ // Dependencies Check
25
+ results.push(...await checkDependencies());
26
+ // AI Service Check
27
+ results.push(...await checkAIService());
28
+ // Security Check
29
+ results.push(...await checkSecurity());
30
+ // Performance Check
31
+ results.push(...await checkPerformance());
32
+ // File System Check
33
+ results.push(...await checkFileSystem());
34
+ // Plugin System Check
35
+ results.push(...await checkPlugins());
36
+ // Display Results
37
+ displayResults(results);
38
+ const duration = Date.now() - startTime;
39
+ logger.info('Health check completed', { duration, results: results.length });
40
+ }
41
+ catch (error) {
42
+ printError('Health check failed');
43
+ console.error(chalk.red(error.message));
44
+ logger.error('Health check failed', error);
45
+ }
46
+ }
47
+ function getSystemInfo() {
48
+ const totalMem = os.totalmem();
49
+ const freeMem = os.freemem();
50
+ const uptime = os.uptime();
51
+ return {
52
+ platform: `${os.platform()} ${os.release()}`,
53
+ arch: os.arch(),
54
+ nodeVersion: process.version,
55
+ totalMemory: `${(totalMem / 1024 / 1024 / 1024).toFixed(2)} GB`,
56
+ freeMemory: `${(freeMem / 1024 / 1024 / 1024).toFixed(2)} GB`,
57
+ uptime: `${Math.floor(uptime / 3600)}h ${Math.floor((uptime % 3600) / 60)}m`,
58
+ cpuCount: os.cpus().length
59
+ };
60
+ }
61
+ async function checkConfiguration() {
62
+ const results = [];
63
+ try {
64
+ const config = loadConfig();
65
+ // Check if config file exists
66
+ const configPath = path.join(process.cwd(), '.dhruv-config.json');
67
+ if (fs.existsSync(configPath)) {
68
+ results.push({
69
+ category: 'Configuration',
70
+ status: 'pass',
71
+ message: 'Configuration file found and loaded successfully'
72
+ });
73
+ }
74
+ else {
75
+ results.push({
76
+ category: 'Configuration',
77
+ status: 'warn',
78
+ message: 'No configuration file found',
79
+ recommendation: 'Run "dhruv init" to create a configuration file'
80
+ });
81
+ }
82
+ // Check model configuration
83
+ if (config.model && config.model.trim().length > 0) {
84
+ results.push({
85
+ category: 'Configuration',
86
+ status: 'pass',
87
+ message: `Model configured: ${config.model}`
88
+ });
89
+ }
90
+ else {
91
+ results.push({
92
+ category: 'Configuration',
93
+ status: 'fail',
94
+ message: 'No model configured',
95
+ recommendation: 'Set a model using "dhruv init" or --model flag'
96
+ });
97
+ }
98
+ // Check theme configuration
99
+ if (config.theme && ['default', 'dark', 'light', 'mono'].includes(config.theme)) {
100
+ results.push({
101
+ category: 'Configuration',
102
+ status: 'pass',
103
+ message: `Theme configured: ${config.theme}`
104
+ });
105
+ }
106
+ else {
107
+ results.push({
108
+ category: 'Configuration',
109
+ status: 'warn',
110
+ message: 'Invalid or missing theme configuration',
111
+ recommendation: 'Set theme using "dhruv init"'
112
+ });
113
+ }
114
+ }
115
+ catch (error) {
116
+ results.push({
117
+ category: 'Configuration',
118
+ status: 'fail',
119
+ message: `Configuration check failed: ${error.message}`,
120
+ details: error
121
+ });
122
+ }
123
+ return results;
124
+ }
125
+ async function checkDependencies() {
126
+ const results = [];
127
+ try {
128
+ // Check package.json
129
+ const packagePath = path.join(process.cwd(), 'package.json');
130
+ if (fs.existsSync(packagePath)) {
131
+ const packageJson = JSON.parse(fs.readFileSync(packagePath, 'utf-8'));
132
+ results.push({
133
+ category: 'Dependencies',
134
+ status: 'pass',
135
+ message: `Package.json found with ${Object.keys(packageJson.dependencies || {}).length} dependencies`
136
+ });
137
+ }
138
+ else {
139
+ results.push({
140
+ category: 'Dependencies',
141
+ status: 'fail',
142
+ message: 'package.json not found',
143
+ recommendation: 'Ensure you are in the correct project directory'
144
+ });
145
+ }
146
+ // Check node_modules
147
+ const nodeModulesPath = path.join(process.cwd(), 'node_modules');
148
+ if (fs.existsSync(nodeModulesPath)) {
149
+ results.push({
150
+ category: 'Dependencies',
151
+ status: 'pass',
152
+ message: 'Node modules installed'
153
+ });
154
+ }
155
+ else {
156
+ results.push({
157
+ category: 'Dependencies',
158
+ status: 'fail',
159
+ message: 'Node modules not found',
160
+ recommendation: 'Run "npm install" to install dependencies'
161
+ });
162
+ }
163
+ }
164
+ catch (error) {
165
+ results.push({
166
+ category: 'Dependencies',
167
+ status: 'fail',
168
+ message: `Dependencies check failed: ${error.message}`,
169
+ details: error
170
+ });
171
+ }
172
+ return results;
173
+ }
174
+ async function checkAIService() {
175
+ const results = [];
176
+ try {
177
+ // Reuses the AI module's seam — no private Ollama connection here.
178
+ const { listModels } = await import('../core/ai.js');
179
+ await listModels();
180
+ results.push({
181
+ category: 'AI Service',
182
+ status: 'pass',
183
+ message: 'Ollama service is running and accessible'
184
+ });
185
+ }
186
+ catch (error) {
187
+ results.push({
188
+ category: 'AI Service',
189
+ status: 'fail',
190
+ message: 'Ollama service is not accessible',
191
+ details: error,
192
+ recommendation: 'Start Ollama with "ollama serve"'
193
+ });
194
+ }
195
+ return results;
196
+ }
197
+ async function checkSecurity() {
198
+ const results = [];
199
+ try {
200
+ const securityStatus = securityManager.getSecurityStatus();
201
+ results.push({
202
+ category: 'Security',
203
+ status: 'pass',
204
+ message: `Security manager active with ${securityStatus.blockedPatternsCount} patterns`,
205
+ details: {
206
+ inputValidation: securityStatus.config.enableInputValidation,
207
+ rateLimiting: securityStatus.config.enableRateLimiting,
208
+ activeRateLimits: securityStatus.activeRateLimits
209
+ }
210
+ });
211
+ }
212
+ catch (error) {
213
+ results.push({
214
+ category: 'Security',
215
+ status: 'fail',
216
+ message: `Security check failed: ${error.message}`,
217
+ details: error
218
+ });
219
+ }
220
+ return results;
221
+ }
222
+ async function checkPerformance() {
223
+ const results = [];
224
+ try {
225
+ // Memory usage check
226
+ const memUsage = process.memoryUsage();
227
+ const memUsageMB = memUsage.heapUsed / 1024 / 1024;
228
+ if (memUsageMB < 100) {
229
+ results.push({
230
+ category: 'Performance',
231
+ status: 'pass',
232
+ message: `Memory usage: ${memUsageMB.toFixed(2)} MB`
233
+ });
234
+ }
235
+ else if (memUsageMB < 500) {
236
+ results.push({
237
+ category: 'Performance',
238
+ status: 'warn',
239
+ message: `High memory usage: ${memUsageMB.toFixed(2)} MB`,
240
+ recommendation: 'Monitor memory usage during extended use'
241
+ });
242
+ }
243
+ else {
244
+ results.push({
245
+ category: 'Performance',
246
+ status: 'fail',
247
+ message: `Excessive memory usage: ${memUsageMB.toFixed(2)} MB`,
248
+ recommendation: 'Restart the application or investigate memory leaks'
249
+ });
250
+ }
251
+ // Update metrics
252
+ metricsCollector.updateMemoryUsage();
253
+ }
254
+ catch (error) {
255
+ results.push({
256
+ category: 'Performance',
257
+ status: 'fail',
258
+ message: `Performance check failed: ${error.message}`,
259
+ details: error
260
+ });
261
+ }
262
+ return results;
263
+ }
264
+ async function checkFileSystem() {
265
+ const results = [];
266
+ try {
267
+ // Check cache directory
268
+ const cacheDir = path.join(process.cwd(), '.dhruv-cache');
269
+ if (fs.existsSync(cacheDir)) {
270
+ const cacheFiles = fs.readdirSync(cacheDir);
271
+ results.push({
272
+ category: 'File System',
273
+ status: 'pass',
274
+ message: `Cache directory healthy with ${cacheFiles.length} files`
275
+ });
276
+ }
277
+ else {
278
+ results.push({
279
+ category: 'File System',
280
+ status: 'pass',
281
+ message: 'Cache directory will be created on first use'
282
+ });
283
+ }
284
+ // Check logs directory
285
+ const logsDir = path.join(process.cwd(), 'logs');
286
+ if (fs.existsSync(logsDir)) {
287
+ const logFiles = fs.readdirSync(logsDir);
288
+ results.push({
289
+ category: 'File System',
290
+ status: 'pass',
291
+ message: `Logs directory healthy with ${logFiles.length} files`
292
+ });
293
+ }
294
+ else {
295
+ results.push({
296
+ category: 'File System',
297
+ status: 'pass',
298
+ message: 'Logs directory will be created on first use'
299
+ });
300
+ }
301
+ }
302
+ catch (error) {
303
+ results.push({
304
+ category: 'File System',
305
+ status: 'fail',
306
+ message: `File system check failed: ${error.message}`,
307
+ details: error
308
+ });
309
+ }
310
+ return results;
311
+ }
312
+ async function checkPlugins() {
313
+ const results = [];
314
+ try {
315
+ const pluginsDir = path.join(process.cwd(), 'plugins');
316
+ if (fs.existsSync(pluginsDir)) {
317
+ const pluginFiles = fs.readdirSync(pluginsDir).filter(f => f.endsWith('.js'));
318
+ results.push({
319
+ category: 'Plugins',
320
+ status: 'pass',
321
+ message: `Plugin system active with ${pluginFiles.length} plugins`,
322
+ details: pluginFiles
323
+ });
324
+ }
325
+ else {
326
+ results.push({
327
+ category: 'Plugins',
328
+ status: 'pass',
329
+ message: 'Plugin system ready (no plugins installed)'
330
+ });
331
+ }
332
+ }
333
+ catch (error) {
334
+ results.push({
335
+ category: 'Plugins',
336
+ status: 'fail',
337
+ message: `Plugin check failed: ${error.message}`,
338
+ details: error
339
+ });
340
+ }
341
+ return results;
342
+ }
343
+ function displayResults(results) {
344
+ const categories = ['Configuration', 'Dependencies', 'AI Service', 'Security', 'Performance', 'File System', 'Plugins'];
345
+ categories.forEach(category => {
346
+ const categoryResults = results.filter(r => r.category === category);
347
+ if (categoryResults.length > 0) {
348
+ console.log(chalk.cyan(`\nšŸ“‹ ${category}:`));
349
+ categoryResults.forEach(result => {
350
+ const icon = result.status === 'pass' ? 'āœ…' : result.status === 'warn' ? 'āš ļø' : 'āŒ';
351
+ const color = result.status === 'pass' ? chalk.green : result.status === 'warn' ? chalk.yellow : chalk.red;
352
+ console.log(` ${icon} ${color(result.message)}`);
353
+ if (result.details) {
354
+ console.log(` ${chalk.gray(JSON.stringify(result.details, null, 2))}`);
355
+ }
356
+ if (result.recommendation) {
357
+ console.log(` šŸ’” ${chalk.blue(result.recommendation)}`);
358
+ }
359
+ });
360
+ }
361
+ });
362
+ // Summary
363
+ const summary = {
364
+ pass: results.filter(r => r.status === 'pass').length,
365
+ warn: results.filter(r => r.status === 'warn').length,
366
+ fail: results.filter(r => r.status === 'fail').length
367
+ };
368
+ console.log(chalk.blue.bold('\nšŸ“Š Summary:'));
369
+ console.log(` āœ… Passed: ${chalk.green(summary.pass)}`);
370
+ console.log(` āš ļø Warnings: ${chalk.yellow(summary.warn)}`);
371
+ console.log(` āŒ Failed: ${chalk.red(summary.fail)}`);
372
+ const overallStatus = summary.fail > 0 ? 'fail' : summary.warn > 0 ? 'warn' : 'pass';
373
+ const statusIcon = overallStatus === 'pass' ? 'šŸŽ‰' : overallStatus === 'warn' ? 'āš ļø' : 'āŒ';
374
+ const statusColor = overallStatus === 'pass' ? chalk.green : overallStatus === 'warn' ? chalk.yellow : chalk.red;
375
+ console.log(`\n${statusIcon} ${statusColor('Overall Status: ' + overallStatus.toUpperCase())}`);
376
+ }
@@ -1,54 +1,59 @@
1
1
  import inquirer from 'inquirer';
2
2
  import { saveConfig, loadConfig } from '../config/config.js';
3
3
  import chalk from 'chalk';
4
+ import { listModels } from '../core/ai.js';
4
5
  export async function init() {
5
6
  const current = loadConfig();
6
7
  let modelChoices = [current.model];
7
8
  try {
8
- // Dynamically import node-fetch for compatibility
9
- // eslint-disable-next-line @typescript-eslint/no-explicit-any
10
- const fetch = (await import('node-fetch')).default;
11
- // Fetch models from Ollama REST API
12
- const res = await fetch('http://localhost:11434/api/tags');
13
- if (res.ok) {
14
- const data = (await res.json());
15
- if (Array.isArray(data.models) && data.models.length > 0) {
16
- modelChoices = data.models.map((m) => m.name);
17
- }
9
+ const models = await listModels();
10
+ if (models.length > 0) {
11
+ modelChoices = models;
18
12
  }
19
13
  }
20
14
  catch {
21
- // If Ollama is not running or fails, fallback to current model
15
+ console.log(chalk.yellow('Warning: Could not fetch available models from Ollama.'));
16
+ console.log(chalk.yellow('Using default model choices.'));
17
+ }
18
+ try {
19
+ const answers = await inquirer.prompt([
20
+ {
21
+ type: 'list',
22
+ name: 'model',
23
+ message: 'Which Ollama model do you want to use?',
24
+ choices: modelChoices,
25
+ default: current.model,
26
+ },
27
+ {
28
+ type: 'list',
29
+ name: 'responseFormat',
30
+ message: 'Preferred response format?',
31
+ choices: ['text', 'json', 'markdown'],
32
+ default: current.responseFormat,
33
+ },
34
+ {
35
+ type: 'confirm',
36
+ name: 'verbose',
37
+ message: 'Enable verbose output?',
38
+ default: current.verbose,
39
+ },
40
+ {
41
+ type: 'list',
42
+ name: 'theme',
43
+ message: 'Choose a color theme:',
44
+ choices: ['default', 'dark', 'light', 'mono'],
45
+ default: current.theme || 'default',
46
+ },
47
+ ]);
48
+ saveConfig(answers);
49
+ console.log(chalk.green('Configuration saved!'));
50
+ }
51
+ catch (error) {
52
+ if (error?.isTtyError) {
53
+ console.log(chalk.red('This command requires an interactive terminal.'));
54
+ }
55
+ else {
56
+ console.log(chalk.red('Configuration cancelled or failed.'));
57
+ }
22
58
  }
23
- const answers = await inquirer.prompt([
24
- {
25
- type: 'list',
26
- name: 'model',
27
- message: 'Which Ollama model do you want to use?',
28
- choices: modelChoices,
29
- default: current.model,
30
- },
31
- {
32
- type: 'list',
33
- name: 'responseFormat',
34
- message: 'Preferred response format?',
35
- choices: ['text', 'json', 'markdown'],
36
- default: current.responseFormat,
37
- },
38
- {
39
- type: 'confirm',
40
- name: 'verbose',
41
- message: 'Enable verbose output?',
42
- default: current.verbose,
43
- },
44
- {
45
- type: 'list',
46
- name: 'theme',
47
- message: 'Choose a color theme:',
48
- choices: ['default', 'dark', 'light', 'mono'],
49
- default: current.theme || 'default',
50
- },
51
- ]);
52
- saveConfig(answers);
53
- console.log(chalk.green('Configuration saved!'));
54
59
  }