gaunt-sloth-assistant 0.0.7 → 0.1.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.
@@ -0,0 +1,137 @@
1
+ import * as td from 'testdouble';
2
+
3
+ describe('questionAnsweringModule', function (){
4
+
5
+ beforeEach(async function() {
6
+ // Reset testdouble before each test
7
+ td.reset();
8
+
9
+ // Create a mock context
10
+ this.mockLlmInvoke = td.function();
11
+ this.context = {
12
+ config: {
13
+ llm: {
14
+ invoke: this.mockLlmInvoke
15
+ }
16
+ },
17
+ session: {configurable: {thread_id: 'test-thread-id'}}
18
+ };
19
+
20
+ // Create fs mock
21
+ this.fs = {
22
+ writeFileSync: td.function()
23
+ };
24
+
25
+ // Create path mock
26
+ this.path = {
27
+ resolve: td.function()
28
+ };
29
+
30
+ // Create consoleUtils mock
31
+ this.consoleUtils = {
32
+ display: td.function(),
33
+ displaySuccess: td.function(),
34
+ displayError: td.function()
35
+ };
36
+
37
+ // Create utils mock functions
38
+ const extractLastMessageContent = td.function();
39
+ const toFileSafeString = td.function();
40
+ const fileSafeLocalDate = td.function();
41
+ const ProgressIndicator = td.constructor();
42
+ const readFileSyncWithMessages = td.function();
43
+ const spawnCommand = td.function();
44
+
45
+ // Set up utils mock stubs
46
+ td.when(extractLastMessageContent(td.matchers.anything())).thenReturn('LLM Response');
47
+ td.when(toFileSafeString(td.matchers.anything())).thenReturn('sloth-ASK');
48
+ td.when(fileSafeLocalDate()).thenReturn('2025-01-01T00-00-00');
49
+
50
+ // Create the utils mock
51
+ this.utils = {
52
+ extractLastMessageContent,
53
+ toFileSafeString,
54
+ fileSafeLocalDate,
55
+ ProgressIndicator,
56
+ readFileSyncWithMessages,
57
+ spawnCommand
58
+ };
59
+
60
+ // Set up path.resolve mock
61
+ td.when(this.path.resolve(td.matchers.anything(), td.matchers.contains('sloth-ASK'))).thenReturn('test-file-path.md');
62
+
63
+ // Mock ProgressIndicator
64
+ this.progressIndicator = {
65
+ indicate: td.function()
66
+ };
67
+ td.when(new this.utils.ProgressIndicator(td.matchers.anything())).thenReturn(this.progressIndicator);
68
+
69
+ // Replace modules with mocks - do this after setting up all mocks
70
+ await td.replaceEsm("node:fs", this.fs);
71
+ await td.replaceEsm("node:path", this.path);
72
+ await td.replaceEsm("../src/consoleUtils.js", this.consoleUtils);
73
+ await td.replaceEsm("../src/utils.js", this.utils);
74
+
75
+ // Mock slothContext and other config exports
76
+ await td.replaceEsm("../src/config.js", {
77
+ slothContext: this.context,
78
+ SLOTH_INTERNAL_PREAMBLE: '.gsloth.preamble.internal.md',
79
+ USER_PROJECT_REVIEW_PREAMBLE: '.gsloth.preamble.review.md',
80
+ USER_PROJECT_CONFIG_FILE: '.gsloth.config.js',
81
+ initConfig: td.function()
82
+ });
83
+ });
84
+
85
+ it('Should call LLM with correct messages', async function() {
86
+ // Mock the LLM response
87
+ const llmResponse = [{ role: 'assistant', content: 'LLM Response' }];
88
+ td.when(this.mockLlmInvoke(td.matchers.anything())).thenResolve(llmResponse);
89
+
90
+ // Import the module after setting up mocks
91
+ const { askQuestionInner } = await import("../src/modules/questionAnsweringModule.js");
92
+
93
+ // Call the function
94
+ const result = await askQuestionInner(this.context, () => {}, 'Test Preamble', 'Test Content');
95
+
96
+ // Verify the result
97
+ expect(result).toBe('LLM Response');
98
+ });
99
+
100
+ it('Should write output to file', async function() {
101
+ // Mock the LLM response
102
+ const llmResponse = [{ role: 'assistant', content: 'LLM Response' }];
103
+ td.when(this.mockLlmInvoke(td.matchers.anything())).thenResolve(llmResponse);
104
+
105
+ // Import the module after setting up mocks
106
+ const { askQuestion } = await import("../src/modules/questionAnsweringModule.js");
107
+
108
+ // Call the function and wait for it to complete
109
+ await askQuestion('sloth-ASK', 'Test Preamble', 'Test Content');
110
+
111
+ // Verify the file was written with the correct content
112
+ td.verify(this.fs.writeFileSync('test-file-path.md', 'LLM Response'));
113
+
114
+ // Verify success message was displayed
115
+ td.verify(this.consoleUtils.displaySuccess(td.matchers.contains('test-file-path.md')));
116
+ });
117
+
118
+ it('Should handle file write errors', async function() {
119
+ // Mock the LLM response
120
+ const llmResponse = [{ role: 'assistant', content: 'LLM Response' }];
121
+ td.when(this.mockLlmInvoke(td.matchers.anything())).thenResolve(llmResponse);
122
+
123
+ // Mock file write to throw an error
124
+ const error = new Error('File write error');
125
+ td.when(this.fs.writeFileSync('test-file-path.md', 'LLM Response')).thenThrow(error);
126
+
127
+ // Import the module after setting up mocks
128
+ const { askQuestion } = await import("../src/modules/questionAnsweringModule.js");
129
+
130
+ // Call the function and wait for it to complete
131
+ await askQuestion('sloth-ASK', 'Test Preamble', 'Test Content');
132
+
133
+ // Verify error message was displayed
134
+ td.verify(this.consoleUtils.displayError(td.matchers.contains('test-file-path.md')));
135
+ td.verify(this.consoleUtils.displayError('File write error'));
136
+ });
137
+ });
@@ -0,0 +1,144 @@
1
+ import {Command} from 'commander';
2
+ import * as td from 'testdouble';
3
+
4
+ describe('reviewCommand', function (){
5
+
6
+ beforeEach(async function() {
7
+ this.review = td.function();
8
+ this.prompt = await td.replaceEsm("../src/prompt.js");
9
+ td.when(this.prompt.readInternalPreamble()).thenReturn("INTERNAL PREAMBLE");
10
+ td.when(this.prompt.readPreamble(".gsloth.preamble.review.md")).thenReturn("PROJECT PREAMBLE");
11
+ this.codeReviewMock = await td.replaceEsm("../src/modules/reviewModule.js");
12
+ await td.replaceEsm("../src/config.js");
13
+ this.utils = await td.replaceEsm("../src/utils.js");
14
+ td.when(this.utils.readFileFromCurrentDir("test.file")).thenReturn("FILE TO REVIEW");
15
+ td.when(this.codeReviewMock.review(
16
+ 'sloth-DIFF-review',
17
+ td.matchers.anything(),
18
+ td.matchers.anything())
19
+ ).thenDo(this.review);
20
+ });
21
+
22
+ it('Should call review with file contents', async function() {
23
+ const { reviewCommand } = await import("../src/commands/reviewCommand.js");
24
+ const program = new Command()
25
+ await reviewCommand(program, {});
26
+ await program.parseAsync(['na', 'na', 'review', '-f', 'test.file']);
27
+ td.verify(this.review(
28
+ 'sloth-DIFF-review',
29
+ "INTERNAL PREAMBLE\nPROJECT PREAMBLE",
30
+ "test.file:\n```\nFILE TO REVIEW\n```")
31
+ );
32
+ });
33
+
34
+ it('Should display predefined providers in help', async function() {
35
+ const { reviewCommand } = await import("../src/commands/reviewCommand.js");
36
+ const program = new Command();
37
+ const testOutput = { text: '' };
38
+
39
+ program.configureOutput({
40
+ writeOut: (str) => testOutput.text += str,
41
+ writeErr: (str) => testOutput.text += str
42
+ });
43
+
44
+ await reviewCommand(program, {});
45
+
46
+ const commandUnderTest = program.commands.find(c => c.name() == 'review');
47
+ expect(commandUnderTest).toBeDefined();
48
+ commandUnderTest.outputHelp();
49
+
50
+ // Verify content providers are displayed
51
+ expect(testOutput.text).toContain('--content-provider <contentProvider>');
52
+ expect(testOutput.text).toContain('(choices: "gh", "text")');
53
+
54
+ // Verify requirements providers are displayed
55
+ expect(testOutput.text).toContain('--requirements-provider <requirementsProvider>');
56
+ expect(testOutput.text).toContain('(choices: "jira-legacy", "text")');
57
+ });
58
+
59
+ it('Should call review with predefined requirements provider', async function() {
60
+ const { reviewCommand } = await import("../src/commands/reviewCommand.js");
61
+ const program = new Command();
62
+ const context = {
63
+ config: {
64
+ requirementsProvider: 'jira-legacy',
65
+ requirementsProviderConfig: {
66
+ 'jira-legacy': {
67
+ username: 'test-user',
68
+ token: 'test-token',
69
+ baseUrl: 'https://test-jira.atlassian.net/rest/api/2/issue/'
70
+ }
71
+ }
72
+ }
73
+ };
74
+
75
+ // Mock the jira provider
76
+ const jiraProvider = td.func();
77
+ td.when(jiraProvider(td.matchers.anything(), 'JIRA-123')).thenResolve('JIRA Requirements');
78
+
79
+ // Replace the dynamic import with our mock
80
+ await td.replaceEsm('../src/providers/jiraIssueLegacyAccessTokenProvider.js', {
81
+ get: jiraProvider
82
+ });
83
+
84
+ await reviewCommand(program, context);
85
+ await program.parseAsync(['na', 'na', 'review', 'content-id', '-r', 'JIRA-123']);
86
+
87
+ td.verify(this.review('sloth-DIFF-review', "INTERNAL PREAMBLE\nPROJECT PREAMBLE", "JIRA Requirements"));
88
+ });
89
+
90
+ it('Should call review with predefined content provider', async function() {
91
+ const { reviewCommand } = await import("../src/commands/reviewCommand.js");
92
+ const program = new Command();
93
+ const context = {
94
+ config: {
95
+ contentProvider: 'gh'
96
+ }
97
+ };
98
+
99
+ // Mock the gh provider
100
+ const ghProvider = td.func();
101
+ td.when(ghProvider(td.matchers.anything(), '123')).thenResolve('PR Diff Content');
102
+
103
+ // Replace the dynamic import with our mock
104
+ await td.replaceEsm('../src/providers/ghPrDiffProvider.js', {
105
+ get: ghProvider
106
+ });
107
+
108
+ await reviewCommand(program, context);
109
+ await program.parseAsync(['na', 'na', 'review', '123']);
110
+
111
+ td.verify(this.review('sloth-DIFF-review', "INTERNAL PREAMBLE\nPROJECT PREAMBLE", "PR Diff Content"));
112
+ });
113
+
114
+ it('Should call pr command', async function() {
115
+ // Create a spy for the review function
116
+ const reviewSpy = td.func();
117
+
118
+ // Replace the review function in the codeReviewMock
119
+ this.codeReviewMock.review = reviewSpy;
120
+
121
+ // Mock the modules/reviewModule.js import in the reviewCommand.js file
122
+ await td.replaceEsm('../src/modules/reviewModule.js', {
123
+ review: reviewSpy
124
+ });
125
+
126
+ const { reviewCommand } = await import("../src/commands/reviewCommand.js");
127
+ const program = new Command();
128
+ const context = {};
129
+
130
+ // Mock the gh provider
131
+ const ghProvider = td.func();
132
+ td.when(ghProvider('123')).thenResolve('PR Diff Content');
133
+
134
+ // Replace the dynamic import with our mock
135
+ await td.replaceEsm('../src/providers/ghPrDiffProvider.js', {
136
+ get: ghProvider
137
+ });
138
+
139
+ await reviewCommand(program, context);
140
+ await program.parseAsync(['na', 'na', 'pr', '123']);
141
+
142
+ td.verify(reviewSpy('sloth-PR-123-review', "INTERNAL PREAMBLE\nPROJECT PREAMBLE", "PR Diff Content"));
143
+ });
144
+ });
@@ -0,0 +1,22 @@
1
+ import { reviewInner } from '../src/modules/reviewModule.js';
2
+ import { slothContext } from '../src/config.js';
3
+ import { FakeListChatModel } from "@langchain/core/utils/testing";
4
+
5
+ describe('reviewModule', () => {
6
+
7
+ it('should invoke LLM', async () => {
8
+ // Setup mock for slothContext
9
+ const testContext = {...slothContext,
10
+ config: {
11
+ llm: new FakeListChatModel({
12
+ responses: ["First LLM message", "Second LLM message"],
13
+ })
14
+ }
15
+ };
16
+
17
+ // Test the function
18
+ const output = await reviewInner(testContext, () => {}, 'test-preamble', 'test-diff');
19
+ expect(output).toBe("First LLM message");
20
+ });
21
+
22
+ });
@@ -0,0 +1,14 @@
1
+ export default {
2
+ spec_dir: "spec",
3
+ spec_files: [
4
+ "**/*[sS]pec.?(m)js"
5
+ ],
6
+ helpers: [
7
+ "helpers/**/*.?(m)js"
8
+ ],
9
+ env: {
10
+ stopSpecOnExpectationFailure: false,
11
+ random: true,
12
+ forbidDuplicateNames: true
13
+ }
14
+ }
@@ -0,0 +1,26 @@
1
+ import { readInternalPreamble } from "../prompt.js";
2
+ import { readFileFromCurrentDir } from "../utils.js";
3
+ import { initConfig } from "../config.js";
4
+
5
+ /**
6
+ * Adds the ask command to the program
7
+ * @param {Object} program - The commander program
8
+ * @param {Object} context - The context object
9
+ */
10
+ export function askCommand(program, context) {
11
+ program.command('ask')
12
+ .description('Ask a question')
13
+ .argument('<message>', 'A message')
14
+ .option('-f, --file <file>', 'Input file. Content of this file will be added BEFORE the diff')
15
+ // TODO add option consuming extra message as argument
16
+ .action(async (message, options) => {
17
+ const preamble = [readInternalPreamble()];
18
+ const content = [message];
19
+ if (options.file) {
20
+ content.push(readFileFromCurrentDir(options.file));
21
+ }
22
+ await initConfig();
23
+ const { askQuestion } = await import('../modules/questionAnsweringModule.js');
24
+ await askQuestion('sloth-ASK', preamble.join("\n"), content.join("\n"));
25
+ });
26
+ }
@@ -0,0 +1,16 @@
1
+ import { Argument } from 'commander';
2
+ import { availableDefaultConfigs, createProjectConfig } from "../config.js";
3
+
4
+ /**
5
+ * Adds the init command to the program
6
+ * @param {Object} program - The commander program
7
+ * @param {Object} context - The context object
8
+ */
9
+ export function initCommand(program, context) {
10
+ program.command('init')
11
+ .description('Initialize the Gaunt Sloth Assistant in your project. This will write necessary config files.')
12
+ .addArgument(new Argument('<type>', 'Config type').choices(availableDefaultConfigs))
13
+ .action(async (config) => {
14
+ await createProjectConfig(config);
15
+ });
16
+ }
@@ -0,0 +1,147 @@
1
+ import {Option} from 'commander';
2
+ import {USER_PROJECT_REVIEW_PREAMBLE} from "../config.js";
3
+ import {readInternalPreamble, readPreamble} from "../prompt.js";
4
+ import {readFileFromCurrentDir} from "../utils.js";
5
+ import {displayError} from "../consoleUtils.js";
6
+
7
+ /**
8
+ * Requirements providers. Expected to be in `.providers/` dir
9
+ */
10
+ const REQUIREMENTS_PROVIDERS = {
11
+ 'jira-legacy': 'jiraIssueLegacyAccessTokenProvider.js',
12
+ 'text': 'text.js'
13
+ };
14
+
15
+ /**
16
+ * Content providers. Expected to be in `.providers/` dir
17
+ */
18
+ const CONTENT_PROVIDERS = {
19
+ 'gh': 'ghPrDiffProvider.js',
20
+ 'text': 'text.js'
21
+ };
22
+
23
+ export function reviewCommand(program, context) {
24
+
25
+ program.command('review')
26
+ .description('Review provided diff or other content')
27
+ .argument('[contentId]', 'Optional content ID argument to retrieve content with content provider')
28
+ .alias('r')
29
+ // TODO add provider to get results of git --no-pager diff
30
+ // TODO add support to include multiple files
31
+ .option('-f, --file <file>', 'Input file. Content of this file will be added BEFORE the diff, but after requirements')
32
+ // TODO figure out what to do with this (we probably want to merge it with requirementsId)?
33
+ .option('-r, --requirements <requirements>', 'Requirements for this review.')
34
+ .addOption(
35
+ new Option('-p, --requirements-provider <requirementsProvider>', 'Requirements provider for this review.')
36
+ .choices(Object.keys(REQUIREMENTS_PROVIDERS))
37
+ )
38
+ .addOption(
39
+ new Option('--content-provider <contentProvider>', 'Content provider')
40
+ .choices(Object.keys(CONTENT_PROVIDERS))
41
+ )
42
+ .option('-m, --message <message>', 'Extra message to provide just before the content')
43
+ .action(async (contentId, options) => {
44
+ const {initConfig} = await import("../config.js");
45
+ await initConfig();
46
+ const preamble = [readInternalPreamble(), readPreamble(USER_PROJECT_REVIEW_PREAMBLE)];
47
+ const content = [];
48
+ const requirementsId = options.requirements;
49
+ const requirementsProvider = options.requirementsProvider ?? context.config?.requirementsProvider;
50
+ const contentProvider = options.contentProvider ?? context.config?.contentProvider;
51
+
52
+ // TODO consider calling these in parallel
53
+ const requirements = await getRequirementsFromProvider(requirementsProvider, requirementsId);
54
+ if (requirements) {
55
+ content.push(requirements);
56
+ }
57
+
58
+ const providedContent = await getContentFromProvider(contentProvider, contentId);
59
+ if (providedContent) {
60
+ content.push(providedContent);
61
+ }
62
+
63
+ if (options.file) {
64
+ content.push(`${options.file}:\n\`\`\`\n${readFileFromCurrentDir(options.file)}\n\`\`\``);
65
+ }
66
+ if (context.stdin) {
67
+ content.push(context.stdin);
68
+ }
69
+ if (options.message) {
70
+ content.push(options.message);
71
+ }
72
+ const {review} = await import('../modules/reviewModule.js');
73
+ await review('sloth-DIFF-review', preamble.join("\n"), content.join("\n"));
74
+ });
75
+
76
+ program.command('pr')
77
+ .description('Review provided Pull Request in current directory. ' +
78
+ 'This command is similar to `review`, but default content provider is `gh`. ' +
79
+ '(assuming that GH cli is installed and authenticated for current project')
80
+ .argument('<prId>', 'Pull request ID to review.')
81
+ .argument('[requirementsId]', 'Optional requirements ID argument to retrieve requirements with requirements provider')
82
+ .addOption(
83
+ new Option('-p, --requirements-provider <requirementsProvider>', 'Requirements provider for this review.')
84
+ .choices(Object.keys(REQUIREMENTS_PROVIDERS))
85
+ )
86
+ .option('-f, --file <file>', 'Input file. Content of this file will be added BEFORE the diff, but after requirements')
87
+ .action(async (prId, requirementsId, options) => {
88
+ const {initConfig} = await import("../config.js");
89
+ await initConfig();
90
+
91
+ const preamble = [readInternalPreamble(), readPreamble(USER_PROJECT_REVIEW_PREAMBLE)];
92
+ const content = [];
93
+ const requirementsProvider = options.requirementsProvider ?? context.config?.requirementsProvider;
94
+
95
+ // Handle requirements
96
+ const requirements = await getRequirementsFromProvider(requirementsProvider, requirementsId);
97
+ if (requirements) {
98
+ content.push(requirements);
99
+ }
100
+
101
+ if (options.file) {
102
+ content.push(`${options.file}:\n\`\`\`\n${readFileFromCurrentDir(options.file)}\n\`\`\``);
103
+ }
104
+
105
+ // Get PR diff using the 'gh' provider
106
+ const providerPath = `../providers/${CONTENT_PROVIDERS['gh']}`;
107
+ const {get} = await import(providerPath);
108
+ content.push(await get(prId));
109
+
110
+ const {review} = await import('../modules/reviewModule.js');
111
+ await review(`sloth-PR-${prId}-review`, preamble.join("\n"), content.join("\n"));
112
+ });
113
+
114
+ async function getRequirementsFromProvider(requirementsProvider, requirementsId) {
115
+ return getFromProvider(
116
+ requirementsProvider,
117
+ requirementsId,
118
+ (context.config?.requirementsProviderConfig ?? {})[requirementsProvider],
119
+ REQUIREMENTS_PROVIDERS
120
+ );
121
+ }
122
+
123
+ async function getContentFromProvider(contentProvider, contentId) {
124
+ return getFromProvider(
125
+ contentProvider,
126
+ contentId,
127
+ (context.config?.contentProviderConfig ?? {})[contentProvider],
128
+ CONTENT_PROVIDERS
129
+ )
130
+ }
131
+
132
+ async function getFromProvider(provider, id, config, legitPredefinedProviders) {
133
+ if (typeof provider === 'string') {
134
+ // Use one of the predefined providers
135
+ if (legitPredefinedProviders[provider]) {
136
+ const providerPath = `../providers/${legitPredefinedProviders[provider]}`;
137
+ const {get} = await import(providerPath);
138
+ return await get(config, id);
139
+ } else {
140
+ displayError(`Unknown provider: ${provider}. Continuing without it.`);
141
+ }
142
+ } else if (typeof provider === 'function') {
143
+ return await provider(id);
144
+ }
145
+ return '';
146
+ }
147
+ }
package/src/config.js CHANGED
@@ -1,16 +1,14 @@
1
- import path, {dirname} from "node:path";
1
+ import path from "node:path";
2
2
  import url from "node:url";
3
- import {v4 as uuidv4} from "uuid";
4
- import {display, displayError, displayInfo, displaySuccess, displayWarning} from "./consoleUtils.js";
5
- import {fileURLToPath} from "url";
6
- import {write, writeFileSync, existsSync} from "node:fs";
7
- import {writeFileIfNotExistsWithMessages} from "./utils.js";
3
+ import { v4 as uuidv4 } from "uuid";
4
+ import { displayError, displayInfo, displayWarning } from "./consoleUtils.js";
5
+ import { writeFileIfNotExistsWithMessages } from "./utils.js";
8
6
 
9
7
  export const USER_PROJECT_CONFIG_FILE = '.gsloth.config.js'
10
8
  export const SLOTH_INTERNAL_PREAMBLE = '.gsloth.preamble.internal.md';
11
9
  export const USER_PROJECT_REVIEW_PREAMBLE = '.gsloth.preamble.review.md';
12
10
 
13
- export const availableDefaultConfigs = ['vertexai', 'anthropic'];
11
+ export const availableDefaultConfigs = ['vertexai', 'anthropic', 'groq'];
14
12
 
15
13
  export const slothContext = {
16
14
  /**
@@ -30,9 +28,16 @@ export const slothContext = {
30
28
 
31
29
  export async function initConfig() {
32
30
  const configFileUrl = url.pathToFileURL(path.join(process.cwd(), USER_PROJECT_CONFIG_FILE));
33
- const {configure} = await import(configFileUrl);
34
- const config = await configure((module) => import(module));
35
- slothContext.config = {...config};
31
+ return import(configFileUrl)
32
+ .then((i) => i.configure((module) => import(module)))
33
+ .then((config) => {
34
+ slothContext.config = {...config};
35
+ })
36
+ .catch((e) => {
37
+ console.log(e);
38
+ displayError(`Failed to read config, make sure ${configFileUrl} contains valid JavaScript.`);
39
+ process.exit();
40
+ });
36
41
  }
37
42
 
38
43
  export async function createProjectConfig(configType) {
@@ -11,7 +11,7 @@ export async function configure(importFunction, global) {
11
11
  const anthropic = await importFunction('@langchain/anthropic');
12
12
  return {
13
13
  llm: new anthropic.ChatAnthropic({
14
- apiKey: "sk-ant-api--YOUR_API_HASH", // You should put your API hash here
14
+ apiKey: process.env.ANTHROPIC_API_KEY, // Default value, but you can provide the key in many different ways, even as literal
15
15
  model: "claude-3-5-sonnet-20241022" // Don't forget to check new models availability.
16
16
  })
17
17
  };
@@ -0,0 +1,25 @@
1
+ import {writeFileIfNotExistsWithMessages} from "../utils.js";
2
+ import path from "node:path";
3
+ import {displayInfo, displayWarning} from "../consoleUtils.js";
4
+ import {USER_PROJECT_CONFIG_FILE} from "../config.js";
5
+
6
+ const content = `/* eslint-disable */
7
+ export async function configure(importFunction, global) {
8
+ // this is going to be imported from sloth dependencies,
9
+ // but can potentially be pulled from global node modules or from this project
10
+ const groq = await importFunction('@langchain/groq');
11
+ return {
12
+ llm: new groq.ChatGroq({
13
+ model: "deepseek-r1-distill-llama-70b", // Check other models available
14
+ apiKey: process.env.GROQ_API_KEY, // Default value, but you can provide the key in many different ways, even as literal
15
+ })
16
+ };
17
+ }
18
+ `;
19
+
20
+ export function init(configFileName, context) {
21
+ path.join(context.currentDir, configFileName);
22
+ writeFileIfNotExistsWithMessages(configFileName, content);
23
+ displayInfo(`You can define GROQ_API_KEY environment variable with your Groq API key and it will work with default model.`);
24
+ displayWarning(`You need to edit your ${USER_PROJECT_CONFIG_FILE} to to configure model.`);
25
+ }
@@ -1,5 +1,7 @@
1
1
  import chalk from 'chalk';
2
2
 
3
+ // TODO it seems like commander supports coloured output, maybe dependency to chalk can be removed
4
+
3
5
  export function displayError (message) {
4
6
  console.error(chalk.red(message));
5
7
  }