# `CMDC.Sandbox`
[🔗](https://github.com/tupleyun/cmdc/blob/v0.5.0/lib/cmdc/sandbox.ex#L1)

文件操作和命令执行的抽象层。

Sandbox behaviour 将工具层（ReadFile/WriteFile/Shell 等）与底层执行环境解耦，
支持在不同环境下替换实现（本地 OS、Docker 容器、远程沙箱等），
而不需要修改任何工具代码。

## 默认实现

`CMDC.Sandbox.Local` — 直接调用本地操作系统，适合开发和受信任环境。

## 自定义实现示例

    defmodule MyApp.DockerSandbox do
      @behaviour CMDC.Sandbox

      @impl true
      def read_file(path, opts) do
        working_dir = Keyword.get(opts, :working_dir, ".")
        # 在 Docker 容器内读取文件
        docker_exec("cat #{Path.join(working_dir, path)}")
      end

      # ... 其他回调实现
    end

## 工具层透明代理模式

    def execute(args, %{sandbox: sandbox} = ctx) when not is_nil(sandbox) do
      sandbox.execute(args["command"], working_dir: ctx.working_dir)
    end

    def execute(args, ctx) do
      CMDC.Sandbox.Local.execute(args["command"], working_dir: ctx.working_dir)
    end

## 选项

所有回调均接受 `opts` keyword list，常见选项：

- `:working_dir` — 操作相对目录，默认 `"."`
- `:timeout` — 超时毫秒数（仅 `execute/2` 适用）
- `:offset` — 读取偏移行数（仅 `read_file/2` 适用）
- `:limit` — 读取最大行数（仅 `read_file/2` 适用）

# `dir_entry`

```elixir
@type dir_entry() :: %{
  name: String.t(),
  type: :file | :directory,
  size: non_neg_integer() | nil
}
```

目录条目，描述单个文件或子目录。

# `glob_match`

```elixir
@type glob_match() :: %{path: String.t(), type: :file | :directory}
```

glob 匹配文件条目。

# `grep_match`

```elixir
@type grep_match() :: %{
  file: String.t(),
  line: non_neg_integer(),
  content: String.t()
}
```

grep 搜索匹配结果条目。

# `edit_file`

```elixir
@callback edit_file(
  path :: String.t(),
  old_string :: String.t(),
  new_string :: String.t(),
  opts :: keyword()
) :: {:ok, non_neg_integer()} | {:error, :not_found | :not_unique | String.t()}
```

精确字符串替换文件内容（str_replace 模式）。

- 要求 `old_string` 在文件中**唯一**出现，否则返回 `{:error, :not_unique}`
- 成功时返回 `{:ok, replacement_count}`

选项：
- `:working_dir` — 工作目录（默认 `"."`）

# `execute`

```elixir
@callback execute(command :: String.t(), opts :: keyword()) ::
  {:ok, String.t()} | {:error, String.t()}
```

执行 shell 命令。

选项：
- `:working_dir` — 命令执行目录（默认 `"."`）
- `:timeout` — 超时毫秒数（默认 30_000）
- `:env` — 额外环境变量 keyword list

# `file_exists?`

```elixir
@callback file_exists?(path :: String.t(), opts :: keyword()) :: boolean()
```

检查文件或目录是否存在。

选项：
- `:working_dir` — 工作目录（默认 `"."`）

# `glob`

```elixir
@callback glob(pattern :: String.t(), path :: String.t(), opts :: keyword()) ::
  {:ok, [glob_match()]} | {:error, String.t()}
```

在指定路径下搜索匹配 glob 模式的文件。

选项：
- `:working_dir` — 工作目录（默认 `"."`）
- `:max_results` — 最大返回结果数（默认 500）

# `grep`

```elixir
@callback grep(pattern :: String.t(), path :: String.t(), opts :: keyword()) ::
  {:ok, [grep_match()]} | {:error, String.t()}
```

在指定路径下搜索匹配正则模式的文件行。

选项：
- `:working_dir` — 工作目录（默认 `"."`）
- `:include` — glob 过滤，如 `"*.ex"`（默认全部）
- `:case_insensitive` — 是否忽略大小写（默认 false）
- `:max_results` — 最大返回结果数（默认 100）

# `list_dir`

```elixir
@callback list_dir(path :: String.t(), opts :: keyword()) ::
  {:ok, [dir_entry()]} | {:error, String.t()}
```

列出目录内容。

选项：
- `:working_dir` — 工作目录（默认 `"."`）

# `read_file`

```elixir
@callback read_file(path :: String.t(), opts :: keyword()) ::
  {:ok, String.t()} | {:error, String.t()}
```

读取文件内容。

选项：
- `:working_dir` — 工作目录（默认 `"."`）
- `:offset` — 从第几行开始读取（1 起始，默认 1）
- `:limit` — 最多读取多少行（nil 则读取全部）

# `write_file`

```elixir
@callback write_file(path :: String.t(), content :: String.t(), opts :: keyword()) ::
  :ok | {:error, String.t()}
```

写入文件内容（覆盖）。若父目录不存在则自动创建。

选项：
- `:working_dir` — 工作目录（默认 `"."`）

---

*Consult [api-reference.md](api-reference.md) for complete listing*
