<!-- title: 用权限模式安全跑通首个项目 -->

## 概述

本教程面向零基础开发者，用一条主线把"安装 → 理解权限 → 配置 settings.json → 跑通第一个真实项目"串成闭环。核心是一套被大多数入门教程忽略的安全护栏——**权限模式（permission mode）**。读完你会：装好 Claude Code 并验证；看懂 default / acceptEdits / plan / bypassPermissions 四种模式的差异；在用户级和项目级写出 allow / deny 规则；最后让 Claude 亲手为你造一个可运行的 Markdown 笔记统计 CLI，全程不被危险命令吓到，也不被频繁确认拖慢节奏。

## 前置条件

- **Node.js 18 或更高版本**：Claude Code 通过 npm 分发，老版本 Node 会直接报错。用 `node --version` 检查，低于 18 请先升级。
- **一个可用的终端**：macOS 用 Terminal 或 iTerm，Windows 推荐 WSL2（Git Bash 也能跑，但部分路径行为有差异），Linux 任意 shell 即可。
- **Claude 订阅**：Pro / Max / Team / Enterprise 任一即可走登录态；也可用 Anthropic API Key（按量计费）。两者二选一，新手建议先用订阅，省去额度管理心智。
- **一个练习目录**：本教程会在 `~/cc-first` 下造项目，提前建好这个空文件夹。

不需要你懂 Git、不需要你懂框架，会敲命令、会读报错即可。

---

## 正文

### 第一步：安装与验证

Claude Code 是一个 npm 全局包，一行命令装完。

```bash
npm install -g @anthropic-ai/claude-code
```

**操作说明**：`-g` 表示全局安装，装完后 `claude` 命令在任何目录都能用。

**预期结果**：终端会滚动输出下载与解包日志，最后没有红色 error 即成功。验证安装：

```bash
claude --version
```

正常会返回一个形如 `2.x.x` 的版本号（截至 2026 年中，主版本已到 2.x，例如 2.1.181 之后 `/config` 支持了 `key=value` 形式）。如果返回 `command not found`，说明 npm 的全局 bin 目录没在 `PATH` 里。

**常见问题与解决**：

1. **`EACCES: permission denied`**：npm 全局目录无写权限。**不要**用 `sudo` 绕过（会把整个全局目录变成 root 所有，后患无穷）。正确做法是把 npm 全局前缀改到用户目录：
   ```bash
   mkdir ~/.npm-global
   npm config set prefix '~/.npm-global'
   # 然后把 ~/.npm-global/bin 加入 PATH
   ```
2. **`claude --version` 报 command not found**：Windows 检查 `%APPDATA%\npm` 是否在 PATH；macOS/Linux 用 `npm config get prefix` 拿到全局 bin 路径，加进 `~/.zshrc` 或 `~/.bashrc` 的 `export PATH=`。
3. **公司网络装不上**：换淘宝镜像 `npm config set registry https://registry.npmmirror.com`，装完再换回官方源，避免后续依赖滞后。

### 第二步：登录与初始化项目目录

安装只是把程序放进系统，第一次跑还要让它"认识你"。

```bash
mkdir -p ~/cc-first && cd ~/cc-first
claude
```

**操作说明**：`claude` 不带任何参数，会进入交互式会话（REPL）。首次启动会引导你完成认证。

**预期结果**：
- 如果用订阅：终端打印一个登录 URL，浏览器打开后授权，回到终端显示已登录、当前账号、可用额度层级。
- 如果用 API Key：会提示你粘贴 Key，或让你提前设环境变量：
  ```bash
  export ANTHROPIC_API_KEY="sk-ant-xxxxx"
  ```
  （把这行写进 `~/.zshrc` / `~/.bashrc` 持久化，新开终端不用重设。）

登录成功后，底部输入框亮起，光标闪烁——你已经能和它对话了。

**常见问题与解决**：

1. **国内访问登录 URL 超时**：Claude Code 的认证与推理都走 Anthropic 域名，需要稳定的网络出口。配好代理后，设置环境变量让 CLI 走代理：
   ```bash
   export HTTPS_PROXY=http://127.0.0.1:7890
   ```
2. **不知道当前账号和额度**：在会话里输入 `/status`，会显示登录身份、套餐层级、剩余额度。新手一定先 `/status` 一次，免得跑批任务时额度见底。
3. **想换账号**：`/logout` 退出，再次 `claude` 重新走认证。

### 第三步：看懂权限模式——本教程的核心

很多人装完就开问，结果每改一个文件、每跑一条命令都被打断确认，体验很糟；或者图省事一键放行所有操作，又不小心让模型 `rm -rf` 了关键目录。**权限模式就是这中间的调节阀**。

Claude Code 在编辑文件、执行 shell 命令、发起网络请求前，会"暂停并询问"。询问的频率由当前模式决定。CLI 里按 **Shift+Tab** 在三种交互模式间循环：`default → acceptEdits → plan`，当前模式显示在输入框附近。

四种模式对照：

| 模式 | 行为 | 适合场景 |
|------|------|----------|
| **default** | 每个敏感操作（写文件、跑命令、联网）都暂停问一次 | 陌生项目、首次审查、敏感生产环境 |
| **acceptEdits** | 文件编辑自动批准，但跑命令、联网仍要问 | 已信任的代码改动，想少打断又怕乱跑命令 |
| **plan** | 只规划不执行——只读分析、给方案，不动文件不跑命令 | 需求不清时先让它出方案，避免误改 |
| **bypassPermissions** | 全部自动批准，不再问 | 沙盒、一次性任务、CI（**慎用**） |

**关键认知**：`bypassPermissions` 不在 Shift+Tab 循环里，因为它风险高，要你显式指定，例如：
```bash
claude --permission-mode bypassPermissions
```
新手**强烈建议全程留在 default**，等熟悉了再视任务切换。

**操作说明**：按一次 Shift+Tab，观察输入框上方提示从 `default` 变成 `acceptEdits`；再按一次变 `plan`；第三次回到 `default`。

**预期结果**：模式字样实时更新，光标回到输入框。你可以立刻问一句"当前处于什么权限模式？"它会如实回答，验证你对提示的理解。

**常见问题与解决**：

1. **忘了当前模式**：看输入框上方文字；或直接问它。`plan` 模式下它若说"我只规划不执行"，就是对的。
2. **`acceptEdits` 还是频繁弹窗**：因为 acceptEdits 只自动批准"文件编辑"，跑 `npm test` 这类命令依旧问。这是设计如此，不是 bug。要连命令也自动批，得靠下一步的 allow 规则。
3. **CI 里默认是哪种**：非交互环境默认不弹窗，未在 allow 列表的命令会按规则处理或失败。CI 通常配 `bypassPermissions` + 锁死的沙盒镜像。

### 第四步：用 settings.json 写权限规则

模式是"总闸"，规则是"细闸"。你可以在不切换模式的前提下，把特定命令永久加入白名单，省掉重复确认。配置入口是 `/config` 命令——交互式选项卡，能看状态、改选项。从 v2.1.181 起，还能一行改单值，例如：

```
/config verbose=true
```

但更可控的是直接编辑配置文件。Claude Code 的配置按**作用域**分层，后者覆盖前者：

```mermaid
flowchart LR
    A["企业/托管策略<br/>managed settings"] --> B["用户级<br/>~/.claude/settings.json"]
    B --> C["项目级<br/>.claude/settings.json<br/>随仓库提交共享"]
    C --> D["本地级<br/>.claude/settings.local.json<br/>个人覆盖，不入库"]
    D --> E["生效配置<br/>优先级最高"]
    style A fill:#fde2e2
    style E fill:#d6e8ff
```

- **用户级** `~/.claude/settings.json`：你所有项目通用的偏好（主题、默认模型、全局 allow/deny）。
- **项目级** `<项目>/.claude/settings.json`：**会进 Git**，团队共享。例如"本项目允许 `npm test`、禁止 `rm -rf`"。
- **本地级** `<项目>/.claude/settings.local.json`：**默认被 .gitignore 忽略**，只影响你自己。临时放宽权限放这里，别污染团队配置。

权限规则用 `allow` / `deny` / `ask` 三个键，规则语法是 `工具(参数模式)`：

```json
{
  "permissions": {
    "allow": [
      "Bash(npm test)",
      "Bash(npm run lint)",
      "Read(./**)",
      "Edit(./src/**)"
    ],
    "deny": [
      "Bash(rm -rf:*)",
      "Bash(curl:*)",
      "Read(./.env)",
      "Edit(./.env)"
    ],
    "ask": [
      "Bash(git push:*)"
    ]
  }
}
```

**语义要点**：
- `allow`：命中即自动放行，不再问。
- `deny`：命中即**拒绝**，连问都不问。`deny` 永远优先于 `allow`，是最后一道安全锁。
- 未被任何规则命中的操作，按当前模式处理（default 会问）。
- `Bash(npm test)` 精确匹配命令；`Bash(npm:*)` 匹配 `npm` 开头的一切；`Bash(rm -rf:*)` 匹配 `rm -rf` 加任意参数。

**操作说明**：在项目根目录手动建 `.claude/settings.local.json`（或让 Claude 帮你建——但它建文件也算操作，default 模式会问你一次）。

**预期结果**：保存后回到会话，让它跑 `npm test`，如果命中 allow，会直接执行不再弹窗；让它读 `.env`，会被拒。

**常见问题与解决**：

1. **改了 settings.json 没生效**：部分选项需重启会话。最稳的办法是退出当前 REPL（`Ctrl+C` 两次或 `/exit`），重新 `claude`。
2. **规则不匹配**：规则按**完整命令字符串前缀**匹配。你写 `Bash(git push)`，但模型执行的是 `git push origin main`，可能不完全匹配——用通配 `Bash(git push:*)` 更稳妥。`:` 是工具与参数的分隔符。
3. **团队误提交了本地配置**：项目级文件该入库共享，但本地级绝不能入库。务必确认 `.claude/settings.local.json` 在 `.gitignore` 里。
4. **想全局禁掉某个危险命令**：放用户级 `~/.claude/settings.json` 的 `deny`，所有项目生效。

### 第五步：发起第一个真实任务

配置就绪，现在让它干活。在 default 模式下，把需求交给它：

```
在当前目录用 Node.js 造一个 Markdown 笔记统计 CLI，叫 mdcount。
功能：扫描指定目录下所有 .md 文件，统计总文件数、总行数、总字数、各级标题数量，
最后按字数降序输出 Top 5 文件。支持 --dir 参数指定目录，默认当前目录。
```

它会先规划（你可以在这一步切到 plan 模式只看方案），确认后开始建文件。default 模式下每个写文件、跑命令的步骤都会弹窗，你逐个批准即可；嫌烦就把对应的 Bash/Edit 加进 allow，体验立刻顺滑。

---

## 实战案例：mdcount 笔记统计器

下面是 Claude 会为你生成的完整可运行项目。即使你不用 Claude，把这几个文件手动建好，`node index.js --dir .` 也能直接跑。

**项目结构**：
```
cc-first/
├── index.js          # 入口与参数解析
├── counter.js        # 统计核心逻辑
├── formatter.js      # 结果格式化输出
└── package.json      # 声明 bin，支持全局调用
```

**counter.js —— 统计核心**：
```javascript
const fs = require('fs');
const path = require('path');

// 递归收集目录下所有 .md 文件
function collectMarkdown(dir) {
  let results = [];
  for (const entry of fs.readdirSync(dir, { withFileTypes: true })) {
    const full = path.join(dir, entry.name);
    // 跳过 node_modules 等噪声目录
    if (entry.isDirectory() && entry.name === 'node_modules') continue;
    if (entry.isDirectory()) {
      results = results.concat(collectMarkdown(full));
    } else if (entry.name.endsWith('.md')) {
      results.push(full);
    }
  }
  return results;
}

// 统计单个文件
function statFile(file) {
  const text = fs.readFileSync(file, 'utf8');
  const lines = text.split('\n');
  const words = text.split(/\s+/).filter(Boolean).length;
  // 匹配 Markdown 标题 # ~ ######
  const headings = {};
  for (const line of lines) {
    const m = line.match(/^(#{1,6})\s/);
    if (m) headings[m[1].length] = (headings[m[1].length] || 0) + 1;
  }
  return { file, lines: lines.length, words, headings };
}

module.exports = { collectMarkdown, statFile };
```

**index.js —— 入口**：
```javascript
#!/usr/bin/env node
const { collectMarkdown, statFile } = require('./counter');
const { formatReport } = require('./formatter');

// 简单解析 --dir 参数，默认当前目录
const args = process.argv.slice(2);
let dir = '.';
for (let i = 0; i < args.length; i++) {
  if (args[i] === '--dir' && args[i + 1]) dir = args[i + 1];
}

const files = collectMarkdown(dir);
if (files.length === 0) {
  console.log(`目录 ${dir} 下没有 .md 文件`);
  process.exit(0);
}

const stats = files.map(statFile);
console.log(formatReport(stats));
```

**formatter.js —— 输出**：
```javascript
function formatReport(stats) {
  const totalLines = stats.reduce((s, f) => s + f.lines, 0);
  const totalWords = stats.reduce((s, f) => s + f.words, 0);
  const headingTotals = {};
  stats.forEach(f => {
    Object.entries(f.headings).forEach(([level, n]) => {
      headingTotals[level] = (headingTotals[level] || 0) + n;
    });
  });

  const top5 = [...stats].sort((a, b) => b.words - a.words).slice(0, 5);

  let out = `📁 共扫描 ${stats.length} 个 Markdown 文件\n`;
  out += `📝 总行数：${totalLines}　总字数：${totalWords}\n`;
  out += `🏷  标题：` +
    Object.keys(headingTotals).sort()
      .map(l => `H${l}=${headingTotals[l]}`).join(' ') + '\n';
  out += `\n🏆 字数 Top 5：\n`;
  top5.forEach((f, i) => {
    out += `  ${i + 1}. ${f.file} — ${f.words} 字 / ${f.lines} 行\n`;
  });
  return out;
}

module.exports = { formatReport };
```

**运行方式**：
```bash
node index.js --dir .
```

**预期输出**（在你的笔记目录里跑）：
```
📁 共扫描 42 个 Markdown 文件
📝 总行数：3180　总字数：21456
🏷  标题：H1=38 H2=156 H3=203

🏆 字数 Top 5：
  1. ./notes/architecture.md — 1842 字 / 312 行
  2. ./notes/roadmap.md — 1205 字 / 198 行
  ...
```

**让它更有趣的延伸指令**（直接在 Claude 会话里接着提）：
- "把 mdcount 包成可执行命令，加到 package.json 的 bin，支持 `npx mdcount`。"
- "加 `--watch` 参数，文件变化时自动重算。"
- "把 Top 5 结果输出成 Markdown 表格写进 REPORT.md。"

每一条都顺带验证你的权限规则——加 bin 要改 package.json（Edit）、要 `npm link` 测试（Bash）、要写 REPORT.md（Write）。default 模式会逐个确认，确认烦了就把规则写进 `settings.local.json`。

---

## 整体工作流程图

下面这张图把"从一条需求到安全落地"的全过程，以及权限系统在每个节点的介入点画在一起：

```mermaid
flowchart TD
    Start(["用户提出需求"]) --> Mode{"当前权限模式?"}
    Mode -->|default| AskEach["每个敏感操作<br/>都暂停询问"]
    Mode -->|acceptEdits| AutoEdit["文件编辑自动批<br/>命令/联网仍问"]
    Mode -->|plan| ReadOnly["只读分析<br/>不动文件不跑命令"]
    Mode -->|bypass| AutoAll["全部自动批准<br/>仅限沙盒"]

    AskEach --> Rule{"命中规则?"}
    AutoEdit --> Rule
    AutoAll --> Exec

    Rule -->|在 deny| Block["❌ 直接拒绝<br/>deny 优先级最高"]
    Rule -->|在 allow| Exec["✅ 自动放行"]
    Rule -->|未命中| Prompt["弹窗让用户决定"]
    Prompt -->|批准| Exec
    Prompt -->|拒绝| Block

    Exec --> Result(["产生改动/输出"])
    Block --> End(["任务终止或换方案"])
    ReadOnly --> Plan(["输出方案<br/>用户确认后再切 default 执行"])

    style Block fill:#fde2e2
    style Exec fill:#d6eedd
    style Plan fill:#fff4cc
```

读懂这张图，你就理解了 Claude Code 的安全模型：**模式管总闸，规则管细闸，deny 是终审**。

---

## 延伸阅读

- **Claude Code 官方快速开始**：`https://code.claude.com/docs/zh-CN/quickstart` —— 权威的安装与首跑流程，本教程的事实基线。
- **权限模式官方文档**：`https://code.claude.com/docs/en/permission-modes` —— 四种模式的官方语义，建议精读。
- **Claude Code 设置官方文档**：`https://code.claude.com/docs/zh-CN/settings` —— `/config`、作用域、环境变量的完整说明。
- **SimilarLabs 2026 完全教程**：`https://similarlabs.com/zh/blog/claude-code-tutorial-guide` —— 从安装到多代理的中文长文，适合作为进阶地图。
- **Solosoft 2026 完整指南**：`https://www.solosoft.dev/zh-cn/post/claude-code-complete-guide-2026/` —— 重点讲四大权限模式与工作流，与本文互补。

---

## 训练营适用性评估

- **适合的训练营类型**：面向零基础到初级开发者的"AI 编程入门"营、企业内部"AI 工具上手"培训、高校"AI 辅助开发"选修课第一课。不适合已熟练使用 Claude Code 的进阶营。
- **预计教学时长**：**2～2.5 小时**。其中安装登录约 30 分钟（含网络排障），权限模式讲解约 40 分钟，settings.json 配置动手约 30 分钟，mdcount 实战与延伸约 50 分钟。
- **教学节奏建议**：把"权限模式"作为贯穿全程的主线，而不是一节独立理论课——每完成一个实操，回头问一句"刚才那步被拦了吗？为什么？"，让规则从经验中长出来，比死记表格有效得多。
- **考核点**：学员能说出四种模式差异、能独立写出一条 allow + 一条 deny 规则、能跑通 mdcount 并解释每一步是否被权限拦截。
- **延伸方向**：本课结束可直接衔接"计划模式做需求拆解""hooks 造护栏""-p 批处理"等进阶主题，构成一条从安全入门到自动化的完整路径。

## 参考来源

1. [Claude Code 官方快速开始（中文）](https://code.claude.com/docs/zh-CN/quickstart) — 安装、订阅前置条件与首跑流程的权威基线
2. [Choose a permission mode — Claude Code Docs](https://code.claude.com/docs/en/permission-modes) — default / acceptEdits / plan / bypassPermissions 四种模式的官方语义，含 Shift+Tab 循环说明
3. [Claude Code 设置官方文档（中文）](https://code.claude.com/docs/zh-CN/settings) — `/config` 命令、配置作用域（用户/项目/本地）与环境变量配置
4. [2026 年 Claude Code 完全使用教程 — SimilarLabs](https://similarlabs.com/zh/blog/claude-code-tutorial-guide) — 从安装配置到核心命令、工作流的中文长文教程
5. [Claude Code 2026 完整使用指南 — Solosoft](https://www.solosoft.dev/zh-cn/post/claude-code-complete-guide-2026/) — 重点覆盖四大权限模式与企业级工作流，与本文权限主线互补

> [!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)
