Packages
fnord
0.8.24
0.9.40
0.9.39
0.9.38
0.9.37
0.9.36
0.9.35
0.9.34
0.9.33
0.9.32
0.9.31
0.9.30
0.9.29
0.9.28
0.9.27
0.9.26
0.9.25
0.9.24
0.9.23
0.9.22
0.9.21
0.9.20
0.9.19
0.9.18
0.9.17
0.9.16
0.9.15
0.9.14
0.9.13
0.9.12
0.9.11
0.9.10
0.9.9
0.9.8
0.9.7
0.9.6
0.9.5
0.9.4
0.9.3
0.9.2
0.9.1
0.9.0
0.8.99
0.8.98
0.8.97
0.8.96
0.8.95
0.8.94
0.8.93
0.8.92
0.8.91
0.8.90
0.8.89
0.8.88
0.8.87
0.8.86
0.8.85
0.8.84
0.8.83
0.8.82
0.8.81
0.8.80
0.8.79
0.8.78
0.8.77
0.8.76
0.8.75
0.8.74
0.8.73
0.8.72
0.8.71
0.8.70
0.8.69
0.8.68
0.8.67
0.8.66
0.8.65
0.8.64
0.8.63
0.8.62
0.8.61
0.8.60
0.8.59
0.8.58
0.8.57
0.8.56
0.8.55
0.8.54
0.8.53
0.8.52
0.8.51
0.8.50
0.8.49
0.8.48
0.8.47
0.8.46
0.8.45
0.8.44
0.8.43
0.8.42
0.8.41
0.8.40
0.8.39
0.8.38
0.8.37
0.8.36
0.8.35
0.8.34
0.8.33
0.8.32
0.8.31
0.8.30
0.8.29
0.8.27
0.8.26
0.8.25
0.8.24
0.8.23
0.8.22
0.8.21
0.8.20
0.8.19
0.8.18
0.8.17
0.8.16
0.8.15
0.8.14
0.8.13
0.8.12
0.8.11
0.8.1
0.8.0
0.7.24
0.7.23
0.7.22
0.7.21
0.7.20
0.7.19
0.7.18
0.7.17
0.7.16
0.7.15
0.7.14
0.7.13
0.7.12
0.7.11
0.7.10
0.7.9
0.7.8
0.7.7
0.7.6
0.7.5
0.7.3
0.7.2
0.7.1
0.7.0
0.6.9
0.6.8
0.6.7
0.6.6
0.6.5
0.6.4
0.6.3
0.6.1
0.6.0
0.5.9
0.5.8
0.5.7
0.5.6
0.5.5
0.5.4
0.5.3
0.5.2
0.5.1
0.5.0
0.4.44
0.4.43
0.4.42
0.4.41
0.4.40
0.4.39
0.4.38
0.4.37
0.4.36
0.4.35
0.4.34
0.4.33
0.4.32
0.4.30
0.4.29
0.4.28
0.4.27
0.4.26
0.4.25
0.4.24
0.4.23
0.4.22
0.4.21
0.4.20
0.4.19
0.4.18
0.4.17
0.4.16
0.4.15
0.4.14
0.4.13
0.4.12
0.4.11
0.4.10
0.4.9
0.4.8
0.4.7
0.4.6
0.4.5
0.4.4
0.4.3
0.4.2
0.4.1
0.4.0
0.3.0
0.2.0
0.1.0
AI code archaeology
Current section
Files
Jump to
Current section
Files
lib/ai/agent/shell_cmd_parser.ex
defmodule AI.Agent.ShellCmdParser do
@behaviour AI.Agent
@max_attempts 3
@model AI.Model.balanced()
@prompt """
You are an expert shell parser.
Your job is to analyze a given shell command string, extract its structural components, and output a strict JSON object as specified below.
Instructions:
- Read the command as a user would type it in a shell, e.g., `git -C path rev-parse --show-toplevel`.
- Parse out the **base command** (the executable), and group any "subcommand" or "verb" that acts as a command selector.
- For commands like `git rev-parse`, the subcommand is `rev-parse`.
- The "cmd" field must be the main executable (e.g., "git").
- The "args" field must be an array of all following tokens, preserving their order.
- The "approval_bits" array must contain the executable and any subcommands that define a *unique operation*.
- For example, for `git -C path rev-parse --show-toplevel`, the approval_bits are ["git", "rev-parse"] (ignore flags and options).
- For `find . -type f`, approval_bits is just ["find"].
- For `docker run -it ubuntu`, approval_bits is ["docker", "run"].
- If the input cannot be confidently parsed, output only: `{"error": "<reason>"}`
Output format
```json
{
"cmd": "<base_command>",
"args": ["<arg1>", "<arg2>", ...],
"approval_bits": ["<base_command>", "<subcommand>", ...]
}
```
Examples
- Input: `git -C /repo diff --stat`
Output:
```json
{
"cmd": "git",
"args": ["-C", "/repo", "diff", "--stat"],
"approval_bits": ["git", "diff"]
}
```
- Input: `find . -type f`
Output:
```json
{
"cmd": "find",
"args": [".", "-type", "f"],
"approval_bits": ["find"]
}
```
- Input: `docker run -it alpine`
Output:
```json
{
"cmd": "docker",
"args": ["run", "-it", "alpine"],
"approval_bits": ["docker", "run"]
}
```
- Input: `ls -l`
Output:
```json
{
"cmd": "ls",
"args": ["-l"],
"approval_bits": ["ls"]
}
```
Do NOT include explanations, summary, or markdown fences in your response.
Respond in pure, strict JSON format as specified above.
Special rules
- Do **not** attempt to guess meanings—split strictly on whitespace, respecting quoted arguments.
- Treat the first token as "cmd", everything else as "args".
- "approval_bits" should only include the executable and literal subcommands (verbs), not options or arguments.
- If the command is ambiguous or doesn't fit the above (e.g., missing executable), output only the error object.
- If the command is obviously malformed or incomplete (e.g., missing or duplicated command or arguments, or contains only flags/options without a command), respond with an error.
- If the command includes any of the following constructs, the "cmd" is "sh", and "args" is ["-c", "<entire_command>"]:
- redirection (`>`, `>>`, `<`, `2>`)
- pipes (`|`)
- subshells or command substitution (`$()`, `` ` ``)
- logical operators (`&&`, `||`)
- semicolons (`;`)
- backgrounding (`&`)
- process substitution (`<(...)`, `>(...)`)
"""
@impl AI.Agent
def get_response(opts) do
with {:ok, shell_cmd} <- Map.fetch(opts, :shell_cmd) do
shell_cmd
|> String.trim()
|> do_stuff()
end
end
defp do_stuff(shell_cmd, attempt \\ 1)
defp do_stuff(shell_cmd, attempt) when attempt <= @max_attempts do
shell_cmd
|> get_completion()
|> validate_response()
|> case do
{:ok, result} ->
{:ok, result}
{:rejected, reason} ->
{:error, "Command rejected: #{reason}"}
{:invalid_format, reason} ->
UI.warn("[shell-cmd-parser]", "Input: #{inspect(shell_cmd)}, Error: #{inspect(reason)}")
do_stuff(shell_cmd, attempt + 1)
end
end
defp do_stuff(_shell_cmd, _attempt) do
{:error, "Failed to parse command after #{@max_attempts} attempts."}
end
defp get_completion(shell_cmd) do
AI.Completion.get(
log_msgs: false,
log_tool_calls: false,
model: @model,
messages: [
AI.Util.system_msg(@prompt),
AI.Util.user_msg("""
Please parse the following shell command:
```sh
#{shell_cmd}
```
""")
]
)
|> case do
{:ok, %{response: response}} -> {:ok, response}
{:error, reason} -> {:error, reason}
end
end
defp validate_response({:error, reason}), do: {:error, reason}
defp validate_response({:ok, response}) do
case Jason.decode(response) do
{:error, reason} ->
{:invalid_format, "Invalid JSON response: #{inspect(reason)}"}
{:ok, %{"error" => error}} ->
{:rejected, error}
{:ok, %{"cmd" => cmd, "args" => args, "approval_bits" => approval_bits} = parsed} ->
cond do
not is_binary(cmd) ->
{:invalid_format, "The 'cmd' field must be a string."}
not is_list(args) ->
{:invalid_format, "The 'args' field must be an array."}
not is_list(approval_bits) ->
{:invalid_format, "The 'approval_bits' field must be an array."}
true ->
{:ok, parsed}
end
end
end
end