<!-- title: 无人值守：-p 批量改造流水线 -->

# 无人值守：用 `-p` 流式 JSON 驱动批量代码改造流水线

## 概述

本教程聚焦 Claude Code 的 **`-p`（print / 无头）模式**——把 Claude Code 当成一个可被脚本调用、输出可解析 JSON、可被 `jq` 接管的子进程。你会学到如何用 `--bare`、`--output-format json`、`--json-schema`、`--allowedTools` 与 `--permission-mode` 搭建**完全无人值守**的批量改造任务，再用 hooks 作为运行时护栏，最终在 GitHub Actions 里跑通一条"提交即迁移"的流水线。适合已写过几个 Skill / MCP、想把 Claude Code 嵌入 CI 与批处理脚本的工程师。

---

## 前置条件

- 已安装 Claude Code（v2.1.x，本文命令基于 `--bare`、`--json-schema`、`stream-json` 等较新特性）
- 一台装有 `jq` 的 Linux/macOS 机器（`jq` 是解析 JSON 输出的核心）
- 一个有效的鉴权：`ANTHROPIC_API_KEY` 环境变量，或 `apiKeyHelper`（`--bare` 会跳过 OAuth/钥匙串读取）
- 一个待改造的 monorepo（用于跑通实战案例）
- 了解 hooks 基本概念（本系列已有多篇 hooks 文章可参考）

---

## 核心心智模型：两个通道，职责分离

在交互式会话里，你既是"提问者"也是"监督者"。但在无人值守场景下，这两件事必须拆开：

| 通道 | 承载者 | 作用 |
|------|--------|------|
| **结果通道** | `-p` 的 `--output-format json` / `--json-schema` | 让脚本拿到机器可读的结论（改了哪些文件、测试是否通过、花了多少钱） |
| **护栏通道** | `hooks`（`PreToolUse` 拦截、`PostToolUse` 校验） | 在单次 `-p` 运行内部，阻止危险操作、自动格式化、记录变更 |

理解这一点，就不会把"用 hook 把结果写文件"和"用 JSON 输出拿结果"搞混——前者是运行期副作用，后者才是脚本与 Claude 之间的契约。

---

## 步骤 1：用 `-p` 跑通最简单的无头调用

加 `-p`（或 `--print`）即可让 Claude Code 非交互运行：执行一次 prompt、打印结果、退出。

```bash
# 最朴素的一次性调用
claude -p "What does the auth module do?"

# 管道喂入数据，把解释写入文件
cat build-error.txt | claude -p 'concisely explain the root cause of this build error' > output.txt
```

**预期结果**：终端直接打印文本回答，进程退出，没有 REPL。

**关键坑**：默认 `-p` 会加载和交互会话**一样**的上下文——你 `~/.claude` 里的 hooks、项目 `.mcp.json` 里的 MCP、`CLAUDE.md` 全都会被读入。这在个人机器上方便，但在 CI 上是**不确定性的来源**：队友 `~/.claude` 里一个 hook 就能让你的构建结果因人而异。

## 步骤 2：用 `--bare` 锁定可复现性

```bash
# 裸模式：跳过 hooks / skills / plugins / MCP / CLAUDE.md / auto memory 的自动发现
claude --bare -p "Summarize this file" --allowedTools "Read"
```

`--bare` 让一次调用**只受你显式传入的 flag 影响**。官方明确：`--bare` 是脚本与 SDK 调用的推荐模式，未来会成为 `-p` 的默认行为。裸模式下 Claude 仍有 `Bash`、文件读、文件编辑三类工具，其余上下文通过 flag 注入：

| 需要加载的内容 | 使用的 flag |
|----------------|-------------|
| 追加系统提示 | `--append-system-prompt` / `--append-system-prompt-file` |
| 配置（含 hooks！） | `--settings <file-or-json>` |
| MCP 服务器 | `--mcp-config <file-or-json>` |
| 自定义子代理 | `--agents <json>` |
| 插件 | `--plugin-dir <path>` / `--plugin-url <url>` |

**常见问题**：`--bare` 下鉴权失败怎么办？
**解决**：`--bare` 跳过 OAuth 与钥匙串。必须用 `ANTHROPIC_API_KEY` 环境变量，或在传给 `--settings` 的 JSON 里配置 `apiKeyHelper`。Bedrock/Vertex/Foundry 用各自的 provider 凭证。

## 步骤 3：拿结构化输出——`--output-format json` + `--json-schema`

这是无人值守场景最关键的一步。`--output-format` 有三档：

- `text`（默认）：纯文本
- `json`：包含 `result`、`session_id`、用量、`total_cost_usd`（**脚本可直接追踪每次调用花费，无需查 dashboard**）
- `stream-json`：换行分隔的 JSON 流，适合实时消费

当你需要让 Claude 严格按 schema 返回时，组合 `--output-format json` 与 `--json-schema`：

```bash
claude -p "Extract the main function names from auth.py" \
  --output-format json \
  --json-schema '{"type":"object","properties":{"functions":{"type":"array","items":{"type":"string"}}},"required":["functions"]}'
```

返回的 JSON 里，结构化结果落在 `structured_output` 字段，元数据（session id、用量）在外层。用 `jq` 提取：

```bash
# 提取纯文本结果
claude -p "Summarize this project" --output-format json | jq -r '.result'

# 提取按 schema 生成的结构化结果
claude -p "Extract function names from auth.py" \
  --output-format json \
  --json-schema '{"type":"object","properties":{"functions":{"type":"array","items":{"type":"string"}}},"required":["functions"]}' \
  | jq '.structured_output'
```

**常见问题**：管道喂入的内容很大，Claude 直接报错退出？
**解决**：自 v2.1.128 起，管道 stdin 上限 10MB，超限会以非零状态码退出并报清晰错误。大输入请**写入文件并在 prompt 里引用文件路径**，而不是管道。

## 步骤 4：用 exit code + `structured_output` 做双重门禁

无人值守不能靠"看一眼输出"判断成功。建立**两层门禁**：

1. **粗门禁（进程级）**：`claude -p` 的退出码。非零通常意味着调用级错误（超 10MB、鉴权失败、`api_retry` 耗尽等）。
2. **细门禁（语义级）**：解析 `structured_output` 里的布尔字段，例如 `tests_passed: true`、`files_changed: [...]`。

```bash
set -o pipefail
RESULT=$(claude --bare -p "..." --output-format json --json-schema "$SCHEMA") || {
  echo "FAIL: claude exited non-zero" >&2; exit 1
}
TESTS_OK=$(echo "$RESULT" | jq -r '.structured_output.tests_passed')
[ "$TESTS_OK" = "true" ] || { echo "FAIL: tests did not pass"; exit 1; }
```

补充一个观测点：当 API 请求遇到可重试错误时，`stream-json` 会先吐出 `system/api_retry` 事件（含 `attempt`、`max_retries`、`retry_delay_ms`、`error_status`、`error` 类别如 `rate_limit`/`overloaded`/`billing_error`），`system/init` 事件则报告本次加载的 plugins 与 `plugin_errors`——后者可用于"插件没加载成功就让 CI 失败"。

## 步骤 5：用 `--allowedTools` / `--permission-mode` 圈定能力边界

无人值守必须提前**授权工具**，否则进程会卡在权限提示上。

```bash
# 显式列出允许的工具，逗号分隔；支持 permission rule 语法
claude -p "Look at my staged changes and create an appropriate commit" \
  --allowedTools "Bash(git diff *),Bash(git log *),Bash(git status *),Bash(git commit *)"
```

注意 `Bash(git diff *)` 末尾的 `*`：它做**前缀匹配**，且 `*` 前的**空格很重要**——写成 `Bash(git diff*)` 会连 `git diff-index` 也放行。

更彻底的做法是用 permission mode：

```bash
# acceptEdits：自动批准写文件 + mkdir/touch/mv/cp 等常见文件命令；其他 shell/网络命令仍需显式授权
claude -p "Apply the lint fixes" --permission-mode acceptEdits

# dontAsk：除了 permissions.allow 规则与只读命令集，一律拒绝——适合锁死的 CI
claude -p "..." --permission-mode dontAsk --allowedTools "Read,Bash(npm test *)"
```

**常见问题**：`acceptEdits` 模式下还是被某个 `git push` 卡住了？
**解决**：`acceptEdits` 只自动放行**写文件**和少数文件系统命令，`git push` 这类 shell 命令仍需 `--allowedTools "Bash(git push *)"` 或一条 `permissions.allow` 规则。

## 步骤 6：用 `--resume` / `--continue` 串联无头多轮

一次 `-p` 是一问一答。要在一个上下文里多轮追问，用会话续接：

```bash
# --continue：续接"最近一次"对话
claude -p "Now focus on the database queries" --continue

# --resume <id>：续接"指定"会话（同目录下查找）
session_id=$(claude -p "Start a review" --output-format json | jq -r '.session_id')
claude -p "Continue that review" --resume "$session_id"
```

会话 ID 查找**限定在当前项目目录及其 git worktree 范围内**，所以续接的两条命令必须在同一目录执行。批量场景里，给每个目标目录维护一个 `session_id`，可实现"改一版→审一版→再改一版"的无人值守迭代。

## 步骤 7：把 hooks 当成运行时护栏

`-p` 默认不加载本地 hooks，但通过 `--settings` 显式注入，护栏就会在**每一次无头调用内部**生效。这是护栏通道的核心用法：拦截危险命令、自动格式化、记录变更——全部发生在 Claude 执行工具的瞬间，无需你盯着。

护栏配置 `.claude/settings.ci.json`：

```json
{
  "hooks": {
    "PreToolUse": [
      {
        "matcher": "Bash",
        "hooks": [
          {
            "type": "command",
            "if": "Bash(git push --force *)",
            "command": "${CLAUDE_PROJECT_DIR}/.claude/hooks/deny-force-push.sh"
          }
        ]
      },
      {
        "matcher": "Edit|Write",
        "hooks": [
          {
            "type": "command",
            "if": "Edit(*.lock)|Write(package-lock.json)|Write(*lock*)",
            "command": "${CLAUDE_PROJECT_DIR}/.claude/hooks/deny-lockfile.sh"
          }
        ]
      }
    ],
    "PostToolUse": [
      {
        "matcher": "Edit|Write",
        "hooks": [
          { "type": "command", "command": "${CLAUDE_PROJECT_DIR}/.claude/hooks/format-on-save.sh" }
        ]
      }
    ]
  }
}
```

`deny-force-push.sh` 从 stdin 读 JSON、做判断、回写 `permissionDecision`：

```bash
#!/bin/bash
# .claude/hooks/deny-force-push.sh
COMMAND=$(jq -r '.tool_input.command')
if echo "$COMMAND" | grep -q 'push --force\|push -f'; then
  jq -n '{
    hookSpecificOutput: {
      hookEventName: "PreToolUse",
      permissionDecision: "deny",
      permissionDecisionReason: "Force push forbidden in unattended CI runs"
    }
  }'
else
  exit 0   # 静默 = 不表态，走正常权限流程（注意：静默≠批准）
fi
```

要点速记：
- **matcher 语法**：`*` 匹配全部；纯字母数字下划线 `|` 视为精确串（`Edit|Write`）；含其他字符视为正则（`^Notebook`、`mcp__.*`）。
- **`if` 字段**用 permission rule 语法，但**只能写一条规则**（没有 `&&`/`||`）；多条件就定义多个 handler。
- `PreToolUse` 静默退出（exit 0 且无输出）只是"不表态"，调用会继续走正常权限流程——**要阻止，必须显式 `permissionDecision: "deny"`**。
- `--bare` 跳过自动发现，但 `--settings` 显式传入的 hooks 照常生效——这正是 CI 可复现护栏的关键。

---

## 实战案例：批量把 `console.log` 迁移成结构化日志

**目标**：monorepo 下 `packages/*` 各包，把 JS/TS 源码里的 `console.log(...)` 替换为 `logger.info(...)`，每个包改完跑测试，产出机器可读报告。完全无人值守。

目录结构：

```
my-monorepo/
├── packages/
│   ├── pkg-a/
│   ├── pkg-b/
│   └── pkg-c/
└── .claude/
    ├── settings.ci.json        # 护栏（上文）
    └── hooks/
        ├── deny-force-push.sh
        ├── deny-lockfile.sh
        └── format-on-save.sh
```

**第一步**：定义结构化报告 schema（每个包一条结论）。

```bash
# schema.json
cat > schema.json <<'JSON'
{
  "type": "object",
  "properties": {
    "package": { "type": "string" },
    "files_changed": { "type": "array", "items": { "type": "string" } },
    "log_calls_replaced": { "type": "integer" },
    "tests_passed": { "type": "boolean" },
    "summary": { "type": "string" }
  },
  "required": ["package", "files_changed", "tests_passed"]
}
JSON
```

**第二步**：批量驱动脚本 `batch.sh`。

```bash
#!/bin/bash
# batch.sh — 无人值守批量改造
set -uo pipefail

SCHEMA="$(cat schema.json)"
REPORT="report.ndjson"
: > "$REPORT"   # 清空报告

PROMPT_TEMPLATE='You are migrating logging in a Node package.
Replace every console.log(...) call in this package'"'"'s JS/TS source files
with logger.info(...). Do NOT touch *.lock files. Do NOT force push.
After editing, run `npm test --silent` in this package.
Report results strictly via the schema. Package name: PKG.'

for PKG_DIR in packages/*/; do
  PKG=$(basename "$PKG_DIR")
  echo "===> Processing $PKG"

  PROMPT="${PROMPT_TEMPLATE/PKG/$PKG}"

  # 粗门禁：进程退出码
  if ! OUT=$(cd "$PKG_DIR" && claude --bare -p "$PROMPT" \
        --output-format json \
        --json-schema "$SCHEMA" \
        --settings ../.claude/settings.ci.json \
        --allowedTools "Edit,Read,Grep,Bash(npm test *),Bash(npm run *)" \
        2>err.log); then
    echo "{\"package\":\"$PKG\",\"status\":\"error\",\"detail\":\"claude non-zero exit\"}" >> "$REPORT"
    cat err.log >&2
    continue
  fi

  # 细门禁：解析 structured_output
  STRUCT=$(echo "$OUT" | jq -c '.structured_output // empty')
  if [ -z "$STRUCT" ]; then
    echo "{\"package\":\"$PKG\",\"status\":\"no_structured_output\"}" >> "$REPORT"
    continue
  fi

  TESTS_OK=$(echo "$STRUCT" | jq -r '.tests_passed')
  if [ "$TESTS_OK" = "true" ]; then
    STATUS="ok"
  else
    STATUS="tests_failed"
  fi

  # 合并状态写回报告，并保留 session_id 以便 --resume 续接
  SID=$(echo "$OUT" | jq -r '.session_id')
  echo "$STRUCT" | jq -c --arg s "$STATUS" --arg sid "$SID" \
    '. + {status:$s, session_id:$sid}' >> "$REPORT"
done

# 汇总
echo "----- SUMMARY -----"
jq -s 'group_by(.status) | map({status:.[0].status, count:length})' "$REPORT"
```

**第三步**：护栏脚本 `format-on-save.sh`（PostToolUse，每次编辑后自动格式化被改文件）。

```bash
#!/bin/bash
# .claude/hooks/format-on-save.sh
FILE=$(jq -r '.tool_input.file_path // .tool_input.path // empty')
[ -z "$FILE" ] && exit 0
case "$FILE" in
  *.ts|*.js|*.tsx|*.jsx)
    npx --no-install prettier --write "$FILE" >/dev/null 2>&1 || true
    ;;
esac
exit 0
```

记得 `chmod +x .claude/hooks/*.sh`。

**第四步**：运行与解读。

```bash
export ANTHROPIC_API_KEY=sk-ant-...
./batch.sh
```

`report.ndjson` 每行一个包的结论，例如：

```json
{"package":"pkg-a","files_changed":["src/index.ts","src/util.ts"],"log_calls_replaced":12,"tests_passed":true,"summary":"...","status":"ok","session_id":"abc123"}
{"package":"pkg-b","files_changed":[],"tests_passed":true,"status":"ok","session_id":"def456"}
{"package":"pkg-c","files_changed":["src/api.js"],"log_calls_replaced":3,"tests_passed":false,"summary":"...","status":"tests_failed","session_id":"ghi789"}
```

**第五步**：对失败的包用 `--resume` 续接修复（仍在无人值守循环里）。

```bash
SID=$(jq -r 'select(.package=="pkg-c").session_id' report.ndjson)
cd packages/pkg-c && claude --bare -p "The previous migration left tests failing. Read the test output, fix the breakage, re-run npm test." \
  --resume "$SID" \
  --settings ../.claude/settings.ci.json \
  --allowedTools "Edit,Read,Bash(npm test *)" \
  --output-format json | jq '.result'
```

**第六步**：接入 GitHub Actions。

```yaml
# .github/workflows/migrate.yml
name: Batch Logging Migration
on: { workflow_dispatch: {}, schedule: [{cron: "0 2 * * *"}] }
jobs:
  migrate:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4
        with: { fetch-depth: 0 }
      - uses: actions/setup-node@v4
        with: { node-version: '20' }
      - run: sudo apt-get update && sudo apt-get install -y jq
      - run: npm ci
      - name: Run batch migration
        env: { ANTHROPIC_API_KEY: ${{ secrets.ANTHROPIC_API_KEY }} }
        run: ./batch.sh
      - uses: actions/upload-artifact@v4
        with: { name: migration-report, path: report.ndjson }
```

---

## 整体工作流程

```mermaid
flowchart TD
    A[batch.sh 读取 packages/*] --> B{遍历每个包}
    B --> C["claude --bare -p ...<br/>--output-format json --json-schema<br/>--settings settings.ci.json<br/>--allowedTools ..."]
    C --> H{hooks 护栏<br/>运行期内生效}
    H -->|PreToolUse deny force-push/lockfile| X[阻止危险操作]
    H -->|PostToolUse 自动 prettier| Y[格式化改动]
    C --> D{exit code 粗门禁}
    D -->|非零| E[标记 error]
    D -->|零| F[jq 解析 structured_output]
    F --> G{tests_passed?}
    G -->|false| I[标记 tests_failed]
    G -->|true| J[标记 ok + session_id]
    E --> K[写入 report.ndjson]
    I --> K
    J --> K
    K --> L{还有包?}
    L -->|是| B
    L -->|否| M[jq 汇总]
    M --> N[失败的包用 --resume 续接修复]
```

单次 `-p` 调用内部的事件时序：

```mermaid
sequenceDiagram
    participant S as batch.sh
    participant C as claude -p --bare
    participant HK as hooks (settings.ci.json)
    participant API as Anthropic API
    S->>C: 启动 + prompt + schema + allowedTools
    C->>API: system/init（plugins/plugin_errors 上报）
    C->>HK: PreToolUse(Edit src/index.ts)
    HK-->>C: 静默放行（非锁文件）
    C->>API: 执行编辑/测试
    C->>HK: PostToolUse(Edit) → prettier 格式化
    C->>HK: PreToolUse(Bash git push --force) 
    HK-->>C: permissionDecision: deny
    C->>API: 收到拒绝原因，改走正常流程
    C-->>S: stdout JSON（result/structured_output/session_id/cost）
    S->>S: jq 双重门禁 → 写 report.ndjson
```

---

## 进阶要点与排错

**1. 后台任务的生命周期。** 如果 Claude 在 `-p` 运行中起了一个后台 Bash（如 dev server），它会在 Claude 返回最终结果、stdin 关闭后约 **5 秒**被终止（v2.1.163+）。后台**子代理/工作流**不受此宽限影响，但等待时长有上限：默认 10 分钟（v2.1.182+），可用 `CLAUDE_CODE_PRINT_BG_WAIT_CEILING_MS` 调整，设为 `0` 表示无限等待。批处理里若依赖长跑后台进程，务必设置这个上限，否则一个卡住的子代理会让整个批量任务挂死。

**2. `--bare` 下 skill 仍可用。** 用户可调用的 skill 和自定义命令在 `-p` 下生效：在 prompt 字符串里写 `/skill-name`，Claude Code 会在运行前展开它。但 `/login` 这类会弹出交互对话框的内置命令在 `-p` 下不可用。改设置可走 `/config thinking=false`（传 `key=value`）。

**3. `--bare` 与 `--settings` 的关系。** 这是新手最易混的点：`--bare` 跳过**自动发现**（不读 `~/.claude`、项目 `.mcp.json`、`CLAUDE.md`），但它**不拒绝**你用 flag **显式**注入的内容。所以"用 `--bare` 保可复现 + 用 `--settings` 注入受版本控制的护栏"是无冲突的组合，也是 CI 的最佳实践。

**4. cost 可观测。** `--output-format json` 的载荷含 `total_cost_usd` 与每模型成本分解。批量任务务必在循环里累加并打印，避免一次无人值守跑飞烧掉预算：

```bash
COST=$(echo "$OUT" | jq -r '.total_cost_usd // 0')
TOTAL=$(echo "$TOTAL + $COST" | bc)
echo "  cost so far: \$$TOTAL"
```

**5. 不要用 hook 强制写结果文件。** 看到上面 hooks 能在运行期执行脚本，很容易想"让 Stop hook 把改动清单写到 report"。可行但反模式——结果应走 `--output-format json` 这条**契约通道**，hooks 只做护栏与格式化。混用会让调试变成"到底是谁写错了文件"的玄学。

---

## 延伸阅读

- 官方 Headless 文档：<https://code.claude.com/docs/en/headless> —— `--bare`、`--json-schema`、`stream-json`、会话续接的权威说明
- 官方 Hooks reference：<https://code.claude.com/docs/en/hooks> —— 全部 hook 事件 schema 与 `permissionDecision` 输出格式
- Agent SDK 文档（CLI / Python / TypeScript）—— 需要比 `-p` 更细的结构化输出与工具审批回调时
- GitHub Actions / GitLab CI/CD 集成页 —— 把上面的 `migrate.yml` 扩展成 PR 门禁或定时任务

---

## 训练营适用性评估

- **适合的类型**：DevOps / 平台工程 / 重构训练营，尤其"大规模代码迁移""技术债治理"主题。也适合 AI 工程师训练营作为"把 LLM 嵌入交付流水线"的实战章节。
- **教学时长**：约 **3–4 小时**（含动手）。1 小时讲通道模型与 `-p`/JSON，1 小时搭 `batch.sh` 与 schema，1 小时写护栏 hooks 并接入 GitHub Actions，0.5–1 小时排错与 `--resume` 续接。
- **前置衔接**：建议放在本系列 hooks 篇与 subagent 篇之后——本教程复用 hooks 知识但聚焦"被脚本驱动"的视角，与既有内容互补不重复。
- **难度**：中高。难点不在某个命令，而在于"结果通道 vs 护栏通道"的架构判断，以及双重门禁（exit code + structured_output）的设计。

---

## 参考来源

1. [Run Claude Code programmatically（官方 Headless 文档）](https://code.claude.com/docs/en/headless) — `-p`、`--bare`、`--output-format json`、`--json-schema`、`--allowedTools`、`--resume`/`--continue` 的权威说明
2. [Hooks reference（官方）](https://code.claude.com/docs/en/hooks) — hook 事件、matcher/`if` 语法、`permissionDecision` 的 deny/allow 输出格式与 settings 配置
3. [Claude Code Headless Mode for Batch Tasks and CI/CD](https://restato.github.io/blog/claude-code-headless-mode/) — 无头模式用于批处理与 CI/CD 的工程实践
4. [Claude Code Headless Mode: The Complete Self-Hosting Guide (2026)](https://amux.io/guides/claude-code-headless/) — CI/CD、隔夜批量、API 驱动工作流的自托管完整指南
5. [How to Automate Claude Code with Hooks and MCP Servers](https://docs.bswen.com/blog/2026-03-19-claude-code-hooks-mcp-automation/) — 用 hooks 把 Claude Code 从被动助手变成自动化基础设施

> [!tip] 51学通信知识星球
> **星主：爱卫生**（微信：gprshome201101）——公众号「51学通信」创办者，19 年通信行业运维与讲师经验，著有《GPRS网络信令实例详解》《EPC原理与实践》《5G核心网原理与实践》三本纸质书（各大电商平台有售）。
> **星球定位**：深耕 5G 核心网、IMS、NFV/AI、云原生等 ICT 领域，主打运维岗位与信令分析。
> **当前规模**：5200 篇主题 / 990 个回答，198 元/年。
> **加入福利**：加入星球后请加爱卫生微信，拉您进知识星球年度陪伴与答疑群——不限量回复您的各种问题，另有群内分享。
> **AI 加持**：51学通信知识星球已作为 skill 接入 WorkBuddy，使用 deepseek-4-flash 在海量星球帖子中精准找贴、找视频、找 FAQ 和案例，做总结、找文档和 PPT 等场景全覆盖。

![51学通信知识星球海报-扫码加入](https://wiki.500nian.com/images/zhixingqiu_qrcode.png)
