scai 0.1.4 → 0.1.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.
- package/README.md +103 -12
- package/dist/commands/CommitSuggesterCmd.js +66 -21
- package/dist/commands/ReadmeCmd.js +58 -0
- package/dist/index.js +5 -0
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -1,29 +1,120 @@
|
|
|
1
|
-
# scai — Smart Commit AI ✨
|
|
1
|
+
# ⚙️ scai — Smart Commit AI ✨
|
|
2
2
|
|
|
3
|
-
> AI-powered commit
|
|
3
|
+
> AI-powered CLI tools for smart commit messages, automated refactoring, and developer insight — all powered by local models.
|
|
4
4
|
|
|
5
|
-
**scai** (Smart Commit AI) is a lightweight, privacy-focused CLI tool that uses
|
|
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
|
+
|
|
7
|
+
- 🤖 Suggest high-quality Git commit messages
|
|
8
|
+
- ✨ Refactor messy code files
|
|
9
|
+
- 🧠 Check Git status and improve workflows
|
|
10
|
+
- 🔒 100% local — no API keys, no cloud, no telemetry
|
|
6
11
|
|
|
7
12
|
---
|
|
8
13
|
|
|
9
14
|
## 🚀 Features
|
|
10
15
|
|
|
11
|
-
-
|
|
12
|
-
-
|
|
13
|
-
-
|
|
14
|
-
-
|
|
16
|
+
- 💬 Generate commit messages from staged Git changes
|
|
17
|
+
- ✨ Refactor a single JavaScript file for improved readability
|
|
18
|
+
- 🔍 Check Git status with one command
|
|
19
|
+
- ⚡️ Powered by Ollama + local models like `llama3` and `mistral`
|
|
20
|
+
- 🛠️ CLI built with Node.js + TypeScript
|
|
21
|
+
- 🔒 No external services, full privacy by design
|
|
15
22
|
|
|
16
23
|
---
|
|
17
24
|
|
|
18
25
|
## 📦 Installation
|
|
19
26
|
|
|
20
|
-
|
|
27
|
+
1. **Install [Ollama](https://ollama.com)**
|
|
28
|
+
- On Windows: [Download Ollama](https://ollama.com/download)
|
|
29
|
+
- Ensure it’s added to your system `PATH`
|
|
30
|
+
|
|
31
|
+
2. **Install scai globally via npm:**
|
|
32
|
+
|
|
33
|
+
```bash
|
|
34
|
+
npm install -g scai
|
|
35
|
+
```
|
|
36
|
+
|
|
37
|
+
3. **Run the initialization step to start Ollama and install models:**
|
|
38
|
+
|
|
39
|
+
```bash
|
|
40
|
+
scai init
|
|
41
|
+
```
|
|
42
|
+
|
|
43
|
+
This will:
|
|
44
|
+
- Launch the Ollama background server (if not running)
|
|
45
|
+
- Pull the required models (`llama3`, `mistral`) if they aren’t present
|
|
46
|
+
|
|
47
|
+
---
|
|
48
|
+
|
|
49
|
+
## 🧪 Usage Examples
|
|
50
|
+
|
|
51
|
+
### 💬 Suggest a commit message
|
|
52
|
+
|
|
53
|
+
```bash
|
|
54
|
+
# Stage your changes
|
|
55
|
+
git add .
|
|
56
|
+
|
|
57
|
+
# Let scai suggest a commit message
|
|
58
|
+
scai commit
|
|
59
|
+
```
|
|
60
|
+
|
|
61
|
+
> Example output:
|
|
62
|
+
```
|
|
63
|
+
feat(api): add error handling to user service
|
|
64
|
+
```
|
|
65
|
+
|
|
66
|
+
To automatically commit with the suggested message:
|
|
67
|
+
|
|
68
|
+
```bash
|
|
69
|
+
scai commit --commit
|
|
70
|
+
```
|
|
71
|
+
|
|
72
|
+
---
|
|
73
|
+
|
|
74
|
+
### 🛠 Refactor a JavaScript file
|
|
75
|
+
|
|
76
|
+
```bash
|
|
77
|
+
scai refactor path/to/file.js
|
|
78
|
+
```
|
|
79
|
+
|
|
80
|
+
A cleaned-up version will be written to:
|
|
81
|
+
|
|
82
|
+
```
|
|
83
|
+
path/to/refactored/file.refactored.js
|
|
84
|
+
```
|
|
85
|
+
|
|
86
|
+
---
|
|
87
|
+
|
|
88
|
+
### 🔍 Check Git status
|
|
21
89
|
|
|
22
90
|
```bash
|
|
23
|
-
|
|
91
|
+
scai git
|
|
92
|
+
```
|
|
93
|
+
|
|
94
|
+
Useful overview of current branch, commits, and uncommitted changes.
|
|
95
|
+
|
|
96
|
+
---
|
|
97
|
+
|
|
98
|
+
## 🔐 License & Fair Use
|
|
99
|
+
|
|
100
|
+
**scai is free to use** for individuals, teams, and companies — including in commercial work.
|
|
101
|
+
You may:
|
|
102
|
+
|
|
103
|
+
- ✅ Use it internally in your projects
|
|
104
|
+
- ✅ Use it at work or in commercial software development
|
|
105
|
+
- ✅ Share and recommend it to colleagues
|
|
106
|
+
|
|
107
|
+
However:
|
|
108
|
+
|
|
109
|
+
- ❌ You may not **resell**, repackage, or redistribute **scai** as a commercial product or SaaS offering
|
|
110
|
+
- ❌ You may not claim ownership or original authorship
|
|
111
|
+
|
|
112
|
+
For full terms, see the [LICENSE](./LICENSE) file.
|
|
113
|
+
|
|
114
|
+
---
|
|
24
115
|
|
|
116
|
+
## ❤️ Why Local-First?
|
|
25
117
|
|
|
26
|
-
|
|
118
|
+
We believe your code — and your workflow — should stay **yours**. scai runs entirely on your machine using open-source models and tools.
|
|
27
119
|
|
|
28
|
-
|
|
29
|
-
See the [LICENSE](./LICENSE) file for details.
|
|
120
|
+
No internet connection. No vendor lock-in. Just local, private, AI-enhanced developer experience.
|
|
@@ -1,4 +1,50 @@
|
|
|
1
1
|
import { execSync } from 'child_process';
|
|
2
|
+
import readline from 'readline';
|
|
3
|
+
function askUserToChoose(suggestions) {
|
|
4
|
+
return new Promise((resolve) => {
|
|
5
|
+
console.log('\n💡 AI-suggested commit messages:\n');
|
|
6
|
+
suggestions.forEach((msg, i) => {
|
|
7
|
+
console.log(`${i + 1}) ${msg}`);
|
|
8
|
+
});
|
|
9
|
+
console.log(`${suggestions.length + 1}) 🔁 Regenerate suggestions`);
|
|
10
|
+
const rl = readline.createInterface({
|
|
11
|
+
input: process.stdin,
|
|
12
|
+
output: process.stdout,
|
|
13
|
+
});
|
|
14
|
+
rl.question(`\n👉 Choose a commit message [1-${suggestions.length + 1}]: `, (answer) => {
|
|
15
|
+
rl.close();
|
|
16
|
+
const choice = parseInt(answer, 10);
|
|
17
|
+
if (isNaN(choice) || choice < 1 || choice > suggestions.length + 1) {
|
|
18
|
+
console.log('⚠️ Invalid selection. Using the first suggestion by default.');
|
|
19
|
+
resolve(0);
|
|
20
|
+
}
|
|
21
|
+
else {
|
|
22
|
+
resolve(choice - 1); // Return 0-based index (0 to 3)
|
|
23
|
+
}
|
|
24
|
+
});
|
|
25
|
+
});
|
|
26
|
+
}
|
|
27
|
+
async function generateSuggestions(prompt) {
|
|
28
|
+
const res = await fetch("http://localhost:11434/api/generate", {
|
|
29
|
+
method: "POST",
|
|
30
|
+
headers: { "Content-Type": "application/json" },
|
|
31
|
+
body: JSON.stringify({
|
|
32
|
+
model: "llama3",
|
|
33
|
+
prompt,
|
|
34
|
+
stream: false,
|
|
35
|
+
}),
|
|
36
|
+
});
|
|
37
|
+
const { response } = await res.json();
|
|
38
|
+
if (!response || typeof response !== 'string') {
|
|
39
|
+
throw new Error('Invalid LLM response');
|
|
40
|
+
}
|
|
41
|
+
const lines = response.trim().split('\n').filter(line => /^\d+\.\s+/.test(line));
|
|
42
|
+
const messages = lines.map(line => line.replace(/^\d+\.\s+/, '').replace(/^"(.*)"$/, '$1').trim());
|
|
43
|
+
if (messages.length === 0) {
|
|
44
|
+
throw new Error('No valid commit messages found in LLM response.');
|
|
45
|
+
}
|
|
46
|
+
return messages;
|
|
47
|
+
}
|
|
2
48
|
export async function suggestCommitMessage(options) {
|
|
3
49
|
try {
|
|
4
50
|
let diff = execSync("git diff", { encoding: "utf-8" }).trim();
|
|
@@ -9,36 +55,35 @@ export async function suggestCommitMessage(options) {
|
|
|
9
55
|
console.log('⚠️ No staged changes to suggest a message for.');
|
|
10
56
|
return;
|
|
11
57
|
}
|
|
12
|
-
const prompt = `Suggest
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
58
|
+
const prompt = `Suggest 3 concise, conventional Git commit message options for this diff. Return ONLY the commit messages, numbered 1 to 3, like so:
|
|
59
|
+
1. ...
|
|
60
|
+
2. ...
|
|
61
|
+
3. ...
|
|
62
|
+
|
|
63
|
+
Here is the diff:
|
|
64
|
+
${diff}`;
|
|
65
|
+
let message = null;
|
|
66
|
+
while (message === null) {
|
|
67
|
+
const suggestions = await generateSuggestions(prompt);
|
|
68
|
+
const choiceIndex = await askUserToChoose(suggestions);
|
|
69
|
+
if (choiceIndex === suggestions.length) {
|
|
70
|
+
// User chose "Regenerate"
|
|
71
|
+
console.log('\n🔄 Regenerating suggestions...\n');
|
|
72
|
+
continue;
|
|
73
|
+
}
|
|
74
|
+
message = suggestions[choiceIndex];
|
|
25
75
|
}
|
|
26
|
-
|
|
27
|
-
console.log(`${message}`);
|
|
28
|
-
// Remove double quotes from the message
|
|
29
|
-
message = message.replace(/^"(.*)"$/, '$1');
|
|
30
|
-
// 3) Optionally commit
|
|
76
|
+
console.log(`\n✅ Selected commit message:\n${message}\n`);
|
|
31
77
|
if (options.commit) {
|
|
32
|
-
// If code not already staged
|
|
33
78
|
const commitDiff = execSync("git diff", { encoding: "utf-8" }).trim();
|
|
34
79
|
if (commitDiff) {
|
|
35
|
-
execSync("git add .", { encoding: "utf-8" })
|
|
80
|
+
execSync("git add .", { encoding: "utf-8" });
|
|
36
81
|
}
|
|
37
82
|
execSync(`git commit -m "${message.replace(/"/g, '\\"')}"`, { stdio: 'inherit' });
|
|
38
83
|
console.log('✅ Committed with AI-suggested message.');
|
|
39
84
|
}
|
|
40
85
|
}
|
|
41
86
|
catch (err) {
|
|
42
|
-
console.error('❌ Error in
|
|
87
|
+
console.error('❌ Error in commit message suggestion:', err.message);
|
|
43
88
|
}
|
|
44
89
|
}
|
|
@@ -0,0 +1,58 @@
|
|
|
1
|
+
import { execSync } from 'child_process';
|
|
2
|
+
import fs from 'fs/promises';
|
|
3
|
+
import path from 'path';
|
|
4
|
+
export async function updateReadmeIfNeeded() {
|
|
5
|
+
try {
|
|
6
|
+
const diff = execSync("git diff", { encoding: "utf-8" }).trim();
|
|
7
|
+
if (!diff) {
|
|
8
|
+
console.log("⚠️ No changes to analyze in the working directory.");
|
|
9
|
+
return;
|
|
10
|
+
}
|
|
11
|
+
const readmePath = path.resolve("README.md");
|
|
12
|
+
let readme = "";
|
|
13
|
+
try {
|
|
14
|
+
readme = await fs.readFile(readmePath, "utf-8");
|
|
15
|
+
}
|
|
16
|
+
catch {
|
|
17
|
+
console.log("📄 No existing README.md found, skipping update.");
|
|
18
|
+
return;
|
|
19
|
+
}
|
|
20
|
+
const prompt = `
|
|
21
|
+
You're an experienced documentation writer. Here's the current README:
|
|
22
|
+
|
|
23
|
+
--- README START ---
|
|
24
|
+
${readme}
|
|
25
|
+
--- README END ---
|
|
26
|
+
|
|
27
|
+
Here is a Git diff of recent code changes:
|
|
28
|
+
|
|
29
|
+
--- DIFF START ---
|
|
30
|
+
${diff}
|
|
31
|
+
--- DIFF END ---
|
|
32
|
+
|
|
33
|
+
✅ If the changes are significant and relevant to the public-facing documentation, return an updated README.
|
|
34
|
+
❌ If they are not, return ONLY: "NO UPDATE".
|
|
35
|
+
`;
|
|
36
|
+
const res = await fetch("http://localhost:11434/api/generate", {
|
|
37
|
+
method: "POST",
|
|
38
|
+
headers: { "Content-Type": "application/json" },
|
|
39
|
+
body: JSON.stringify({
|
|
40
|
+
model: "llama3",
|
|
41
|
+
prompt,
|
|
42
|
+
stream: false,
|
|
43
|
+
}),
|
|
44
|
+
});
|
|
45
|
+
const { response } = await res.json();
|
|
46
|
+
const result = response.trim();
|
|
47
|
+
if (result === "NO UPDATE") {
|
|
48
|
+
console.log("✅ No significant changes for README.");
|
|
49
|
+
}
|
|
50
|
+
else {
|
|
51
|
+
await fs.writeFile(readmePath, result, "utf-8");
|
|
52
|
+
console.log("📝 README.md updated based on significant changes.");
|
|
53
|
+
}
|
|
54
|
+
}
|
|
55
|
+
catch (err) {
|
|
56
|
+
console.error("❌ Failed to update README:", err.message);
|
|
57
|
+
}
|
|
58
|
+
}
|
package/dist/index.js
CHANGED
|
@@ -4,6 +4,7 @@ import { checkEnv } from "./commands/EnvCmd.js";
|
|
|
4
4
|
import { checkGit } from "./commands/GitCmd.js";
|
|
5
5
|
import { suggestCommitMessage } from "./commands/CommitSuggesterCmd.js";
|
|
6
6
|
import { handleRefactor } from "./commands/RefactorCmd.js";
|
|
7
|
+
import { updateReadmeIfNeeded } from "./commands/ReadmeCmd.js";
|
|
7
8
|
// Import the model check and initialization logic
|
|
8
9
|
import { bootstrap } from './modelSetup.js';
|
|
9
10
|
// Create the CLI instance
|
|
@@ -34,5 +35,9 @@ cmd
|
|
|
34
35
|
.command('refactor <file>')
|
|
35
36
|
.description('Suggest a refactor for the given JS file')
|
|
36
37
|
.action((file) => handleRefactor(file));
|
|
38
|
+
cmd
|
|
39
|
+
.command('readme')
|
|
40
|
+
.description('Update README.md if relevant changes were made')
|
|
41
|
+
.action(updateReadmeIfNeeded);
|
|
37
42
|
// Parse CLI arguments
|
|
38
43
|
cmd.parse(process.argv);
|