本教程面向从未用过 Claude Code 的开发者,目标是 30 分钟内完成一条完整闭环:安装 → 认证 → 写第一份 CLAUDE.md → 配置 settings.json 权限 → 跑通第一个真实项目。我们不会停留在"敲一个 hello world",而是带你亲手造一个命令行 Markdown 转静态站点生成器(mdsite)——它能读取目录、批量转换、生成带样式的 HTML,足以让你体会 Claude Code 读文件、写文件、跑命令、按约定工作的全部能力。适合学生、转岗工程师、想从"复制粘贴聊天框"切换到"终端里跑 Agent"的任何人。
只需三样东西:
Node.js 18 或更高版本。Claude Code 以 npm 包形式分发,靠 Node 运行。检查版本:
bash
node --version # 期望 v18.0.0 以上
若未安装,用 nvm(nvm install --lts)或直接到 nodejs.org 下载安装包。
一个 Anthropic 账号。三种身份任选其一:
- API 按量付费:在 console.anthropic.com 的 Settings → API Keys 生成密钥,按 token 计费(典型 30–60 分钟会话约 \$0.50–\$3.00)。
- Max 订阅:个人 \$100/月、团队 \$200/月(截至 2026 年 3 月),内含 Claude Code 用量与更高限额。
- 企业 SSO:由 IT 统一发放。
任意终端。Terminal.app、iTerm2、Windows Terminal、Alacritty 或 VS Code 内置终端皆可。建议宽度 ≥ 120 列,因为 Claude Code 会展示文件 diff 与工具输出,横向空间越大越舒服。
⚠️ Windows 用户:可在 WSL(Windows Subsystem for Linux) 中获得最稳定的体验;原生 Windows 也已支持,但路径与脚本兼容性以 WSL 更佳。
最通用的方式是全局安装 npm 包:
npm install -g @anthropic-ai/claude-code
安装后验证:
claude --version
预期结果:终端打印一串版本号(如 2.x.x),即安装成功。
常见问题:command not found
表示 npm 的全局 bin 目录不在 PATH 里。查一下前缀:
npm config get prefix
把返回值下的 /bin 子目录加进 shell 配置(~/.zshrc 或 ~/.bashrc):
export PATH="$(npm config get prefix)/bin:$PATH"
随后 source ~/.zshrc 生效。
常见问题:EACCES 权限错误
安装时报 EACCES,不要用 sudo。正确做法是让 npm 用一个用户可写目录:
mkdir -p ~/.npm-global
npm config set prefix '~/.npm-global'
# 再写入 shell 配置:
export PATH="$HOME/.npm-global/bin:$PATH"
重新执行 npm install -g 即可。
📌 除 npm 外,Anthropic 也提供平台原生安装器(macOS/Linux 的 shell 脚本、Windows 安装包),适合不想装 Node 的场景。本教程以 npm 为主,因为它跨平台、易升级(
npm update -g @anthropic-ai/claude-code)。
Claude Code 的凭证只存在你本机(~/.claude/ 下),用来认证 API 请求,不会外传。两条认证路径:
路径 A:浏览器 OAuth(推荐新手)
在终端直接运行 claude,首次启动会自动打开浏览器跳转到 Anthropic 控制台,你登录并授权后,token 即存入本地。
路径 B:环境变量 API Key(适合 CI/脚本)
export ANTHROPIC_API_KEY="sk-ant-xxxxxxxx"
把上面这行写进 ~/.zshrc,注意别提交到 git。
验证认证:运行 claude 进入交互界面后,输入一句简单问候,若收到回复,说明认证已通。
新建一个项目目录并进入:
mkdir -p ~/Projects/mdsite && cd ~/Projects/mdsite
claude
Claude Code 启动后是一个交互式 REPL。新项目里它会自动做三件事:
package.json、pyproject.toml、Cargo.toml 等配置识别技术栈;CLAUDE.md 获取项目专属指令。试一句确认一切就绪:
> 解释一下当前这个目录的结构
你会看到两样关键东西:
💡 黄金法则:先观察、再放行。前几轮把每次审批都看一遍,你能快速建立"哪些操作安全、哪些值得警惕"的直觉。
CLAUDE.md 是整个体系里杠杆最高的一个文件。没有它,Claude 只能靠读代码猜你的约定,而"猜"会漂移——可能在一个 ESM 项目里写 require(),或用错测试运行器。有了它,从第一个 prompt 起就严格遵循你的规范。
Claude Code 采用三级层次,从高到低合并(高优先级覆盖低优先级):
| 层级 | 位置 | 作用域 | 是否提交 git |
|---|---|---|---|
| 个人项目级 | CLAUDE.local.md |
仅你自己 | 否(gitignore) |
| 团队项目级 | ./CLAUDE.md |
整个团队 | 是 |
| 用户全局级 | ~/.claude/CLAUDE.md |
你所有项目 | 否 |
给本项目新建 CLAUDE.md:
touch CLAUDE.md
填入一个精简实用的模板(务必控制在 200 行内,否则指令遵循率会下降):
# mdsite —— Markdown 静态站点生成器
## 技术栈
- 运行时:Node.js 20
- 语言:纯 JavaScript(CommonJS),无外部依赖
- 测试:Node 内置 node:test
## 命令
- `node bin/mdsite.js build ./content ./dist` 构建站点
- `node --test` 运行测试
- `node bin/mdsite.js serve ./dist 8080` 本地预览
## 约定
- 所有入口逻辑放在 bin/mdsite.js,核心函数放在 lib/
- 输入输出统一用 utf-8
- 转换函数必须是纯函数,便于单测
- HTML 模板统一从 lib/template.js 取
## 重要
- 不要引入任何 npm 依赖,保持零依赖
- 每个导出函数都要有对应的 node:test 测试
预期结果:下次会话,Claude 会立刻知道用 CommonJS、用 node:test、保持零依赖,无需你反复提醒。
权限是 Claude Code 的安全核心。它有三层配置文件,更具体的覆盖更宽泛的:
| 文件 | 作用 | 共享 |
|---|---|---|
~/.claude/settings.json |
你的全局默认 | 否 |
.claude/settings.json |
团队项目配置 | 是(git) |
.claude/settings.local.json |
你的本地覆盖 | 否(gitignore) |
权限规则有三档语义:deny(永远禁止)、ask(每次询问)、allow(自动放行)。评估顺序是 deny → ask → allow。
给本项目新建 .claude/settings.json:
mkdir -p .claude && touch .claude/settings.json
写入一个新手友好的权限配置:
{
"$schema": "https://json.schemastore.org/claude-code-settings.json",
"permissions": {
"allow": [
"Read",
"Glob",
"Grep",
"Bash(node --test)",
"Bash(node --test*)",
"Bash(node bin/mdsite.js *)",
"Edit(./lib/**)",
"Write(./lib/**)"
],
"ask": [
"Bash(npm *)"
],
"deny": [
"Bash(rm -rf *)",
"Bash(curl *)",
"Read(.env)",
"Read(.env.*)"
],
"defaultMode": "default"
}
}
逐行解读:
Read/Glob/Grep:只读类工具直接放行,不打扰你;Bash(node --test*)、Bash(node bin/mdsite.js *):项目自己的测试与构建命令免确认;Edit(./lib/**)、Write(./lib/**):限定在 lib/ 目录下自动读写——既省心又有边界;ask 里的 Bash(npm *):涉及依赖变更要你点头;deny 拦死 rm -rf、外网 curl、读取 .env 密钥;defaultMode: default:除上述规则外,其余操作都先问。关键提醒:永远不要写 Bash(*)。这条通配放行会让 Claude 无确认执行任意 shell 命令,等于拆掉整个权限系统的护栏。
💡 权限模式四选一:
default(默认询问)、acceptEdits(自动接受文件编辑)、dontAsk(自动拒绝,已放行的仍可跑)、bypassPermissions(全部跳过,仅限无人值守的 CI,本地切勿使用)。
mdsite现在让 Claude 动手。在交互界面输入(一次性把需求讲清,比挤牙膏式追问高效得多):
请按 CLAUDE.md 的约定,帮我实现 mdsite:
1. lib/markdown.js 导出两个纯函数:escapeHtml(str) 和 mdToHtml(md)
2. mdToHtml 至少支持:标题(#~######)、无序列表(- )、有序列表(1. )、段落、粗体(**x**)、代码块(反引号围栏)
3. lib/template.js 导出 wrapHtml(title, body) 套上基础 HTML 模板,带简单内联样式
4. lib/build.js 导出 buildSite(srcDir, outDir):扫描 srcDir 下所有 .md,转成同名 .html 写入 outDir
5. bin/mdsite.js 作为 CLI 入口,解析 process.argv,支持 build <src> <out> 子命令
6. 给每个纯函数写 node:test 测试放 test/*.test.js
最后运行 node --test 全部通过,并执行 node bin/mdsite.js build ./content ./dist 跑一次构建。
预期结果:Claude 会读 CLAUDE.md、规划结构、逐个创建文件,每次写文件前(按你的 settings.json)在 lib/ 内自动放行、其它位置征求确认。最终跑通测试并生成 ./dist。
下面给出核心实现,你可以直接对照 Claude 的产物:
lib/markdown.js:
// 纯函数:HTML 转义,防注入
function escapeHtml(str) {
return String(str)
.replace(/&/g, '&')
.replace(/</g, '<')
.replace(/>/g, '>');
}
// 纯函数:极简 Markdown → HTML
function mdToHtml(md) {
const lines = md.split('\n');
const out = [];
let inUL = false, inOL = false, inCode = false;
const closeLists = () => {
if (inUL) { out.push('</ul>'); inUL = false; }
if (inOL) { out.push('</ol>'); inOL = false; }
};
for (const raw of lines) {
const line = raw;
if (line.startsWith('```')) { // 代码块围栏
if (inCode) { out.push('</code></pre>'); inCode = false; }
else { closeLists(); out.push('<pre><code>'); inCode = true; }
continue;
}
if (inCode) { out.push(escapeHtml(line)); continue; }
if (/^#{1,6}\s/.test(line)) { // 标题
const level = line.match(/^#+/)[0].length;
closeLists();
out.push(`<h${level}>${inline(line.replace(/^#+\s/, ''))}</h${level}>`);
} else if (/^-\s/.test(line)) { // 无序列表
if (!inUL) { closeLists(); out.push('<ul>'); inUL = true; }
out.push(`<li>${inline(line.replace(/^-\s/, ''))}</li>`);
} else if (/^\d+\.\s/.test(line)) { // 有序列表
if (!inOL) { closeLists(); out.push('<ol>'); inOL = true; }
out.push(`<li>${inline(line.replace(/^\d+\.\s/, ''))}</li>`);
} else if (line.trim() === '') { // 空行
closeLists();
} else { // 段落
closeLists();
out.push(`<p>${inline(line)}</p>`);
}
}
closeLists();
if (inCode) out.push('</code></pre>');
return out.join('\n');
function inline(s) { // 行内:粗体 + 转义
return escapeHtml(s).replace(/\*\*(.+?)\*\*/g, '<strong>$1</strong>');
}
}
module.exports = { escapeHtml, mdToHtml };
lib/template.js:
function wrapHtml(title, body) {
return `<!DOCTYPE html>
<html lang="zh">
<head><meta charset="utf-8"><title>${title}</title>
<style>body{font-family:system-ui,sans-serif;max-width:720px;margin:2rem auto;padding:0 1rem;line-height:1.7}
pre{background:#f4f4f4;padding:1rem;overflow:auto}code{background:#eee;padding:.1rem .3rem}</style>
</head><body><h1>${title}</h1>${body}</body></html>`;
}
module.exports = { wrapHtml };
lib/build.js:
const fs = require('fs');
const path = require('path');
const { mdToHtml } = require('./markdown');
const { wrapHtml } = require('./template');
function buildSite(srcDir, outDir) {
if (!fs.existsSync(outDir)) fs.mkdirSync(outDir, { recursive: true });
const files = fs.readdirSync(srcDir).filter(f => f.endsWith('.md'));
const built = [];
for (const file of files) {
const md = fs.readFileSync(path.join(srcDir, file), 'utf-8');
const base = path.basename(file, '.md');
const html = wrapHtml(base, mdToHtml(md));
fs.writeFileSync(path.join(outDir, base + '.html'), html, 'utf-8');
built.push(base + '.html');
}
return built;
}
module.exports = { buildSite };
bin/mdsite.js:
#!/usr/bin/env node
const { buildSite } = require('../lib/build');
const [cmd, src, out] = process.argv.slice(2);
if (cmd === 'build' && src && out) {
const built = buildSite(src, out);
console.log(`已构建 ${built.length} 个页面 → ${out}/`);
} else {
console.log('用法: mdsite build <srcDir> <outDir>');
process.exit(1);
}
test/markdown.test.js:
const test = require('node:test');
const assert = require('node:assert');
const { escapeHtml, mdToHtml } = require('../lib/markdown');
test('escapeHtml 转义尖括号', () => {
assert.strictEqual(escapeHtml('<b>'), '<b>');
});
test('标题转换', () => {
assert.strictEqual(mdToHtml('# 标题'), '<h1>标题</h1>');
});
test('粗体与列表', () => {
assert.ok(mdToHtml('**重点**').includes('<strong>重点</strong>'));
assert.ok(mdToHtml('- 项').includes('<ul>'));
});
跑一次完整闭环:
mkdir -p content
echo -e "# 你好\n\n这是一篇 **测试** 文章。\n\n- 列表项一\n- 列表项二" > content/hello.md
node --test # 测试全绿
node bin/mdsite.js build ./content ./dist
cat dist/hello.html # 看到带样式的完整 HTML
🎉 至此你已完成:安装、认证、CLAUDE.md 约定、settings.json 权限、一个能跑的真实项目。
mdsite 虽小,却串起了 Claude Code 的核心工作面:读目录(Glob/Read)→ 理解约定(CLAUDE.md)→ 写代码(Edit/Write 受 settings.json 约束)→ 跑测试与构建(Bash 受 allow 规则约束)→ 产出文件。当你想新增"支持表格语法"或"加 serve 预览命令",只需在交互界面提需求,Claude 会顺着既有约定扩展——这正是 CLAUDE.md + settings.json 组合的复利效应。
下面这张图展示了从安装到首跑的完整闭环,以及权限决策如何贯穿其中:
flowchart TD
A[安装: npm i -g @anthropic-ai/claude-code] --> B[认证: OAuth 或 API Key]
B --> C[cd 进项目目录, 运行 claude]
C --> D[自动扫描结构 + 读 CLAUDE.md]
D --> E{你发出 prompt}
E --> F[Agent 规划工具调用]
F --> G{权限决策}
G -->|命中 deny| H[直接拒绝]
G -->|命中 allow| I[自动执行]
G -->|命中 ask / 默认| J[弹窗征求你同意]
J -->|同意| I
J -->|拒绝| K[放弃该操作]
I --> L[读文件 / 写代码 / 跑命令]
L --> M{PostToolUse Hook?}
M -->|有| N[自动格式化 / lint]
N --> O[产出结果]
M -->|无| O
O --> P[你审阅 git diff]
P -->|满意| Q[git commit]
P -->|不满意| R[git checkout 回滚]
style G fill:#fff3cd,stroke:#856404
style Q fill:#d4edda,stroke:#155724
style H fill:#f8d7da,stroke:#721c24
权限三层配置如何合并(更具体覆盖更宽泛):
flowchart LR
M[Managed Policy<br/>IT 部署, 不可覆盖] --> U
U[~/.claude/settings.json<br/>你的全局默认] --> P
P[.claude/settings.json<br/>团队项目配置] --> L
L[.claude/settings.local.json<br/>本地个人覆盖] --> R[最终生效配置<br/>allow/ask/deny 合并]
style M fill:#e2e3e5,stroke:#383d41
style L fill:#fff3cd,stroke:#856404
style R fill:#d4edda,stroke:#155724
| 现象 | 原因 | 解决 |
|---|---|---|
command not found: claude |
npm bin 不在 PATH | 把 npm config get prefix 的 /bin 加入 PATH |
安装报 EACCES |
全局目录无写权限 | 设 npm prefix 到用户目录,勿用 sudo |
| Claude 不认 CLAUDE.md | 已缓存旧内容 | /clear 或重启会话;会话开始时才重读 |
| 误改了不该改的文件 | allow 规则过宽 | 收窄到具体命令/路径;git checkout -- <file> 回滚 |
| 长会话上下文爆炸 | 200K 窗口被压缩 | 每完成一个子任务就 commit,再 /clear 开新会话 |
| JSON 配置不生效 | 语法错误致整文件失效 | 用 JSON 校验器检查逗号/括号 |
mdsite 并扩展一个新功能。.claude 目录全结构详解:CLAUDE.md 三级层次、rules、commands、skills、agents、settings.json、hooks、MCP、memory 的实战示例。[!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 等场景全覆盖。
