@seberto/agcp 1.0.0 → 1.0.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/README.md +36 -0
- package/bin/agcp.js +17 -2
- package/package.json +3 -2
- package/service/ollama.js +52 -2
package/README.md
ADDED
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
# agcp
|
|
2
|
+
|
|
3
|
+
Turn plain English into shell commands using your local AI.
|
|
4
|
+
|
|
5
|
+
## Requirements
|
|
6
|
+
|
|
7
|
+
- [Ollama](https://ollama.com) running locally
|
|
8
|
+
- At least one model pulled (e.g. `ollama pull gemma3`)
|
|
9
|
+
- Node.js 18+
|
|
10
|
+
|
|
11
|
+
## Install
|
|
12
|
+
|
|
13
|
+
```bash
|
|
14
|
+
npm install -g @seberto/agcp
|
|
15
|
+
```
|
|
16
|
+
|
|
17
|
+
## Usage
|
|
18
|
+
|
|
19
|
+
```bash
|
|
20
|
+
agcp "show all running processes"
|
|
21
|
+
agcp "commit with message bug fixed"
|
|
22
|
+
agcp "find all js files modified today"
|
|
23
|
+
```
|
|
24
|
+
|
|
25
|
+
agcp will generate the command, show it to you, and ask before running it.
|
|
26
|
+
|
|
27
|
+
## How it works
|
|
28
|
+
|
|
29
|
+
1. You describe what you want in plain English
|
|
30
|
+
2. agcp sends it to your local Ollama instance
|
|
31
|
+
3. The suggested command is shown
|
|
32
|
+
4. You confirm before it runs — nothing executes without your approval
|
|
33
|
+
|
|
34
|
+
## License
|
|
35
|
+
|
|
36
|
+
ISC
|
package/bin/agcp.js
CHANGED
|
@@ -4,7 +4,7 @@ import { Command } from "commander";
|
|
|
4
4
|
import chalk from "chalk";
|
|
5
5
|
import { confirm } from "@inquirer/prompts";
|
|
6
6
|
import { execSync } from "child_process";
|
|
7
|
-
import generateCommand from "../service/ollama.js";
|
|
7
|
+
import generateCommand, { isSafe } from "../service/ollama.js";
|
|
8
8
|
|
|
9
9
|
const program = new Command();
|
|
10
10
|
|
|
@@ -19,6 +19,16 @@ program
|
|
|
19
19
|
|
|
20
20
|
const command = await generateCommand(prompt);
|
|
21
21
|
|
|
22
|
+
if (command === "UNSAFE") {
|
|
23
|
+
console.log(chalk.red("\n✖ That request is not development related or is unsafe. Only dev commands are supported."));
|
|
24
|
+
process.exit(1);
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
if (!isSafe(command)) {
|
|
28
|
+
console.log(chalk.red(`\n✖ Blocked unsafe command: ${chalk.bold(command)}`));
|
|
29
|
+
process.exit(1);
|
|
30
|
+
}
|
|
31
|
+
|
|
22
32
|
console.log(chalk.cyan(`\n💡 Suggested command:\n ${chalk.bold(command)}\n`));
|
|
23
33
|
|
|
24
34
|
const shouldRun = await confirm({
|
|
@@ -28,7 +38,12 @@ program
|
|
|
28
38
|
|
|
29
39
|
if (shouldRun) {
|
|
30
40
|
console.log(chalk.green("\n▶ Running...\n"));
|
|
31
|
-
|
|
41
|
+
try {
|
|
42
|
+
execSync(command, { stdio: "inherit", shell: true });
|
|
43
|
+
} catch {
|
|
44
|
+
// command already printed its output, non-zero exit is not our error
|
|
45
|
+
}
|
|
46
|
+
|
|
32
47
|
} else {
|
|
33
48
|
console.log(chalk.gray("\nAborted."));
|
|
34
49
|
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@seberto/agcp",
|
|
3
|
-
"version": "1.0.
|
|
3
|
+
"version": "1.0.2",
|
|
4
4
|
"description": "AI CLI that generates and runs shell commands",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"bin": {
|
|
@@ -8,7 +8,8 @@
|
|
|
8
8
|
},
|
|
9
9
|
"files": [
|
|
10
10
|
"bin",
|
|
11
|
-
"service"
|
|
11
|
+
"service",
|
|
12
|
+
"README.md"
|
|
12
13
|
],
|
|
13
14
|
"scripts": {
|
|
14
15
|
"start": "node ./bin/agcp.js"
|
package/service/ollama.js
CHANGED
|
@@ -1,4 +1,32 @@
|
|
|
1
1
|
import axios from "axios";
|
|
2
|
+
import { platform } from "os";
|
|
3
|
+
|
|
4
|
+
const OS_MAP = { darwin: "macOS", linux: "Linux", win32: "Windows" };
|
|
5
|
+
const os = OS_MAP[platform()] ?? platform();
|
|
6
|
+
const shell = platform() === "win32" ? "PowerShell" : "bash";
|
|
7
|
+
|
|
8
|
+
// Commands that are never allowed to run
|
|
9
|
+
const BLOCKED = [
|
|
10
|
+
/rm\s+-rf/,
|
|
11
|
+
/rm\s+-fr/,
|
|
12
|
+
/rmdir/,
|
|
13
|
+
/mkfs/,
|
|
14
|
+
/dd\s+if=/,
|
|
15
|
+
/:\(\)\{.*\}/, // fork bomb
|
|
16
|
+
/chmod\s+-R\s+777/,
|
|
17
|
+
/chown\s+-R/,
|
|
18
|
+
/shutdown/,
|
|
19
|
+
/reboot/,
|
|
20
|
+
/halt/,
|
|
21
|
+
/curl.*\|\s*sh/, // curl pipe to shell
|
|
22
|
+
/wget.*\|\s*sh/,
|
|
23
|
+
/sudo\s+rm/,
|
|
24
|
+
/>\s*\/dev\//, // writing to devices
|
|
25
|
+
];
|
|
26
|
+
|
|
27
|
+
export function isSafe(command) {
|
|
28
|
+
return !BLOCKED.some((pattern) => pattern.test(command));
|
|
29
|
+
}
|
|
2
30
|
|
|
3
31
|
async function generateCommand(userPrompt) {
|
|
4
32
|
const { data } = await axios.get("http://localhost:11434/api/tags");
|
|
@@ -6,7 +34,19 @@ async function generateCommand(userPrompt) {
|
|
|
6
34
|
|
|
7
35
|
if (!model) throw new Error("No Ollama models found. Run 'ollama pull <model>' first.");
|
|
8
36
|
|
|
9
|
-
const prompt = `
|
|
37
|
+
const prompt = `You are a development shell command assistant for ${os} using ${shell}.
|
|
38
|
+
|
|
39
|
+
Allowed topics: git, npm, yarn, node, file navigation, code editors, docker, build tools, package managers, and general software development tasks.
|
|
40
|
+
|
|
41
|
+
Rules:
|
|
42
|
+
- Output a single shell command only
|
|
43
|
+
- No markdown, no backticks, no code blocks, no explanation
|
|
44
|
+
- One line only
|
|
45
|
+
- Only generate commands related to software development
|
|
46
|
+
- Never generate destructive commands like rm -rf, shutdown, reboot, mkfs, dd, fork bombs, or piping curl/wget to shell
|
|
47
|
+
- If the request is not development related, respond with exactly: UNSAFE
|
|
48
|
+
|
|
49
|
+
User request: ${userPrompt}`;
|
|
10
50
|
|
|
11
51
|
const response = await axios.post("http://localhost:11434/api/generate", {
|
|
12
52
|
model,
|
|
@@ -14,7 +54,17 @@ async function generateCommand(userPrompt) {
|
|
|
14
54
|
stream: false,
|
|
15
55
|
});
|
|
16
56
|
|
|
17
|
-
|
|
57
|
+
const raw = response.data.response.trim();
|
|
58
|
+
|
|
59
|
+
// Strip accidental markdown the model may add
|
|
60
|
+
const cleaned = raw
|
|
61
|
+
.replace(/^```[\w]*\n?/m, "")
|
|
62
|
+
.replace(/```$/m, "")
|
|
63
|
+
.replace(/^`|`$/g, "")
|
|
64
|
+
.split("\n")[0]
|
|
65
|
+
.trim();
|
|
66
|
+
|
|
67
|
+
return cleaned;
|
|
18
68
|
}
|
|
19
69
|
|
|
20
70
|
export default generateCommand;
|