scai 0.1.0 → 0.1.2
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/dist/index.js +14 -2
- package/dist/modelSetup.js +96 -0
- package/package.json +12 -2
- package/src/commands/CommitSuggesterCmd.ts +0 -46
- package/src/commands/EnvCmd.ts +0 -11
- package/src/commands/GitCmd.ts +0 -28
- package/src/commands/RefactorCmd.ts +0 -49
- package/src/index.ts +0 -32
- package/tsconfig.json +0 -15
package/dist/index.js
CHANGED
|
@@ -4,8 +4,19 @@ 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
|
-
|
|
7
|
+
// Import the model check and initialization logic
|
|
8
|
+
import { bootstrap } from './modelSetup.js';
|
|
9
|
+
// Create the CLI instance
|
|
10
|
+
const cmd = new Command('scai')
|
|
8
11
|
.version('0.1.0');
|
|
12
|
+
// Define CLI commands
|
|
13
|
+
cmd
|
|
14
|
+
.command('init')
|
|
15
|
+
.description('Initialize the model and download required models')
|
|
16
|
+
.action(async () => {
|
|
17
|
+
await bootstrap();
|
|
18
|
+
console.log('✅ Model initialization completed!');
|
|
19
|
+
});
|
|
9
20
|
cmd
|
|
10
21
|
.command('env')
|
|
11
22
|
.description('Check environment variables')
|
|
@@ -15,7 +26,7 @@ cmd
|
|
|
15
26
|
.description('Check Git status')
|
|
16
27
|
.action(checkGit);
|
|
17
28
|
cmd
|
|
18
|
-
.command('
|
|
29
|
+
.command('commit')
|
|
19
30
|
.description('Suggest a commit message from staged changes')
|
|
20
31
|
.option('-c, --commit', 'Automatically commit with suggested message')
|
|
21
32
|
.action(suggestCommitMessage);
|
|
@@ -23,4 +34,5 @@ cmd
|
|
|
23
34
|
.command('refactor <file>')
|
|
24
35
|
.description('Suggest a refactor for the given JS file')
|
|
25
36
|
.action((file) => handleRefactor(file));
|
|
37
|
+
// Parse CLI arguments
|
|
26
38
|
cmd.parse(process.argv);
|
|
@@ -0,0 +1,96 @@
|
|
|
1
|
+
import { spawn, execSync } from 'child_process';
|
|
2
|
+
import * as readline from 'readline';
|
|
3
|
+
// Port and models
|
|
4
|
+
const MODEL_PORT = 11434;
|
|
5
|
+
const REQUIRED_MODELS = ['llama3', 'mistral']; // Add more if needed
|
|
6
|
+
// Ensure Ollama is running
|
|
7
|
+
async function ensureOllamaRunning() {
|
|
8
|
+
try {
|
|
9
|
+
const res = await fetch(`http://localhost:${MODEL_PORT}`);
|
|
10
|
+
if (res.ok) {
|
|
11
|
+
console.log('✅ Ollama is already running.');
|
|
12
|
+
}
|
|
13
|
+
}
|
|
14
|
+
catch (error) {
|
|
15
|
+
console.error('🟡 Ollama is not running. Starting it in the background...');
|
|
16
|
+
if (error instanceof Error) {
|
|
17
|
+
console.error('❌ Error during Ollama health check:', error.message);
|
|
18
|
+
}
|
|
19
|
+
else {
|
|
20
|
+
console.error('❌ Unexpected error during Ollama health check:', error);
|
|
21
|
+
}
|
|
22
|
+
const child = spawn('ollama', ['serve'], {
|
|
23
|
+
detached: true,
|
|
24
|
+
stdio: 'ignore',
|
|
25
|
+
windowsHide: true,
|
|
26
|
+
});
|
|
27
|
+
child.unref();
|
|
28
|
+
await new Promise((res) => setTimeout(res, 3000)); // Wait a bit for server to be ready
|
|
29
|
+
}
|
|
30
|
+
}
|
|
31
|
+
// Get installed models via ollama list
|
|
32
|
+
async function getInstalledModels() {
|
|
33
|
+
try {
|
|
34
|
+
const result = execSync('ollama list', { encoding: 'utf-8' });
|
|
35
|
+
const installedModels = result
|
|
36
|
+
.split('\n')
|
|
37
|
+
.map((line) => line.split(/\s+/)[0].split(':')[0]) // Get model name, ignore version (e.g., 'llama3:latest' becomes 'llama3')
|
|
38
|
+
.filter((model) => REQUIRED_MODELS.includes(model)); // Filter based on required models
|
|
39
|
+
return installedModels;
|
|
40
|
+
}
|
|
41
|
+
catch (error) {
|
|
42
|
+
console.error('❌ Failed to fetch installed models:', error instanceof Error ? error.message : error);
|
|
43
|
+
return [];
|
|
44
|
+
}
|
|
45
|
+
}
|
|
46
|
+
// Prompt user for input
|
|
47
|
+
function promptUser(question) {
|
|
48
|
+
const rl = readline.createInterface({
|
|
49
|
+
input: process.stdin,
|
|
50
|
+
output: process.stdout,
|
|
51
|
+
});
|
|
52
|
+
return new Promise((resolve) => rl.question(question, (answer) => {
|
|
53
|
+
rl.close();
|
|
54
|
+
resolve(answer.trim());
|
|
55
|
+
}));
|
|
56
|
+
}
|
|
57
|
+
// Ensure all required models are downloaded
|
|
58
|
+
async function ensureModelsDownloaded() {
|
|
59
|
+
const installedModels = await getInstalledModels();
|
|
60
|
+
const missingModels = REQUIRED_MODELS.filter((model) => !installedModels.includes(model));
|
|
61
|
+
if (missingModels.length === 0) {
|
|
62
|
+
console.log('✅ All required models are already installed.');
|
|
63
|
+
return;
|
|
64
|
+
}
|
|
65
|
+
console.log(`🟡 Missing models: ${missingModels.join(', ')}`);
|
|
66
|
+
const answer = await promptUser('Do you want to download the missing models now? (y/N): ');
|
|
67
|
+
if (answer.toLowerCase() !== 'y') {
|
|
68
|
+
console.log('🚫 Missing models not downloaded. Exiting.');
|
|
69
|
+
process.exit(1);
|
|
70
|
+
}
|
|
71
|
+
for (const model of missingModels) {
|
|
72
|
+
try {
|
|
73
|
+
console.log(`⬇️ Pulling model: ${model} ...`);
|
|
74
|
+
execSync(`ollama pull ${model}`, { stdio: 'inherit' });
|
|
75
|
+
console.log(`✅ Successfully pulled ${model}.`);
|
|
76
|
+
}
|
|
77
|
+
catch (err) {
|
|
78
|
+
console.error(`❌ Failed to pull ${model}:`, err instanceof Error ? err.message : err);
|
|
79
|
+
process.exit(1);
|
|
80
|
+
}
|
|
81
|
+
}
|
|
82
|
+
}
|
|
83
|
+
// Initialize the application
|
|
84
|
+
export async function bootstrap() {
|
|
85
|
+
try {
|
|
86
|
+
// Ensure Ollama is running only once at the start
|
|
87
|
+
await ensureOllamaRunning();
|
|
88
|
+
// Ensure models are downloaded once
|
|
89
|
+
await ensureModelsDownloaded();
|
|
90
|
+
// Now your CLI logic can proceed here...
|
|
91
|
+
}
|
|
92
|
+
catch (error) {
|
|
93
|
+
console.error('❌ Error during initialization:', error instanceof Error ? error.message : error);
|
|
94
|
+
process.exit(1);
|
|
95
|
+
}
|
|
96
|
+
}
|
package/package.json
CHANGED
|
@@ -1,10 +1,17 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "scai",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.2",
|
|
4
4
|
"type": "module",
|
|
5
5
|
"bin": {
|
|
6
6
|
"scai": "./dist/index.js"
|
|
7
7
|
},
|
|
8
|
+
"repository": {
|
|
9
|
+
"type": "git",
|
|
10
|
+
"url": "git+https://github.com/rzs/scai.git"
|
|
11
|
+
},
|
|
12
|
+
"author": "Rasmus Uhd Norgaard",
|
|
13
|
+
"license": "MIT",
|
|
14
|
+
"keywords": ["cli", "ai", "refactor", "devtools", "local", "typescript"],
|
|
8
15
|
"scripts": {
|
|
9
16
|
"build": "tsc",
|
|
10
17
|
"start": "node dist/index.js"
|
|
@@ -15,5 +22,8 @@
|
|
|
15
22
|
"devDependencies": {
|
|
16
23
|
"@types/node": "^24.0.1",
|
|
17
24
|
"typescript": "^5.8.3"
|
|
18
|
-
}
|
|
25
|
+
},
|
|
26
|
+
"files": [
|
|
27
|
+
"dist/"
|
|
28
|
+
]
|
|
19
29
|
}
|
|
@@ -1,46 +0,0 @@
|
|
|
1
|
-
import { execSync } from 'child_process';
|
|
2
|
-
|
|
3
|
-
export async function suggestCommitMessage(options: { commit?: boolean }) {
|
|
4
|
-
try {
|
|
5
|
-
let diff = execSync("git diff", { encoding: "utf-8" }).trim();
|
|
6
|
-
if (!diff) {
|
|
7
|
-
diff = execSync("git diff --cached", { encoding: "utf-8" }).trim();
|
|
8
|
-
}
|
|
9
|
-
if (!diff) {
|
|
10
|
-
console.log('⚠️ No staged changes to suggest a message for.');
|
|
11
|
-
return;
|
|
12
|
-
}
|
|
13
|
-
|
|
14
|
-
const prompt = `Suggest a concise, conventional Git commit message for this diff. Return ONLY the commit message:\n\n${diff}`;
|
|
15
|
-
const res = await fetch("http://localhost:11434/api/generate", {
|
|
16
|
-
method: "POST",
|
|
17
|
-
headers: { "Content-Type": "application/json" },
|
|
18
|
-
body: JSON.stringify({
|
|
19
|
-
model: "llama3",
|
|
20
|
-
prompt: prompt,
|
|
21
|
-
stream: false
|
|
22
|
-
}),
|
|
23
|
-
});
|
|
24
|
-
|
|
25
|
-
const { response } = await res.json();
|
|
26
|
-
if (response.error) {
|
|
27
|
-
throw new Error(`LLM error: ${response.error}`);
|
|
28
|
-
}
|
|
29
|
-
const message = response?.trim();
|
|
30
|
-
console.log(`${message}`);
|
|
31
|
-
|
|
32
|
-
// 3) Optionally commit
|
|
33
|
-
if (options.commit) {
|
|
34
|
-
// If code not already staged
|
|
35
|
-
const commitDiff = execSync("git diff", { encoding: "utf-8" }).trim();
|
|
36
|
-
if (commitDiff) {
|
|
37
|
-
execSync("git add .",{ encoding: "utf-8" }).trim();
|
|
38
|
-
}
|
|
39
|
-
execSync(`git commit -m "${message.replace(/"/g, '\\"')}"`, { stdio: 'inherit' });
|
|
40
|
-
console.log('✅ Committed with AI-suggested message.');
|
|
41
|
-
}
|
|
42
|
-
|
|
43
|
-
} catch (err: any) {
|
|
44
|
-
console.error('❌ Error in msg command:', err.message);
|
|
45
|
-
}
|
|
46
|
-
}
|
package/src/commands/EnvCmd.ts
DELETED
|
@@ -1,11 +0,0 @@
|
|
|
1
|
-
export function checkEnv() {
|
|
2
|
-
const requiredVars = ["DB_HOST", "API_KEY"];
|
|
3
|
-
const missing = requiredVars.filter((v) => !process.env[v]);
|
|
4
|
-
|
|
5
|
-
if (missing.length) {
|
|
6
|
-
console.warn("❌ Missing env vars:", missing.join(", "));
|
|
7
|
-
} else {
|
|
8
|
-
console.log("✅ All env vars are set");
|
|
9
|
-
}
|
|
10
|
-
}
|
|
11
|
-
|
package/src/commands/GitCmd.ts
DELETED
|
@@ -1,28 +0,0 @@
|
|
|
1
|
-
import { execSync } from "child_process";
|
|
2
|
-
|
|
3
|
-
export function checkGit() {
|
|
4
|
-
try {
|
|
5
|
-
execSync("git rev-parse --is-inside-work-tree", { stdio: "ignore" });
|
|
6
|
-
const status = execSync("git status --porcelain").toString();
|
|
7
|
-
const isClean = status.trim().length === 0;
|
|
8
|
-
|
|
9
|
-
if (isClean) {
|
|
10
|
-
console.log("✅ Git working directory is clean");
|
|
11
|
-
} else {
|
|
12
|
-
console.log("⚠️ Git working directory has uncommitted changes");
|
|
13
|
-
}
|
|
14
|
-
|
|
15
|
-
const branch = execSync("git rev-parse --abbrev-ref HEAD").toString().trim();
|
|
16
|
-
const mainHash = execSync("git rev-parse origin/main").toString().trim();
|
|
17
|
-
const localHash = execSync("git rev-parse HEAD").toString().trim();
|
|
18
|
-
|
|
19
|
-
if (localHash === mainHash) {
|
|
20
|
-
console.log("✅ Up to date with origin/main");
|
|
21
|
-
} else {
|
|
22
|
-
console.log(`🔄 Branch ${branch} is not up to date with origin/main`);
|
|
23
|
-
}
|
|
24
|
-
} catch {
|
|
25
|
-
console.error("❌ Not inside a Git repository");
|
|
26
|
-
}
|
|
27
|
-
}
|
|
28
|
-
|
|
@@ -1,49 +0,0 @@
|
|
|
1
|
-
import fs from 'fs/promises';
|
|
2
|
-
import path from 'path';
|
|
3
|
-
|
|
4
|
-
export async function handleRefactor(filepath: string) {
|
|
5
|
-
try {
|
|
6
|
-
// 1) Read original file
|
|
7
|
-
const code = await fs.readFile(filepath, 'utf-8');
|
|
8
|
-
|
|
9
|
-
// 2) Prompt the LLM
|
|
10
|
-
const prompt = `
|
|
11
|
-
You are a senior JavaScript engineer. Refactor the code below to improve readability,
|
|
12
|
-
modularity, and maintainability. Return ONLY the refactored code.
|
|
13
|
-
|
|
14
|
-
--- ORIGINAL CODE START ---
|
|
15
|
-
${code}
|
|
16
|
-
--- ORIGINAL CODE END ---
|
|
17
|
-
`.trim();
|
|
18
|
-
|
|
19
|
-
const res = await fetch('http://localhost:11434/api/generate', {
|
|
20
|
-
method: 'POST',
|
|
21
|
-
headers: { 'Content-Type': 'application/json' },
|
|
22
|
-
body: JSON.stringify({
|
|
23
|
-
model: 'mistral',
|
|
24
|
-
prompt,
|
|
25
|
-
stream: false
|
|
26
|
-
})
|
|
27
|
-
});
|
|
28
|
-
const data = await res.json();
|
|
29
|
-
console.log('🔍 Raw LLM response:', data);
|
|
30
|
-
|
|
31
|
-
const refactored = data.response?.trim();
|
|
32
|
-
if (!refactored) {
|
|
33
|
-
throw new Error('No refactored code returned from the model.');
|
|
34
|
-
}
|
|
35
|
-
|
|
36
|
-
// 3) Write to a new file alongside the original
|
|
37
|
-
const { dir, name } = path.parse(filepath);
|
|
38
|
-
console.log('dir: ', dir);
|
|
39
|
-
console.log('name: ', name);
|
|
40
|
-
const outDir = path.join(dir, 'refactored');
|
|
41
|
-
await fs.mkdir(outDir, { recursive: true });
|
|
42
|
-
const outPath = path.join(outDir, `${name}.refactored.js`);
|
|
43
|
-
await fs.writeFile(outPath, refactored, 'utf-8');
|
|
44
|
-
|
|
45
|
-
console.log(`✅ Refactored code saved to: ${outPath}`);
|
|
46
|
-
} catch (err: any) {
|
|
47
|
-
console.error('❌ Error in refactor command:', err.message);
|
|
48
|
-
}
|
|
49
|
-
}
|
package/src/index.ts
DELETED
|
@@ -1,32 +0,0 @@
|
|
|
1
|
-
#!/usr/bin/env node
|
|
2
|
-
import { Command } from "commander";
|
|
3
|
-
import { checkEnv } from "./commands/EnvCmd.js";
|
|
4
|
-
import { checkGit } from "./commands/GitCmd.js";
|
|
5
|
-
import { suggestCommitMessage } from "./commands/CommitSuggesterCmd.js";
|
|
6
|
-
import { handleRefactor } from "./commands/RefactorCmd.js";
|
|
7
|
-
|
|
8
|
-
const cmd = new Command('devcheck')
|
|
9
|
-
.version('0.1.0');
|
|
10
|
-
|
|
11
|
-
cmd
|
|
12
|
-
.command('env')
|
|
13
|
-
.description('Check environment variables')
|
|
14
|
-
.action(checkEnv);
|
|
15
|
-
|
|
16
|
-
cmd
|
|
17
|
-
.command('git')
|
|
18
|
-
.description('Check Git status')
|
|
19
|
-
.action(checkGit);
|
|
20
|
-
|
|
21
|
-
cmd
|
|
22
|
-
.command('msg')
|
|
23
|
-
.description('Suggest a commit message from staged changes')
|
|
24
|
-
.option('-c, --commit', 'Automatically commit with suggested message')
|
|
25
|
-
.action(suggestCommitMessage);
|
|
26
|
-
|
|
27
|
-
cmd
|
|
28
|
-
.command('refactor <file>')
|
|
29
|
-
.description('Suggest a refactor for the given JS file')
|
|
30
|
-
.action((file) => handleRefactor(file));
|
|
31
|
-
|
|
32
|
-
cmd.parse(process.argv);
|
package/tsconfig.json
DELETED
|
@@ -1,15 +0,0 @@
|
|
|
1
|
-
{
|
|
2
|
-
"compilerOptions": {
|
|
3
|
-
"target": "ES2020",
|
|
4
|
-
"module": "ESNext",
|
|
5
|
-
"moduleResolution": "node",
|
|
6
|
-
"outDir": "./dist",
|
|
7
|
-
"rootDir": "./src",
|
|
8
|
-
"esModuleInterop": true,
|
|
9
|
-
"strict": true,
|
|
10
|
-
"resolveJsonModule": true,
|
|
11
|
-
"skipLibCheck": true,
|
|
12
|
-
"types": ["node"]
|
|
13
|
-
},
|
|
14
|
-
"include": ["src"]
|
|
15
|
-
}
|