@seberto/agcp 1.0.1 → 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/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
- execSync(command, { stdio: "inherit" });
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.1",
3
+ "version": "1.0.2",
4
4
  "description": "AI CLI that generates and runs shell commands",
5
5
  "type": "module",
6
6
  "bin": {
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 = `Convert the following text into a shell command. Return only the command, no explanation, no markdown.\n\n${userPrompt}`;
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
- return response.data.response.trim();
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;