scai 0.1.11 → 0.1.12
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 +21 -31
- package/dist/commands/CommitSuggesterCmd.refactored.js +49 -59
- package/dist/commands/EnvCmd.test.js +146 -0
- package/dist/commands/SummaryCmd.js +12 -0
- package/dist/config/ModelConfig.js +16 -0
- package/dist/index.js +31 -15
- package/dist/pipeline/modules/commentModule.js +6 -3
- package/dist/pipeline/modules/generateTestsModule.js +5 -2
- package/dist/pipeline/modules/refactorModule.js +31 -11
- package/dist/pipeline/modules/summaryModule.js +22 -11
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -1,25 +1,26 @@
|
|
|
1
1
|
# ⚙️ scai — Smart Commit AI ✨
|
|
2
2
|
|
|
3
|
-
> AI-powered CLI tools for smart commit messages,
|
|
3
|
+
> AI-powered CLI tools for smart commit messages, auto generated comments, and readme files — all powered by local models.
|
|
4
4
|
|
|
5
5
|
**scai** (Smart Commit AI) is a lightweight, privacy-focused CLI tool that uses local AI models (via [Ollama](https://ollama.com)) to help developers work faster and cleaner:
|
|
6
6
|
|
|
7
7
|
- 🤖 Suggest high-quality Git commit messages
|
|
8
8
|
- ✨ Comments your code automatically
|
|
9
|
-
- 🧠
|
|
9
|
+
- 🧠 Get a summary of any code file directly to the terminal
|
|
10
10
|
- 🔒 100% local — no API keys, no cloud, no telemetry
|
|
11
11
|
|
|
12
12
|
---
|
|
13
13
|
|
|
14
|
-
**scai** follows the Unix philosophy — small, composable tools with powerful output.
|
|
15
|
-
|
|
16
14
|
## 🚀 Features
|
|
17
15
|
|
|
18
|
-
-
|
|
19
|
-
-
|
|
20
|
-
- ⚡️ Powered by open local Ollama models like `mistral`
|
|
21
|
-
- 🛠️ CLI built with Node.js + TypeScript
|
|
16
|
+
- ⚡️ Powered by open local Ollama models like `mistral` and new `commentModule`
|
|
17
|
+
- 🛠️ CLI built with Node.js + TypeScript
|
|
22
18
|
- 🔒 No external services, full privacy by design
|
|
19
|
+
- ✅ Now supports global options for model and language settings
|
|
20
|
+
|
|
21
|
+
### 📦 Recent Additions
|
|
22
|
+
|
|
23
|
+
* **Summary Command**: Print a summary of the given file to the terminal (e.g., `scai summ <file>`)
|
|
23
24
|
|
|
24
25
|
---
|
|
25
26
|
|
|
@@ -34,6 +35,8 @@ No internet connection. No vendor lock-in. Just local, private, AI-enhanced deve
|
|
|
34
35
|
✅ Backed by open-source models
|
|
35
36
|
✅ Designed for CLI-first devs and scriptable workflows
|
|
36
37
|
|
|
38
|
+
**scai** follows the Unix philosophy — small, composable tools with powerful output.
|
|
39
|
+
|
|
37
40
|
---
|
|
38
41
|
|
|
39
42
|
## 📦 Installation
|
|
@@ -75,7 +78,7 @@ This will:
|
|
|
75
78
|
git add .
|
|
76
79
|
|
|
77
80
|
# Let scai suggest a commit message
|
|
78
|
-
scai
|
|
81
|
+
scai sugg
|
|
79
82
|
```
|
|
80
83
|
|
|
81
84
|
> Example output:
|
|
@@ -86,42 +89,29 @@ feat(api): add error handling to user service
|
|
|
86
89
|
To automatically commit with the suggested message:
|
|
87
90
|
|
|
88
91
|
```bash
|
|
89
|
-
scai
|
|
92
|
+
scai sugg --commit
|
|
90
93
|
```
|
|
91
94
|
|
|
92
95
|
---
|
|
93
96
|
|
|
94
|
-
### 🛠
|
|
97
|
+
### 🛠 Comment a code file
|
|
95
98
|
|
|
96
99
|
```bash
|
|
97
|
-
scai
|
|
100
|
+
scai comment <file>
|
|
98
101
|
```
|
|
99
102
|
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
```
|
|
103
|
-
path/to/refactored/file.refactored.js
|
|
104
|
-
```
|
|
103
|
+
Write comments for the given file. Optional flags:
|
|
104
|
+
- `-a, --apply` - Apply the refactored version to the original file
|
|
105
105
|
|
|
106
106
|
---
|
|
107
107
|
|
|
108
|
-
###
|
|
108
|
+
### 🔍 Code summary
|
|
109
109
|
|
|
110
110
|
```bash
|
|
111
|
-
scai
|
|
111
|
+
scai summ <file>
|
|
112
112
|
```
|
|
113
113
|
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
---
|
|
117
|
-
|
|
118
|
-
### 🔍 Check Git status
|
|
119
|
-
|
|
120
|
-
```bash
|
|
121
|
-
scai git
|
|
122
|
-
```
|
|
123
|
-
|
|
124
|
-
Useful overview of current branch, commits, and uncommitted changes.
|
|
114
|
+
Create a quick summary of the file your working on to gain fast insights.
|
|
125
115
|
|
|
126
116
|
---
|
|
127
117
|
|
|
@@ -141,4 +131,4 @@ However:
|
|
|
141
131
|
|
|
142
132
|
For full terms, see the [LICENSE](./LICENSE) file.
|
|
143
133
|
|
|
144
|
-
---
|
|
134
|
+
---
|
|
@@ -1,41 +1,35 @@
|
|
|
1
|
-
// Import required modules
|
|
2
1
|
import { execSync } from 'child_process';
|
|
3
2
|
import readline from 'readline';
|
|
4
|
-
// Function to ask the user to choose a commit message suggestion
|
|
5
3
|
async function askUserToChoose(suggestions) {
|
|
6
|
-
// Create an interface for reading and writing to the console
|
|
7
|
-
const rl = readline.createInterface({
|
|
8
|
-
input: process.stdin,
|
|
9
|
-
output: process.stdout,
|
|
10
|
-
});
|
|
11
|
-
// Show AI-suggested commit messages to the user and ask for their choice
|
|
12
4
|
return new Promise((resolve) => {
|
|
13
|
-
console.log('\n💡 AI-suggested commit messages:\n');
|
|
5
|
+
console.log('\n💡 AI-suggested commit messages:\n'); // Display all suggestions to the user
|
|
14
6
|
suggestions.forEach((msg, i) => {
|
|
15
7
|
console.log(`${i + 1}) ${msg}`);
|
|
16
8
|
});
|
|
17
|
-
console.log(`${suggestions.length + 1}) 🔁 Regenerate suggestions`);
|
|
18
|
-
console.log(`${suggestions.length + 2}) ✍️ Write your own commit message`);
|
|
19
|
-
rl
|
|
20
|
-
|
|
9
|
+
console.log(`${suggestions.length + 1}) 🔁 Regenerate suggestions`); // Allow the user to regenerate suggestions if they want
|
|
10
|
+
console.log(`${suggestions.length + 2}) ✍️ Write your own commit message`); // Allow the user to write their own commit message
|
|
11
|
+
const rl = readline.createInterface({
|
|
12
|
+
input: process.stdin,
|
|
13
|
+
output: process.stdout,
|
|
14
|
+
});
|
|
15
|
+
rl.question(`\n👉 Choose a commit message [1-${suggestions.length + 2}]: `, // Ask the user to choose from the suggestions or write their own
|
|
16
|
+
(answer) => {
|
|
21
17
|
rl.close();
|
|
22
|
-
const choice = parseInt(answer, 10);
|
|
23
|
-
if (isNaN(choice) || choice < 1 || choice > suggestions.length + 2) {
|
|
24
|
-
console.log('⚠️ Invalid selection. Using the first suggestion by default.');
|
|
25
|
-
resolve(0);
|
|
18
|
+
const choice = parseInt(answer, 10); // Parse the user's input as a number
|
|
19
|
+
if (isNaN(choice) || choice < 1 || choice > suggestions.length + 2) { // Check that the user has entered a valid number
|
|
20
|
+
console.log('⚠️ Invalid selection. Using the first suggestion by default.'); // Default to the first suggestion if the input is invalid
|
|
21
|
+
resolve(0); // Resolve the promise with the 0-based index (0 to 3)
|
|
26
22
|
}
|
|
27
|
-
else if (choice === suggestions.length + 2) {
|
|
28
|
-
resolve('custom');
|
|
23
|
+
else if (choice === suggestions.length + 2) { // If the user has chosen "Write your own commit message"
|
|
24
|
+
resolve('custom'); // Return 'custom' to indicate that the user wants to write their own commit message
|
|
29
25
|
}
|
|
30
26
|
else {
|
|
31
|
-
resolve(choice - 1); // Return 0-based index (0 to 3)
|
|
27
|
+
resolve(choice - 1); // Return the 0-based index (0 to 3) of the selected suggestion
|
|
32
28
|
}
|
|
33
29
|
});
|
|
34
30
|
});
|
|
35
31
|
}
|
|
36
|
-
// Async function to generate commit message suggestions using an API call
|
|
37
32
|
async function generateSuggestions(prompt) {
|
|
38
|
-
// Fetch the suggestions from an API and return them as an array of strings
|
|
39
33
|
const res = await fetch("http://localhost:11434/api/generate", {
|
|
40
34
|
method: "POST",
|
|
41
35
|
headers: { "Content-Type": "application/json" },
|
|
@@ -45,17 +39,19 @@ async function generateSuggestions(prompt) {
|
|
|
45
39
|
stream: false,
|
|
46
40
|
}),
|
|
47
41
|
});
|
|
48
|
-
const { response } = await res.json();
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
42
|
+
const { response } = await res.json(); // Parse the response from the API as JSON
|
|
43
|
+
if (!response || typeof response !== 'string') { // Check that the response is valid
|
|
44
|
+
throw new Error('Invalid LLM response'); // Throw an error if the response is invalid
|
|
45
|
+
}
|
|
46
|
+
const lines = response.trim().split('\n').filter(line => /^\d+\.\s+/.test(line)); // Split the response into individual lines and filter out any lines that don't start with a number followed by a period
|
|
47
|
+
const messages = lines.map(line => // Map each line to its commit message suggestion
|
|
48
|
+
line.replace(/^\d+\.\s+/, '').replace(/^"(.*)"$/, '$1').trim());
|
|
49
|
+
if (messages.length === 0) { // Check that there are any commit message suggestions
|
|
50
|
+
throw new Error('No valid commit messages found in LLM response.'); // Throw an error if there are no valid commit message suggestions
|
|
52
51
|
}
|
|
53
|
-
|
|
54
|
-
return lines.map(line => line.replace(/^\d+\.\s+/, '').replace(/^"(.*)"$/, '$1').trim());
|
|
52
|
+
return messages; // Return the array of commit message suggestions
|
|
55
53
|
}
|
|
56
|
-
|
|
57
|
-
async function promptCustomMessage() {
|
|
58
|
-
// Read the user's input for their custom commit message
|
|
54
|
+
function promptCustomMessage() {
|
|
59
55
|
return new Promise((resolve) => {
|
|
60
56
|
const rl = readline.createInterface({
|
|
61
57
|
input: process.stdin,
|
|
@@ -63,18 +59,17 @@ async function promptCustomMessage() {
|
|
|
63
59
|
});
|
|
64
60
|
rl.question('\n📝 Enter your custom commit message:\n> ', (input) => {
|
|
65
61
|
rl.close();
|
|
66
|
-
resolve(input.trim());
|
|
62
|
+
resolve(input.trim()); // Resolve the promise with the trimmed input string
|
|
67
63
|
});
|
|
68
64
|
});
|
|
69
65
|
}
|
|
70
|
-
// Export the main function to suggest a commit message based on user input and generated suggestions
|
|
71
66
|
export async function suggestCommitMessage(options) {
|
|
72
67
|
try {
|
|
73
|
-
let diff = execSync("git diff", { encoding: "utf-8" }).trim();
|
|
74
|
-
if (!diff) {
|
|
75
|
-
diff = execSync("git diff --cached", { encoding: "utf-8" }).trim();
|
|
68
|
+
let diff = execSync("git diff", { encoding: "utf-8" }).trim(); // Get the current Git diff
|
|
69
|
+
if (!diff) { // Check that there are any changes to stage
|
|
70
|
+
diff = execSync("git diff --cached", { encoding: "utf-8" }).trim(); // If there are no staged changes, check for unstaged changes
|
|
76
71
|
}
|
|
77
|
-
if (!diff) {
|
|
72
|
+
if (!diff) { // If there are still no changes, display a message and return early
|
|
78
73
|
console.log('⚠️ No staged changes to suggest a message for.');
|
|
79
74
|
return;
|
|
80
75
|
}
|
|
@@ -84,33 +79,28 @@ export async function suggestCommitMessage(options) {
|
|
|
84
79
|
3. ...
|
|
85
80
|
|
|
86
81
|
Here is the diff:
|
|
87
|
-
${diff}`;
|
|
82
|
+
${diff}`; // Create a prompt string for the user to enter their own commit message suggestions
|
|
88
83
|
let message = null;
|
|
89
|
-
while (message === null) {
|
|
90
|
-
const suggestions = await generateSuggestions(prompt);
|
|
91
|
-
const choice = await askUserToChoose(suggestions);
|
|
92
|
-
if (choice ===
|
|
93
|
-
//
|
|
94
|
-
|
|
95
|
-
continue;
|
|
96
|
-
}
|
|
97
|
-
if (choice === 'custom') {
|
|
98
|
-
message = await promptCustomMessage();
|
|
99
|
-
break;
|
|
84
|
+
while (message === null) { // Loop until the user has entered a valid commit message or chosen to write their own
|
|
85
|
+
const suggestions = await generateSuggestions(prompt); // Generate commit message suggestions based on the diff output from Git
|
|
86
|
+
const choice = await askUserToChoose(suggestions); // Ask the user to choose from the suggestions or write their own
|
|
87
|
+
if (choice === 'custom') { // If the user has chosen "Write your own commit message"
|
|
88
|
+
message = await promptCustomMessage(); // Prompt the user for their custom commit message
|
|
89
|
+
break; // Break out of the loop and use the custom commit message
|
|
100
90
|
}
|
|
101
|
-
message = suggestions[choice];
|
|
91
|
+
message = suggestions[choice]; // Use the selected suggestion as the commit message
|
|
102
92
|
}
|
|
103
|
-
console.log(`\n✅ Selected commit message:\n${message}\n`);
|
|
104
|
-
if (options.commit) {
|
|
105
|
-
const commitDiff = execSync("git diff", { encoding: "utf-8" }).trim();
|
|
106
|
-
if (commitDiff) {
|
|
107
|
-
execSync("git add .", { encoding: "utf-8" });
|
|
93
|
+
console.log(`\n✅ Selected commit message:\n${message}\n`); // Display the selected commit message to the user
|
|
94
|
+
if (options.commit) { // If the user has specified that they want to commit the changes
|
|
95
|
+
const commitDiff = execSync("git diff", { encoding: "utf-8" }).trim(); // Get the current Git diff again, in case anything has changed since the last call to `generateSuggestions`
|
|
96
|
+
if (commitDiff) { // Check that there are any staged changes
|
|
97
|
+
execSync("git add .", { encoding: "utf-8" }); // Stage all changes before committing
|
|
108
98
|
}
|
|
109
|
-
execSync(`git commit -m "${message.replace(/"/g, '\\"')}"`, { stdio: 'inherit' });
|
|
110
|
-
console.log('✅ Committed with selected message.');
|
|
99
|
+
execSync(`git commit -m "${message.replace(/"/g, '\\"')}"`, { stdio: 'inherit' }); // Commit the changes with the selected commit message
|
|
100
|
+
console.log('✅ Committed with selected message.'); // Display a success message to the user
|
|
111
101
|
}
|
|
112
102
|
}
|
|
113
|
-
catch (err) {
|
|
114
|
-
console.error('❌ Error in commit message suggestion:', err
|
|
103
|
+
catch (err) { // If there is an error in the code
|
|
104
|
+
console.error('❌ Error in commit message suggestion:', err); // Log the error to the console
|
|
115
105
|
}
|
|
116
106
|
}
|
|
@@ -0,0 +1,146 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Here;
|
|
3
|
+
's a Jest test file for the `checkEnv` function: `` `typescript
|
|
4
|
+
import { checkEnv } from "../src/utils";
|
|
5
|
+
|
|
6
|
+
describe("checkEnv", () => {
|
|
7
|
+
it("should warn when missing required env vars", () => {
|
|
8
|
+
const processEnv = Object.assign({}, process.env);
|
|
9
|
+
delete processEnv.DB_HOST;
|
|
10
|
+
delete processEnv.API_KEY;
|
|
11
|
+
const consoleWarnSpy = jest.spyOn(console, "warn").mockImplementation();
|
|
12
|
+
checkEnv();
|
|
13
|
+
expect(consoleWarnSpy).toHaveBeenCalledWith("❌ Missing env vars: DB_HOST, API_KEY");
|
|
14
|
+
consoleWarnSpy.mockRestore();
|
|
15
|
+
});
|
|
16
|
+
|
|
17
|
+
it("should log a message when all required env vars are set", () => {
|
|
18
|
+
const processEnv = Object.assign({}, process.env);
|
|
19
|
+
checkEnv();
|
|
20
|
+
expect(console.log).toHaveBeenCalledWith("✅ All env vars are set");
|
|
21
|
+
});
|
|
22
|
+
});
|
|
23
|
+
` ``;
|
|
24
|
+
In;
|
|
25
|
+
this;
|
|
26
|
+
test;
|
|
27
|
+
file, we;
|
|
28
|
+
first;
|
|
29
|
+
var the = ;
|
|
30
|
+
`checkEnv`;
|
|
31
|
+
function from() { }
|
|
32
|
+
the;
|
|
33
|
+
contains;
|
|
34
|
+
it.We;
|
|
35
|
+
then;
|
|
36
|
+
define;
|
|
37
|
+
two;
|
|
38
|
+
test;
|
|
39
|
+
cases: one;
|
|
40
|
+
for (when; required; environment)
|
|
41
|
+
variables;
|
|
42
|
+
are;
|
|
43
|
+
missing;
|
|
44
|
+
and;
|
|
45
|
+
another;
|
|
46
|
+
for (when; all; required)
|
|
47
|
+
environment;
|
|
48
|
+
variables;
|
|
49
|
+
are;
|
|
50
|
+
set.
|
|
51
|
+
;
|
|
52
|
+
In;
|
|
53
|
+
the;
|
|
54
|
+
first;
|
|
55
|
+
test;
|
|
56
|
+
we;
|
|
57
|
+
use `jest.spyOn()`;
|
|
58
|
+
to;
|
|
59
|
+
mock;
|
|
60
|
+
the `console.warn()`;
|
|
61
|
+
method;
|
|
62
|
+
and;
|
|
63
|
+
replace;
|
|
64
|
+
it;
|
|
65
|
+
with (a)
|
|
66
|
+
spy;
|
|
67
|
+
function () { }
|
|
68
|
+
We;
|
|
69
|
+
then;
|
|
70
|
+
delete the `DB_HOST`;
|
|
71
|
+
and `API_KEY`;
|
|
72
|
+
environment;
|
|
73
|
+
variables;
|
|
74
|
+
and;
|
|
75
|
+
call;
|
|
76
|
+
the `checkEnv()`;
|
|
77
|
+
function () { }
|
|
78
|
+
Finally, we;
|
|
79
|
+
check;
|
|
80
|
+
that;
|
|
81
|
+
the `console.warn()`;
|
|
82
|
+
method;
|
|
83
|
+
was;
|
|
84
|
+
called;
|
|
85
|
+
with (the)
|
|
86
|
+
expected;
|
|
87
|
+
message.
|
|
88
|
+
;
|
|
89
|
+
In;
|
|
90
|
+
the;
|
|
91
|
+
second;
|
|
92
|
+
test;
|
|
93
|
+
we;
|
|
94
|
+
simply;
|
|
95
|
+
call;
|
|
96
|
+
the `checkEnv()`;
|
|
97
|
+
function without() { }
|
|
98
|
+
deleting;
|
|
99
|
+
any;
|
|
100
|
+
environment;
|
|
101
|
+
variables;
|
|
102
|
+
and;
|
|
103
|
+
check;
|
|
104
|
+
that;
|
|
105
|
+
the `console.log()`;
|
|
106
|
+
method;
|
|
107
|
+
was;
|
|
108
|
+
called;
|
|
109
|
+
with (the)
|
|
110
|
+
expected;
|
|
111
|
+
message.
|
|
112
|
+
;
|
|
113
|
+
Note;
|
|
114
|
+
that;
|
|
115
|
+
we;
|
|
116
|
+
use;
|
|
117
|
+
the `mockImplementation()`;
|
|
118
|
+
method;
|
|
119
|
+
to;
|
|
120
|
+
mock;
|
|
121
|
+
the `console.warn()`;
|
|
122
|
+
method;
|
|
123
|
+
and;
|
|
124
|
+
the `mockRestore()`;
|
|
125
|
+
method;
|
|
126
|
+
to;
|
|
127
|
+
restore;
|
|
128
|
+
it;
|
|
129
|
+
after;
|
|
130
|
+
the;
|
|
131
|
+
test;
|
|
132
|
+
is;
|
|
133
|
+
done.This;
|
|
134
|
+
is;
|
|
135
|
+
necessary;
|
|
136
|
+
because;
|
|
137
|
+
Jest;
|
|
138
|
+
does;
|
|
139
|
+
not;
|
|
140
|
+
allow;
|
|
141
|
+
us;
|
|
142
|
+
to;
|
|
143
|
+
modify;
|
|
144
|
+
the;
|
|
145
|
+
original `console.warn()`;
|
|
146
|
+
method.;
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
// File: src/commands/SummaryCmd.ts
|
|
2
|
+
import fs from 'fs/promises';
|
|
3
|
+
import { summaryModule } from '../pipeline/modules/summaryModule.js';
|
|
4
|
+
export async function summarizeFile(filepath) {
|
|
5
|
+
try {
|
|
6
|
+
const code = await fs.readFile(filepath, 'utf-8');
|
|
7
|
+
await summaryModule.run({ code });
|
|
8
|
+
}
|
|
9
|
+
catch (err) {
|
|
10
|
+
console.error(`❌ Could not read or summarize ${filepath}:`, err.message);
|
|
11
|
+
}
|
|
12
|
+
}
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
export class ModelConfig {
|
|
2
|
+
static setModel(model) {
|
|
3
|
+
this.model = model;
|
|
4
|
+
}
|
|
5
|
+
static getModel() {
|
|
6
|
+
return this.model;
|
|
7
|
+
}
|
|
8
|
+
static setLanguage(lang) {
|
|
9
|
+
this.language = lang;
|
|
10
|
+
}
|
|
11
|
+
static getLanguage() {
|
|
12
|
+
return this.language;
|
|
13
|
+
}
|
|
14
|
+
}
|
|
15
|
+
ModelConfig.model = 'codellama:7b';
|
|
16
|
+
ModelConfig.language = 'ts';
|
package/dist/index.js
CHANGED
|
@@ -1,16 +1,22 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
import { Command } from "commander";
|
|
3
|
+
import { createRequire } from 'module';
|
|
4
|
+
const require = createRequire(import.meta.url);
|
|
5
|
+
const { version } = require('../package.json');
|
|
3
6
|
import { checkEnv } from "./commands/EnvCmd.js";
|
|
4
7
|
import { checkGit } from "./commands/GitCmd.js";
|
|
5
8
|
import { suggestCommitMessage } from "./commands/CommitSuggesterCmd.js";
|
|
6
9
|
import { handleRefactor } from "./commands/RefactorCmd.js";
|
|
7
10
|
import { updateReadmeIfNeeded } from "./commands/ReadmeCmd.js";
|
|
8
11
|
import { generateTests } from "./commands/TestGenCmd.js";
|
|
9
|
-
// Import the model check and initialization logic
|
|
10
12
|
import { bootstrap } from './modelSetup.js';
|
|
13
|
+
import { ModelConfig } from './config/ModelConfig.js';
|
|
14
|
+
import { summarizeFile } from "./commands/SummaryCmd.js";
|
|
11
15
|
// Create the CLI instance
|
|
12
16
|
const cmd = new Command('scai')
|
|
13
|
-
.version(
|
|
17
|
+
.version(version)
|
|
18
|
+
.option('--model <model>', 'Set the model to use (e.g., codellama:34b)')
|
|
19
|
+
.option('--lang <lang>', 'Set the target language (ts, java, rust)');
|
|
14
20
|
// Define CLI commands
|
|
15
21
|
cmd
|
|
16
22
|
.command('init')
|
|
@@ -20,21 +26,13 @@ cmd
|
|
|
20
26
|
console.log('✅ Model initialization completed!');
|
|
21
27
|
});
|
|
22
28
|
cmd
|
|
23
|
-
.command('
|
|
24
|
-
.description('Check environment variables')
|
|
25
|
-
.action(checkEnv);
|
|
26
|
-
cmd
|
|
27
|
-
.command('git')
|
|
28
|
-
.description('Check Git status')
|
|
29
|
-
.action(checkGit);
|
|
30
|
-
cmd
|
|
31
|
-
.command('suggest')
|
|
29
|
+
.command('sugg')
|
|
32
30
|
.description('Suggest a commit message from staged changes')
|
|
33
31
|
.option('-c, --commit', 'Automatically commit with suggested message')
|
|
34
32
|
.action(suggestCommitMessage);
|
|
35
33
|
cmd
|
|
36
|
-
.command('
|
|
37
|
-
.description('
|
|
34
|
+
.command('comm <file>')
|
|
35
|
+
.description('Write comments for the given file')
|
|
38
36
|
.option('-a, --apply', 'Apply the refactored version to the original file')
|
|
39
37
|
.action((file, options) => handleRefactor(file, options));
|
|
40
38
|
cmd
|
|
@@ -42,8 +40,26 @@ cmd
|
|
|
42
40
|
.description('Update README.md if relevant changes were made')
|
|
43
41
|
.action(updateReadmeIfNeeded);
|
|
44
42
|
cmd
|
|
45
|
-
.command('
|
|
43
|
+
.command('summ <file>')
|
|
44
|
+
.description('Print a summary of the given file to the terminal')
|
|
45
|
+
.action((file) => summarizeFile(file));
|
|
46
|
+
cmd
|
|
47
|
+
.command('git')
|
|
48
|
+
.description('Check Git status')
|
|
49
|
+
.action(checkGit);
|
|
50
|
+
cmd
|
|
51
|
+
.command('env')
|
|
52
|
+
.description('Check environment variables')
|
|
53
|
+
.action(checkEnv);
|
|
54
|
+
cmd
|
|
55
|
+
.command('gen-tests <file>')
|
|
46
56
|
.description('Generate a Jest test file for the specified JS/TS module')
|
|
47
57
|
.action((file) => generateTests(file));
|
|
48
|
-
//
|
|
58
|
+
// ✅ Now that commands are defined, parse args
|
|
49
59
|
cmd.parse(process.argv);
|
|
60
|
+
// ✅ Apply global options after parsing
|
|
61
|
+
const opts = cmd.opts();
|
|
62
|
+
if (opts.model)
|
|
63
|
+
ModelConfig.setModel(opts.model);
|
|
64
|
+
if (opts.lang)
|
|
65
|
+
ModelConfig.setLanguage(opts.lang);
|
|
@@ -1,9 +1,12 @@
|
|
|
1
|
+
import { ModelConfig } from '../../config/ModelConfig.js';
|
|
1
2
|
export const addCommentsModule = {
|
|
2
3
|
name: 'addComments',
|
|
3
4
|
description: 'Adds meaningful // comments to each block of code',
|
|
4
5
|
async run(input) {
|
|
6
|
+
const model = ModelConfig.getModel();
|
|
7
|
+
const lang = ModelConfig.getLanguage();
|
|
5
8
|
const prompt = `
|
|
6
|
-
You are a senior
|
|
9
|
+
You are a senior ${lang.toUpperCase()} engineer.
|
|
7
10
|
|
|
8
11
|
Add clear and helpful single-line // comments to complex parts of the code.
|
|
9
12
|
- Do NOT remove or change in ANY way any parts of the code!
|
|
@@ -17,8 +20,8 @@ ${input.code}
|
|
|
17
20
|
method: 'POST',
|
|
18
21
|
headers: { 'Content-Type': 'application/json' },
|
|
19
22
|
body: JSON.stringify({
|
|
20
|
-
model
|
|
21
|
-
prompt
|
|
23
|
+
model,
|
|
24
|
+
prompt,
|
|
22
25
|
stream: false,
|
|
23
26
|
}),
|
|
24
27
|
});
|
|
@@ -1,11 +1,14 @@
|
|
|
1
1
|
import fs from 'fs/promises';
|
|
2
2
|
import path from 'path';
|
|
3
|
+
import { ModelConfig } from '../../config/ModelConfig.js';
|
|
3
4
|
export const generateTestsModule = {
|
|
4
5
|
name: 'generateTests',
|
|
5
6
|
description: 'Generate a Jest test file for the class/module',
|
|
6
7
|
async run({ code, filepath }) {
|
|
8
|
+
const model = ModelConfig.getModel();
|
|
9
|
+
const lang = ModelConfig.getLanguage();
|
|
7
10
|
const prompt = `
|
|
8
|
-
You
|
|
11
|
+
You are a senior ${lang.toUpperCase()} engineer. Given the following class or module, generate a Jest test file.
|
|
9
12
|
|
|
10
13
|
Guidelines:
|
|
11
14
|
- Use the 'jest' test framework
|
|
@@ -21,7 +24,7 @@ ${code}
|
|
|
21
24
|
method: 'POST',
|
|
22
25
|
headers: { 'Content-Type': 'application/json' },
|
|
23
26
|
body: JSON.stringify({
|
|
24
|
-
model
|
|
27
|
+
model,
|
|
25
28
|
prompt,
|
|
26
29
|
stream: false
|
|
27
30
|
})
|
|
@@ -1,25 +1,45 @@
|
|
|
1
|
+
import { ModelConfig } from '../../config/ModelConfig.js';
|
|
1
2
|
export const refactorModule = {
|
|
2
3
|
name: 'refactor',
|
|
3
4
|
description: 'Break code into small, clean functions',
|
|
4
|
-
async run(
|
|
5
|
+
async run(input) {
|
|
6
|
+
const model = ModelConfig.getModel();
|
|
7
|
+
const lang = ModelConfig.getLanguage();
|
|
5
8
|
const prompt = `
|
|
6
|
-
You are a senior
|
|
9
|
+
You are a senior ${lang.toUpperCase()} engineer.
|
|
7
10
|
|
|
8
11
|
Refactor the following code:
|
|
9
|
-
- Refactor only long and complex functions
|
|
12
|
+
- Refactor only long and complex functions
|
|
10
13
|
- Keep original names and semantics.
|
|
11
14
|
- Do NOT insert comments
|
|
12
15
|
|
|
13
16
|
--- CODE START ---
|
|
14
|
-
${code}
|
|
17
|
+
${input.code}
|
|
15
18
|
--- CODE END ---
|
|
16
19
|
`.trim();
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
20
|
+
try {
|
|
21
|
+
const res = await fetch('http://localhost:11434/api/generate', {
|
|
22
|
+
method: 'POST',
|
|
23
|
+
headers: { 'Content-Type': 'application/json' },
|
|
24
|
+
body: JSON.stringify({
|
|
25
|
+
model,
|
|
26
|
+
prompt,
|
|
27
|
+
stream: false
|
|
28
|
+
})
|
|
29
|
+
});
|
|
30
|
+
if (!res.ok) {
|
|
31
|
+
const errBody = await res.text();
|
|
32
|
+
throw new Error(`❌ Refactor failed: ${res.status} ${res.statusText} - ${errBody}`);
|
|
33
|
+
}
|
|
34
|
+
const data = await res.json();
|
|
35
|
+
if (!data.response) {
|
|
36
|
+
throw new Error('❌ No response field returned from model.');
|
|
37
|
+
}
|
|
38
|
+
return { code: data.response.trim() };
|
|
39
|
+
}
|
|
40
|
+
catch (err) {
|
|
41
|
+
console.error('🔥 Error in refactorModule:', err);
|
|
42
|
+
throw new Error('❌ Error in refactor module: ' + err.message);
|
|
43
|
+
}
|
|
24
44
|
}
|
|
25
45
|
};
|
|
@@ -1,20 +1,22 @@
|
|
|
1
|
+
import { ModelConfig } from '../../config/ModelConfig.js';
|
|
1
2
|
export const summaryModule = {
|
|
2
3
|
name: 'summary',
|
|
3
|
-
description: '
|
|
4
|
+
description: 'Prints a summary of changes to the terminal',
|
|
4
5
|
async run({ code }) {
|
|
6
|
+
const model = ModelConfig.getModel();
|
|
7
|
+
const lang = ModelConfig.getLanguage();
|
|
5
8
|
const prompt = `
|
|
6
|
-
You are a senior
|
|
9
|
+
You are a senior ${lang.toUpperCase()} engineer.
|
|
7
10
|
|
|
8
|
-
|
|
9
|
-
|
|
11
|
+
Analyze the code below and return a concise summary of its functionality or structure.
|
|
12
|
+
Only return the summary in this format:
|
|
10
13
|
|
|
11
|
-
// Summary of
|
|
12
|
-
// -
|
|
13
|
-
// -
|
|
14
|
+
// Summary of code:
|
|
15
|
+
// - [Bullet 1]
|
|
16
|
+
// - [Bullet 2]
|
|
14
17
|
// - etc.
|
|
15
18
|
|
|
16
|
-
|
|
17
|
-
Just append the summary.
|
|
19
|
+
Return the original code after the summary.
|
|
18
20
|
|
|
19
21
|
--- CODE START ---
|
|
20
22
|
${code}
|
|
@@ -23,9 +25,18 @@ ${code}
|
|
|
23
25
|
const res = await fetch('http://localhost:11434/api/generate', {
|
|
24
26
|
method: 'POST',
|
|
25
27
|
headers: { 'Content-Type': 'application/json' },
|
|
26
|
-
body: JSON.stringify({ model
|
|
28
|
+
body: JSON.stringify({ model, prompt, stream: false })
|
|
27
29
|
});
|
|
28
30
|
const data = await res.json();
|
|
29
|
-
|
|
31
|
+
const summary = data.response?.trim();
|
|
32
|
+
if (summary) {
|
|
33
|
+
console.log('\n📝 Code Summary:\n');
|
|
34
|
+
console.log(summary);
|
|
35
|
+
}
|
|
36
|
+
else {
|
|
37
|
+
console.warn('⚠️ No summary generated.');
|
|
38
|
+
}
|
|
39
|
+
// Return unchanged input so it’s pipe-safe, if reused in pipelines
|
|
40
|
+
return { code };
|
|
30
41
|
}
|
|
31
42
|
};
|