scai 0.1.11 → 0.1.13

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 CHANGED
@@ -1,38 +1,34 @@
1
1
  # ⚙️ scai — Smart Commit AI ✨
2
2
 
3
- > AI-powered CLI tools for smart commit messages, automated refactoring, and developer insight — all powered by local models.
3
+ > AI-powered CLI tools for smart commit messages, code comments, summaries, and changelogs — 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
- - 🤖 Suggest high-quality Git commit messages
8
- - ✨ Comments your code automatically
9
- - 🧠 Generate README updates based on your diff
7
+ - 💬 Suggest high-quality Git commit messages
8
+ - ✨ Automatically comment your code
9
+ - 🧠 Summarize code files instantly in the terminal
10
+ - 📝 Generate changelog entries based on Git diffs
10
11
  - 🔒 100% local — no API keys, no cloud, no telemetry
11
12
 
12
13
  ---
13
14
 
14
- **scai** follows the Unix philosophy — small, composable tools with powerful output.
15
-
16
15
  ## 🚀 Features
17
16
 
18
- - 💬 Generate commit messages from staged Git changes
19
- - Add comments to your code
20
- - ⚡️ Powered by open local Ollama models like `mistral`
21
- - 🛠️ CLI built with Node.js + TypeScript
17
+ - ⚡️ Powered by local Ollama models like `llama3` and `codellama`
18
+ - 🛠️ CLI built with Node.js + TypeScript
22
19
  - 🔒 No external services, full privacy by design
20
+ - ✅ Global options for model and language selection
23
21
 
24
22
  ---
25
23
 
26
- ## ❤️ Philosophy: Why Local AI?
24
+ ## ❤️ Why Local AI?
27
25
 
28
26
  We believe your code — and your workflow — should stay **yours**. scai runs entirely on your machine using open-source models and tools.
29
27
 
30
- No internet connection. No vendor lock-in. Just local, private, AI-enhanced developer experience.
31
-
32
28
  ✅ Works entirely offline
33
- ✅ No API keys, cloud accounts, or telemetry
29
+ ✅ No API keys or cloud dependencies
34
30
  ✅ Backed by open-source models
35
- ✅ Designed for CLI-first devs and scriptable workflows
31
+ ✅ Designed for CLI-first developers
36
32
 
37
33
  ---
38
34
 
@@ -41,28 +37,27 @@ No internet connection. No vendor lock-in. Just local, private, AI-enhanced deve
41
37
  1. **Install [Ollama](https://ollama.com)**
42
38
  - On **Windows**: [Download Ollama](https://ollama.com/download)
43
39
  - On **macOS**:
44
- - Install via Homebrew:
40
+ - Via Homebrew:
45
41
  ```bash
46
42
  brew install ollama
47
43
  ```
48
- - Or download directly from the [Ollama website](https://ollama.com/download)
49
- - Ensure it’s added to your system `PATH`
44
+ - Or download directly from the website
50
45
 
51
- 2. **Install scai globally via npm:**
46
+ 2. **Install scai globally:**
52
47
 
53
48
  ```bash
54
49
  npm install -g scai
55
50
  ```
56
51
 
57
- 3. **Run the initialization step to start Ollama and install models:**
52
+ 3. **Initialize the tool and models:**
58
53
 
59
54
  ```bash
60
55
  scai init
61
56
  ```
62
57
 
63
58
  This will:
64
- - Launch the Ollama background server (if not running)
65
- - Pull the required models (`llama3`, `mistral`) if they aren’t present
59
+ - Start the Ollama background server
60
+ - Download required models (`llama3`, etc.)
66
61
 
67
62
  ---
68
63
 
@@ -71,11 +66,8 @@ This will:
71
66
  ### 💬 Suggest a commit message
72
67
 
73
68
  ```bash
74
- # Stage your changes
75
69
  git add .
76
-
77
- # Let scai suggest a commit message
78
- scai suggest
70
+ scai sugg
79
71
  ```
80
72
 
81
73
  > Example output:
@@ -83,62 +75,60 @@ scai suggest
83
75
  feat(api): add error handling to user service
84
76
  ```
85
77
 
86
- To automatically commit with the suggested message:
78
+ To commit automatically with the suggestion:
87
79
 
88
80
  ```bash
89
- scai suggest --commit
81
+ scai sugg --commit
90
82
  ```
91
83
 
92
84
  ---
93
85
 
94
- ### 🛠 Refactor a JavaScript file
86
+ ### Comment a code file
95
87
 
96
88
  ```bash
97
- scai refac path/to/file.js
89
+ scai comm <file>
98
90
  ```
99
91
 
100
- A cleaned-up version will be written to:
101
-
102
- ```
103
- path/to/refactored/file.refactored.js
104
- ```
92
+ Adds clear, helpful comments to the code. Optional flag:
93
+ - `-a, --apply` — Overwrite the original file with the commented version
105
94
 
106
95
  ---
107
96
 
108
- ### 🧩 Generate Tests
97
+ ### 🔍 Summarize a code file
109
98
 
110
99
  ```bash
111
- scai generate-tests <file>
100
+ scai summ <file>
112
101
  ```
113
102
 
114
- Automatically generates Jest test files for specified JavaScript/TypeScript modules.
103
+ Prints a summary of what the code does directly to your terminal.
115
104
 
116
105
  ---
117
106
 
118
- ### 🔍 Check Git status
107
+ ### 📝 Update the changelog
119
108
 
120
109
  ```bash
121
- scai git
110
+ scai changelog
122
111
  ```
123
112
 
124
- Useful overview of current branch, commits, and uncommitted changes.
113
+ Analyzes the current Git diff and updates (or creates) a `CHANGELOG.md` file with relevant public-facing changes.
125
114
 
126
115
  ---
127
116
 
128
117
  ## 🔐 License & Fair Use
129
118
 
130
- **scai is free to use** for individuals, teams, and companies — including in commercial work.
119
+ **scai is free to use** for individuals, teams, and companies — including in commercial work.
120
+
131
121
  You may:
132
122
 
133
123
  - ✅ Use it internally in your projects
134
- - ✅ Use it at work or in commercial software development
135
- - ✅ Share and recommend it to colleagues
124
+ - ✅ Use it at work or in commercial development
125
+ - ✅ Share and recommend it
136
126
 
137
- However:
127
+ But you may not:
138
128
 
139
- - ❌ You may not **resell**, repackage, or redistribute **scai** as a commercial product or SaaS offering
140
- - ❌ You may not claim ownership or original authorship
129
+ - ❌ Repackage or resell **scai** as a product or SaaS
130
+ - ❌ Claim ownership of the tool
141
131
 
142
- For full terms, see the [LICENSE](./LICENSE) file.
132
+ See [LICENSE](./LICENSE) for full terms.
143
133
 
144
134
  ---
@@ -0,0 +1,39 @@
1
+ // src/commands/handleChangelogUpdate.ts
2
+ import { execSync } from 'child_process';
3
+ import fs from 'fs/promises';
4
+ import path from 'path';
5
+ import { runPromptPipeline } from '../pipeline/runPipeline.js';
6
+ import { changelogModule } from '../pipeline/modules/changeLogModule.js';
7
+ export async function handleChangelogUpdate() {
8
+ try {
9
+ let diff = execSync("git diff", { encoding: "utf-8" }).trim();
10
+ if (!diff) {
11
+ diff = execSync("git diff --cached", { encoding: "utf-8" }).trim();
12
+ }
13
+ if (!diff) {
14
+ console.log("⚠️ No staged or unstaged changes to include in changelog.");
15
+ return;
16
+ }
17
+ const result = await runPromptPipeline([changelogModule], { code: diff });
18
+ if (!result.code.trim()) {
19
+ console.log("✅ No significant changes for changelog.");
20
+ return;
21
+ }
22
+ const root = execSync("git rev-parse --show-toplevel", { encoding: "utf-8" }).trim();
23
+ const changelogPath = path.join(root, "CHANGELOG.md");
24
+ let existing = "";
25
+ try {
26
+ existing = await fs.readFile(changelogPath, "utf-8");
27
+ }
28
+ catch {
29
+ console.log("📄 Creating new CHANGELOG.md");
30
+ }
31
+ const today = new Date().toISOString().split("T")[0];
32
+ const newEntry = `\n\n## ${today}\n\n${result.code}`;
33
+ await fs.writeFile(changelogPath, existing + newEntry, "utf-8");
34
+ console.log("📝 CHANGELOG.md updated.");
35
+ }
36
+ catch (err) {
37
+ console.error("❌ Failed to update changelog:", err.message);
38
+ }
39
+ }
@@ -3,11 +3,12 @@ import readline from 'readline';
3
3
  function askUserToChoose(suggestions) {
4
4
  return new Promise((resolve) => {
5
5
  console.log('\n💡 AI-suggested commit messages:\n');
6
- suggestions.forEach((msg, i) => {
6
+ suggestions.slice(0, 3).forEach((msg, i) => {
7
7
  console.log(`${i + 1}) ${msg}`);
8
8
  });
9
- console.log(`${suggestions.length + 1}) 🔁 Regenerate suggestions`);
10
- console.log(`${suggestions.length + 2}) ✍️ Write your own commit message`);
9
+ console.log('\n---');
10
+ console.log('4) 🔁 Regenerate suggestions');
11
+ console.log('5) ✍️ Write your own commit message');
11
12
  const rl = readline.createInterface({
12
13
  input: process.stdin,
13
14
  output: process.stdout,
@@ -0,0 +1,36 @@
1
+ import fs from 'fs/promises';
2
+ import { summaryModule } from '../pipeline/modules/summaryModule.js';
3
+ import readline from 'readline';
4
+ export async function summarizeFile(filepath) {
5
+ let code = '';
6
+ if (filepath) {
7
+ try {
8
+ code = await fs.readFile(filepath, 'utf-8');
9
+ }
10
+ catch (err) {
11
+ console.error(`❌ Could not read or summarize ${filepath}:`, err.message);
12
+ return;
13
+ }
14
+ }
15
+ // ⬇️ Add this check right here
16
+ else if (process.stdin.isTTY) {
17
+ console.error('❌ No file provided and no piped input.\n👉 Usage: scai summ <file> or cat file | scai summ');
18
+ return;
19
+ }
20
+ else {
21
+ const rl = readline.createInterface({
22
+ input: process.stdin,
23
+ output: process.stdout,
24
+ terminal: false,
25
+ });
26
+ for await (const line of rl) {
27
+ code += line + '\n';
28
+ }
29
+ }
30
+ if (code.trim()) {
31
+ await summaryModule.run({ code });
32
+ }
33
+ else {
34
+ console.error('❌ No code provided to summarize.');
35
+ }
36
+ }
@@ -0,0 +1,23 @@
1
+ export class ModelConfig {
2
+ static setModel(model) {
3
+ this.model = model;
4
+ console.log(`📦 Model set to: ${model}`);
5
+ }
6
+ static getModel() {
7
+ return this.model;
8
+ }
9
+ static setLanguage(lang) {
10
+ this.language = lang;
11
+ console.log(`🗣️ Language set to: ${lang}`);
12
+ }
13
+ static getLanguage() {
14
+ return this.language;
15
+ }
16
+ static logCurrentConfig() {
17
+ console.log(`🔧 Current configuration:`);
18
+ console.log(` Model : ${this.model}`);
19
+ console.log(` Language: ${this.language}`);
20
+ }
21
+ }
22
+ ModelConfig.model = 'codellama:7b';
23
+ 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
- import { updateReadmeIfNeeded } from "./commands/ReadmeCmd.js";
8
10
  import { generateTests } from "./commands/TestGenCmd.js";
9
- // Import the model check and initialization logic
10
11
  import { bootstrap } from './modelSetup.js';
12
+ import { ModelConfig } from './config/ModelConfig.js';
13
+ import { summarizeFile } from "./commands/SummaryCmd.js";
14
+ import { handleChangelogUpdate } from './commands/ChangeLogUpdateCmd.js';
11
15
  // Create the CLI instance
12
16
  const cmd = new Command('scai')
13
- .version('0.1.0');
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,30 +26,48 @@ cmd
20
26
  console.log('✅ Model initialization completed!');
21
27
  });
22
28
  cmd
23
- .command('env')
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('refac <file>')
37
- .description('Suggest a refactor for the given JS/TS file')
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
41
- .command('readme')
42
- .description('Update README.md if relevant changes were made')
43
- .action(updateReadmeIfNeeded);
39
+ .command('changelog')
40
+ .description('Update or create the CHANGELOG.md based on current Git diff')
41
+ .action(async () => {
42
+ await handleChangelogUpdate();
43
+ });
44
+ cmd
45
+ .command('summ [file]')
46
+ .description('Print a summary of the given file to the terminal')
47
+ .action((file) => summarizeFile(file));
44
48
  cmd
45
- .command('generate-tests <file>')
49
+ .command('git')
50
+ .description('Check Git status')
51
+ .action(checkGit);
52
+ cmd
53
+ .command('env')
54
+ .description('Check environment variables')
55
+ .action(checkEnv);
56
+ cmd
57
+ .command('gen-tests <file>')
46
58
  .description('Generate a Jest test file for the specified JS/TS module')
47
59
  .action((file) => generateTests(file));
48
- // Parse CLI arguments
60
+ cmd
61
+ .command('config')
62
+ .description('Show the currently active model and language settings')
63
+ .action(() => {
64
+ ModelConfig.logCurrentConfig();
65
+ });
66
+ // ✅ Now that commands are defined, parse args
49
67
  cmd.parse(process.argv);
68
+ // ✅ Apply global options after parsing
69
+ const opts = cmd.opts();
70
+ if (opts.model)
71
+ ModelConfig.setModel(opts.model);
72
+ if (opts.lang)
73
+ ModelConfig.setLanguage(opts.lang);
@@ -0,0 +1,30 @@
1
+ import { ModelConfig } from '../../config/ModelConfig.js';
2
+ export const changelogModule = {
3
+ name: 'changelogModule',
4
+ description: 'Generates changelog entry based on Git diff',
5
+ async run(input) {
6
+ const model = ModelConfig.getModel();
7
+ const prompt = `
8
+ You're an experienced changelog writer. Based on this Git diff, write a markdown bullet-point entry suitable for CHANGELOG.md:
9
+
10
+ --- DIFF START ---
11
+ ${input.code}
12
+ --- DIFF END ---
13
+
14
+ ✅ If the changes are significant, return a changelog entry.
15
+ ❌ If not, return ONLY: "NO UPDATE".
16
+ `.trim();
17
+ const res = await fetch('http://localhost:11434/api/generate', {
18
+ method: 'POST',
19
+ headers: { 'Content-Type': 'application/json' },
20
+ body: JSON.stringify({
21
+ model,
22
+ prompt,
23
+ stream: false,
24
+ }),
25
+ });
26
+ const data = await res.json();
27
+ const output = data.response?.trim() ?? '';
28
+ return { code: output === 'NO UPDATE' ? '' : output };
29
+ },
30
+ };
@@ -1,13 +1,21 @@
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 JavaScript engineer.
9
+ You are a senior ${lang.toUpperCase()} engineer reviewing source code.
7
10
 
8
- Add clear and helpful single-line // comments to complex parts of the code.
9
- - Do NOT remove or change in ANY way any parts of the code!
10
- - Output ALL of the original code with your comments included
11
+ Your task is to add clear and helpful single-line // comments to complex or non-obvious parts of the code.
12
+
13
+ ⚠️ VERY IMPORTANT RULES:
14
+ - You MUST return the ENTIRE original code.
15
+ - You MUST NOT remove, replace, reformat, or alter ANY code.
16
+ - Only add single-line // comments in appropriate places.
17
+ - Do NOT wrap the code in markdown or code blocks.
18
+ - The code should be valid ${lang.toUpperCase()} after your changes.
11
19
 
12
20
  --- CODE START ---
13
21
  ${input.code}
@@ -17,8 +25,8 @@ ${input.code}
17
25
  method: 'POST',
18
26
  headers: { 'Content-Type': 'application/json' },
19
27
  body: JSON.stringify({
20
- model: 'mistral',
21
- prompt: prompt,
28
+ model,
29
+ prompt,
22
30
  stream: false,
23
31
  }),
24
32
  });
@@ -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're a senior TypeScript developer. Given the following class or module, generate a Jest test file.
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: 'mistral',
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({ code }) {
5
+ async run(input) {
6
+ const model = ModelConfig.getModel();
7
+ const lang = ModelConfig.getLanguage();
5
8
  const prompt = `
6
- You are a senior JavaScript/TypeScript engineer.
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
- const res = await fetch('http://localhost:11434/api/generate', {
18
- method: 'POST',
19
- headers: { 'Content-Type': 'application/json' },
20
- body: JSON.stringify({ model: 'mistral', prompt: prompt, stream: false })
21
- });
22
- const data = await res.json();
23
- return { code: data.response?.trim() ?? '' };
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,27 @@
1
+ import { ModelConfig } from '../../config/ModelConfig.js';
1
2
  export const summaryModule = {
2
3
  name: 'summary',
3
- description: 'Append // Summary of changes at the end',
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 developer assistant.
9
+ You are a senior ${lang.toUpperCase()} engineer.
7
10
 
8
- Take the following code and return it exactly as-is,
9
- but add a summary at the very end in this format:
11
+ Take the following source code and do NOT modify it in any way. Your task is:
10
12
 
11
- // Summary of refactor:
12
- // - Did X
13
- // - Did Y
14
- // - etc.
13
+ 1. Output the original code exactly as it is.
14
+ 2. After the code, append a short summary in this format:
15
15
 
16
- DO NOT modify or omit any existing code.
17
- Just append the summary.
16
+ // Summary of code:
17
+ // - [What the code does at a high level]
18
+ // - [Main features or components]
19
+ // - [Any interesting logic or patterns]
20
+
21
+ ⚠️ IMPORTANT:
22
+ - Do NOT omit or alter any part of the original code.
23
+ - Do NOT insert comments inside the code.
24
+ - Only append the summary AFTER the full code.
18
25
 
19
26
  --- CODE START ---
20
27
  ${code}
@@ -23,9 +30,18 @@ ${code}
23
30
  const res = await fetch('http://localhost:11434/api/generate', {
24
31
  method: 'POST',
25
32
  headers: { 'Content-Type': 'application/json' },
26
- body: JSON.stringify({ model: 'llama3', prompt: prompt, stream: false })
33
+ body: JSON.stringify({ model, prompt, stream: false })
27
34
  });
28
35
  const data = await res.json();
29
- return { code: data.response?.trim() ?? '' };
36
+ const summary = data.response?.trim();
37
+ if (summary) {
38
+ console.log('\n📝 Code Summary:\n');
39
+ console.log(summary);
40
+ }
41
+ else {
42
+ console.warn('⚠️ No summary generated.');
43
+ }
44
+ // Return unchanged input so it’s pipe-safe, if reused in pipelines
45
+ return { code };
30
46
  }
31
47
  };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "scai",
3
- "version": "0.1.11",
3
+ "version": "0.1.13",
4
4
  "type": "module",
5
5
  "bin": {
6
6
  "scai": "./dist/index.js"
@@ -1,116 +0,0 @@
1
- // Import required modules
2
- import { execSync } from 'child_process';
3
- import readline from 'readline';
4
- // Function to ask the user to choose a commit message suggestion
5
- 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
- return new Promise((resolve) => {
13
- console.log('\n💡 AI-suggested commit messages:\n');
14
- suggestions.forEach((msg, i) => {
15
- console.log(`${i + 1}) ${msg}`);
16
- });
17
- console.log(`${suggestions.length + 1}) 🔁 Regenerate suggestions`);
18
- console.log(`${suggestions.length + 2}) ✍️ Write your own commit message`);
19
- rl.question(`\n👉 Choose a commit message [1-${suggestions.length + 2}]: `, (answer) => {
20
- // Close the readline interface and resolve the promise with user choice
21
- 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);
26
- }
27
- else if (choice === suggestions.length + 2) {
28
- resolve('custom');
29
- }
30
- else {
31
- resolve(choice - 1); // Return 0-based index (0 to 3)
32
- }
33
- });
34
- });
35
- }
36
- // Async function to generate commit message suggestions using an API call
37
- async function generateSuggestions(prompt) {
38
- // Fetch the suggestions from an API and return them as an array of strings
39
- const res = await fetch("http://localhost:11434/api/generate", {
40
- method: "POST",
41
- headers: { "Content-Type": "application/json" },
42
- body: JSON.stringify({
43
- model: "llama3",
44
- prompt,
45
- stream: false,
46
- }),
47
- });
48
- const { response } = await res.json();
49
- // Check for validity of the LLM response and return an array of commit messages
50
- if (!response || typeof response !== 'string') {
51
- throw new Error('Invalid LLM response');
52
- }
53
- const lines = response.trim().split('\n').filter(line => /^\d+\.\s+/.test(line));
54
- return lines.map(line => line.replace(/^\d+\.\s+/, '').replace(/^"(.*)"$/, '$1').trim());
55
- }
56
- // Function to prompt the user for a custom commit message
57
- async function promptCustomMessage() {
58
- // Read the user's input for their custom commit message
59
- return new Promise((resolve) => {
60
- const rl = readline.createInterface({
61
- input: process.stdin,
62
- output: process.stdout,
63
- });
64
- rl.question('\n📝 Enter your custom commit message:\n> ', (input) => {
65
- rl.close();
66
- resolve(input.trim());
67
- });
68
- });
69
- }
70
- // Export the main function to suggest a commit message based on user input and generated suggestions
71
- export async function suggestCommitMessage(options) {
72
- try {
73
- let diff = execSync("git diff", { encoding: "utf-8" }).trim();
74
- if (!diff) {
75
- diff = execSync("git diff --cached", { encoding: "utf-8" }).trim();
76
- }
77
- if (!diff) {
78
- console.log('⚠️ No staged changes to suggest a message for.');
79
- return;
80
- }
81
- const prompt = `Suggest 3 concise, conventional Git commit message options for this diff. Return ONLY the commit messages, numbered 1 to 3, like so:
82
- 1. ...
83
- 2. ...
84
- 3. ...
85
-
86
- Here is the diff:
87
- ${diff}`;
88
- let message = null;
89
- while (message === null) {
90
- const suggestions = await generateSuggestions(prompt);
91
- const choice = await askUserToChoose(suggestions);
92
- if (choice === suggestions.length) {
93
- // User chose "Regenerate"
94
- console.log('\n🔄 Regenerating suggestions...\n');
95
- continue;
96
- }
97
- if (choice === 'custom') {
98
- message = await promptCustomMessage();
99
- break;
100
- }
101
- message = suggestions[choice];
102
- }
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" });
108
- }
109
- execSync(`git commit -m "${message.replace(/"/g, '\\"')}"`, { stdio: 'inherit' });
110
- console.log('✅ Committed with selected message.');
111
- }
112
- }
113
- catch (err) {
114
- console.error('❌ Error in commit message suggestion:', err.message);
115
- }
116
- }
@@ -1,61 +0,0 @@
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
- let diff = execSync("git diff", { encoding: "utf-8" }).trim();
7
- if (!diff) {
8
- diff = execSync("git diff --cached", { encoding: "utf-8" }).trim();
9
- }
10
- if (!diff) {
11
- console.log('⚠️ No staged changes to suggest a message for.');
12
- return;
13
- }
14
- const readmePath = path.resolve("README.md");
15
- let readme = "";
16
- try {
17
- readme = await fs.readFile(readmePath, "utf-8");
18
- }
19
- catch {
20
- console.log("📄 No existing README.md found, skipping update.");
21
- return;
22
- }
23
- const prompt = `
24
- You're an experienced documentation writer. Here's the current README:
25
-
26
- --- README START ---
27
- ${readme}
28
- --- README END ---
29
-
30
- Here is a Git diff of recent code changes:
31
-
32
- --- DIFF START ---
33
- ${diff}
34
- --- DIFF END ---
35
-
36
- ✅ If the changes are significant and relevant to the public-facing documentation, return an updated README.
37
- ❌ If they are not, return ONLY: "NO UPDATE".
38
- `;
39
- const res = await fetch("http://localhost:11434/api/generate", {
40
- method: "POST",
41
- headers: { "Content-Type": "application/json" },
42
- body: JSON.stringify({
43
- model: "llama3",
44
- prompt,
45
- stream: false,
46
- }),
47
- });
48
- const { response } = await res.json();
49
- const result = response.trim();
50
- if (result === "NO UPDATE") {
51
- console.log("✅ No significant changes for README.");
52
- }
53
- else {
54
- await fs.writeFile(readmePath, result, "utf-8");
55
- console.log("📝 README.md updated based on significant changes.");
56
- }
57
- }
58
- catch (err) {
59
- console.error("❌ Failed to update README:", err.message);
60
- }
61
- }