import type { Locale } from '@/lib/i18n';

/**
 * Blog post data.
 *
 * Each post ships an `en` and `zh` body. We keep the bodies inline as template
 * literals — no extra MDX/markdown loader, no extra build step. `react-markdown`
 * (already a dependency) renders them at request time / static-export time.
 */

export interface PostBody {
  title: string;
  description: string;
  body: string;
  /** Optional plain-text reading-time hint, e.g. "8 min read". */
  readingTime?: string;
}

export interface Post {
  slug: string;
  /** ISO date — used for sitemap lastModified and visible publish date. */
  date: string;
  /** SEO keywords specific to this post. */
  keywords: string[];
  /** Per-locale content. */
  content: Record<Locale, PostBody>;
}

// ---------------------------------------------------------------------------
// Posts (newest first)
// ---------------------------------------------------------------------------

export const posts: Post[] = [
  {
    slug: 'cockpit-on-your-phone',
    date: '2026-06-23',
    keywords: [
      'mobile AI agent',
      'monitor coding agent on phone',
      'web push notifications',
      'Claude Code GUI mobile',
      'agent monitoring',
      'mobile layout',
      '移动端',
      '手机监控 agent',
      '推送通知',
      'Claude Code GUI',
      'OpenCockpit',
      'Cockpit',
    ],
    content: {
      en: {
        title: 'Cockpit on your phone',
        description:
          'Cockpit now has a mobile layout. Open it on your phone to see your sessions, open any chat, and receive a notification when a run finishes.',
        readingTime: '2 min read',
        body: `Agents often keep running after you've left your desk. Until now, checking on them meant coming back to a computer. This release adds a mobile layout and notifications, so you can check on your sessions from your phone.

## A mobile layout

When you open Cockpit on a phone, it shows a single-column layout instead of the desktop's three panels. On a tablet, you can switch to the desktop layout if you prefer it.

## Your sessions

The first screen lists your recent sessions and updates as their status changes. A **Frequent** tab keeps the sessions you use often in one place. Tap a session to open its chat, with full history and live streaming, and send messages from a touch input.

| On mobile | What it does |
|---|---|
| Session list | Shows recent sessions and their current status |
| Chat | Full history, live streaming, touch input |
| Diffs & previews | Open fullscreen, with a collapsible file tree |

## Notifications

Turn on notifications and Cockpit sends one to your phone when a run finishes, including when the app is closed. It doesn't require any extra accounts or setup.

## Try it

Update Cockpit and open it on your phone. Turn on notifications and start a run.

---

**Try it:** \`npm i -g @surething/cockpit\` · [GitHub](https://github.com/Surething-io/cockpit) · [Try Online](/try)`,
      },
      zh: {
        title: '在手机上用 Cockpit',
        description:
          'Cockpit 新增了移动端布局。在手机上打开,可以查看会话、打开任意聊天,并在一轮跑完时收到通知。',
        readingTime: '阅读约 2 分钟',
        body: `Agent 经常在你离开桌前之后还在跑。在此之前,查看它们得回到电脑前。这个版本新增了移动端布局和通知,你可以在手机上查看自己的会话。

## 移动端布局

在手机上打开 Cockpit 时,显示的是单栏布局,而不是桌面的三栏。在平板上,如果你更习惯桌面布局,可以切换过去。

## 你的会话

第一屏列出你的最近会话,会随状态变化更新。一个 **常用会话** 标签,把你常用的会话集中在一处。点一个会话就打开它的聊天,包含完整历史和实时流式,可以用触屏输入框发消息。

| 移动端 | 作用 |
|---|---|
| 会话列表 | 显示最近会话及其当前状态 |
| 聊天 | 完整历史、实时流式、触屏输入 |
| Diff 与预览 | 全屏打开,文件树可折叠 |

## 通知

打开通知后,一轮跑完 Cockpit 会发一条到你手机上,app 关闭时也会发。不需要额外账号或配置。

## 试一下

更新 Cockpit,在手机上打开。打开通知,发起一轮任务。

---

**试一下:** \`npm i -g @surething/cockpit\` · [GitHub](https://github.com/Surething-io/cockpit) · [在线体验](/try)`,
      },
    },
  },
  {
    slug: 'orchestrate-workflows-in-chat',
    date: '2026-06-18',
    keywords: [
      'AI workflow orchestration',
      'multi-step agent workflow',
      'slash command chaining',
      'subagent delegation',
      'Claude sub-agent',
      'multi-command prompt',
      'agent pipeline',
      'slash commands',
      '工作流编排',
      '多步骤 agent',
      '斜杠命令编排',
      '子代理',
      'Claude Code GUI',
      'OpenCockpit',
      'Cockpit',
    ],
    content: {
      en: {
        title: 'Orchestrate a workflow from the message box',
        description:
          'A Cockpit message can now hold more than one command. Start several lines with `/` or `@` and Cockpit reads the whole message as an ordered, multi-step workflow — `/` runs in the main session, `@` delegates to a sub-agent. Plan the run once, send it once.',
        readingTime: '4 min read',
        body: `Most agent work isn't one instruction — it's a small sequence. *Clarify what I actually want, then fix it, then have something independent review the fix.* Until now that was three messages, sent one at a time, each waiting on the last. As of this release you can write the whole thing in one message: start lines with \`/\` or \`@\` and Cockpit reads them as an ordered workflow.

## One message, several steps

Every line that starts with a known command becomes a step. The first character picks where it runs:

| Marker | Where the step runs |
|---|---|
| \`/verb\` | the **main session** — the AI continues in this chat |
| \`@verb\` | a **sub-agent** — delegated to a separate agent, then reported back |

So this:

\`\`\`text
Here is the failing test output: payment webhook 500s on retries.
/fx
figure out why the idempotency key isn't being honored
@cr
audit the fix for race conditions and missing rollbacks
\`\`\`

…becomes a single numbered plan the AI works through in order. The text before the first command rides along as shared context — paste your log or state the goal once, up top, for the whole run. Everything under a command line, including blank lines and multiple paragraphs, belongs to that step.

You wrote four lines. The agent received a structured plan.

## \`/\` keeps it close, \`@\` sends it away

The two markers are about *attention*, not just routing:

- **\`/verb\`** keeps the work in the current chat, where you can watch it turn by turn and steer. Use it for the steps you care about.
- **\`@verb\`** hands a self-contained chunk — a review, an exploration, a focused investigation — to a sub-agent, which does it and summarizes back without cluttering the main thread.

The everyday shape is "do it here, then send someone to check it":

\`\`\`text
/go
implement the retry backoff described in the ticket
@cr
review what was just written for correctness and style
\`\`\`

## Your own skills, in the mix

Steps aren't limited to the built-ins. Any [skill](/en/docs/agent/skills/) you've installed is a verb too, and built-ins and your skills can sit side by side in the same workflow — they all resolve through the same "read this SKILL.md" path. If a skill you wrote shares a name with a built-in, yours wins, so your version is what runs.

And autocomplete now follows your cursor: type \`/\` or \`@\` at the start of *any* line — the second, the third — and the command menu pops for that line. That's what makes stacking steps feel natural instead of fiddly.

## Nothing changes for one-offs

If your message is a single \`/verb\` with no preamble and no \`@\`, it behaves exactly as before — one command, one turn, no ceremony. The numbered plan only appears when there's a real workflow to run: two or more commands, any \`@\` step, or leading context text. The simple case stays simple; the multi-step case finally gets to be one message.

## Try it

Update Cockpit, open a chat, and write two commands on two lines — say \`/qa\` then \`@cr\`. Watch the agent receive them as an ordered plan. Full details in the [Workflows](/en/docs/agent/workflows/) docs.

---

**Try it:** \`npm i -g @surething/cockpit\` · [GitHub](https://github.com/Surething-io/cockpit) · [Try Online](/try)`,
      },
      zh: {
        title: '在消息框里编排一条工作流',
        description:
          'Cockpit 的一条消息现在可以放不止一个命令。让好几行分别以 `/` 或 `@` 开头,Cockpit 就把整条消息当成一条有序的多步工作流来读 —— `/` 在主会话执行,`@` 委派给子代理。一次规划,一次发送。',
        readingTime: '阅读约 4 分钟',
        body: `大多数 agent 活儿都不是一条指令,而是一小段序列。*先澄清我到底要什么,再修它,然后让一个独立的东西来审这个修复。* 在此之前这是三条消息,一条一条发,每条都等着上一条。从这个版本起,你可以把整件事写进一条消息:让行以 \`/\` 或 \`@\` 开头,Cockpit 就把它们读成一条有序工作流。

## 一条消息,多个步骤

每一行只要以已知命令开头,就成为一个步骤。第一个字符决定它在哪里跑:

| 标记 | 步骤在哪里执行 |
|---|---|
| \`/verb\` | **主会话** —— AI 在这个聊天里继续 |
| \`@verb\` | **子代理** —— 委派给一个独立 agent,完成后回报 |

于是这样:

\`\`\`text
这是失败的测试输出:支付 webhook 在重试时返回 500。
/fx
查清楚为什么幂等键没被尊重
@cr
审一下这个修复有没有竞态和漏掉的回滚
\`\`\`

……就变成一份带编号的计划,AI 按序推进。第一条命令之前的文字作为共享上下文一起带上 —— 把日志贴在顶部,或为整条工作流统一交代一次目标。命令行下面的所有内容,包括空行和多个段落,都属于那个步骤。

你写了四行。Agent 收到的是一份结构化计划。

## \`/\` 留在身边,\`@\` 派出去

这两个标记关乎的是*注意力*,不只是路由:

- **\`/verb\`** 把工作留在当前聊天,你能逐轮盯着、随时纠偏。用在你真正在意的步骤上。
- **\`@verb\`** 把一块自成一体的活儿 —— 一轮审查、一次探索、一段聚焦调查 —— 交给子代理,它办完并回来给小结,不塞满主线。

日常形态就是「在这儿干,再派个人来检查」:

\`\`\`text
/go
实现工单里描述的重试退避
@cr
审一下刚写的东西,看正确性和风格
\`\`\`

## 把你自己的 skill 也混进来

步骤不限于内置命令。你装过的任意 [skill](/zh/docs/agent/skills/) 也是一个 verb,内置命令和你的 skill 可以并排出现在同一条工作流里 —— 它们都走同一条「读这个 SKILL.md」的路径解析。如果你写的 skill 和某个内置同名,你的优先,所以跑的是你那一版。

而且自动补全现在跟着光标走:在*任意一行*开头打 \`/\` 或 \`@\` —— 第二行、第三行 —— 命令菜单就为那一行弹出。这正是让叠步骤变得自然、而不是别扭的关键。

## 单次命令一切照旧

如果你的消息只是一个 \`/verb\`、没有前言、也没有 \`@\`,它的行为和以前完全一样 —— 一个命令、一轮、没有多余仪式。带编号的计划只在真的有工作流要跑时才出现:两个及以上命令、任意 \`@\` 步骤、或开头有上下文文字。简单的事保持简单;多步的事终于能放进一条消息。

## 试一下

更新 Cockpit,打开一个聊天,在两行上写两个命令 —— 比如 \`/qa\` 然后 \`@cr\`。看着 agent 把它们当成一份有序计划接收。完整细节见[工作流](/zh/docs/agent/workflows/)文档。

---

**试试看:** \`npm i -g @surething/cockpit\` · [GitHub](https://github.com/Surething-io/cockpit) · [在线体验](/try)`,
      },
    },
  },

  {
    slug: 'claude-code-cli-execution-mode',
    date: '2026-06-10',
    keywords: [
      'Claude Code CLI',
      'Claude Agent SDK',
      'Claude Agent SDK billing',
      'Claude Agent SDK usage limits',
      'claude -p',
      'Claude Code subscription',
      'Claude plan usage limits',
      'Claude Code billing change',
      'June 15 2026 Claude billing',
      'interactive Claude Code',
      'Claude 订阅计费',
      'Claude Code 订阅额度',
      'Claude 用量限制',
      'PTY mode',
      'execution mode',
      'Claude Code GUI',
      'OpenCockpit',
      'Cockpit',
    ],
    content: {
      en: {
        title: 'Cockpit now has a Claude Code CLI execution mode',
        description:
          'Every Claude chat tab in Cockpit can now run in one of two ways: the Claude Agent SDK (headless `query()`), or a real interactive `claude` session driven in a PTY. Both share the same conversation, so you can switch per tab, mid-conversation, one click away. Here is what the new CLI mode is and how it works.',
        readingTime: '5 min read',
        body: `Cockpit drives Claude Code through the Claude Agent SDK — \`query()\`, headless, programmatic. That is still the default. As of this release, each Claude / Claude2 chat tab also has a second execution mode you can flip to: **Claude Code CLI**.

Here is what the two modes are, and how the new one works.

## Two ways to run the same conversation

The toggle sits right above the message list:

| Mode | What actually runs |
|---|---|
| **Claude Agent SDK** | \`query()\` — headless, programmatic |
| **Claude Code CLI** | a real interactive \`claude\` session in a PTY |

Same Claude, same project, same tools. The only difference is *how* the turn is driven.

In **Claude Code CLI** mode, every message spawns an interactive \`claude\` in a pseudo-terminal, types your prompt into the live REPL exactly like a human at a keyboard would, watches the session transcript to know when the turn is done, and exits. It is ephemeral — no daemon, no resident process — but for the duration of a turn it is, by every observable signal, an interactive Claude Code session.

Different invocation paths can be metered differently over time. We will leave you to decide which one fits; the point here is that you now have the choice, per tab, one click away.

## Same session, seamless switching

The detail that makes this painless: **both modes read and write the same session file.**

The SDK's \`query({ resume })\` and the CLI's \`claude -r\` operate on the *same* \`~/.claude/projects/.../<id>.jsonl\` transcript — they are the same underlying \`claude-code\` binary. So:

- One continuous conversation, one history. No "SDK history" vs "CLI history."
- You can flip a tab between **Claude Agent SDK** and **Claude Code CLI** mid-conversation and the next turn just continues.
- The chat panel always renders from that one transcript, so the UI does not change between modes.

You are not choosing between two products. You are choosing how the *next message* runs.

## A real terminal you can actually see

Driving an interactive TUI from code is the kind of thing that works in a demo and then quietly wedges on a dialog at 2am. So CLI mode is not a black box — it ships with a small floating terminal in the corner of the chat.

- It shows the **live, character-by-character** output of the real \`claude\` session. (The chat panel, rendered from the transcript, is block-level — the floating window is where you get the typewriter feel.)
- It **auto-expands** while a turn is running and collapses when it is done.
- If a turn ever gets stuck — say Claude is waiting on a prompt the driver did not anticipate — you get a notice in the chat bubble *and* you can **type directly into that terminal** to nudge it through by hand. Human-in-the-loop, exactly where you would want it.

Press \`ESC\` to interrupt at any time; the session is reaped cleanly, with no orphan processes left behind.

## Try it

Update Cockpit, open a Claude or Claude2 chat, and look for the **Claude Agent SDK / Claude Code CLI** toggle above the message list. Flip it to **Claude Code CLI**, send a message, and watch the floating terminal drive a real interactive session.

---

**Try it:** \`npm i -g @surething/cockpit\` · [GitHub](https://github.com/Surething-io/cockpit) · [Try Online](/try)`,
      },
      zh: {
        title: 'Cockpit 新增 Claude Code CLI 执行模式',
        description:
          'Cockpit 里每个 Claude 聊天标签现在都能用两种方式跑：Claude Agent SDK（无头 `query()`），或在 PTY 里驱动一个真·交互式 `claude` 会话。两者共享同一份对话，所以你可以 per-tab、在对话中途、一键切换。这篇讲讲新增的 CLI 模式是什么、怎么工作。',
        readingTime: '阅读约 5 分钟',
        body: `Cockpit 一直是通过 Claude Agent SDK 来驱动 Claude Code 的——\`query()\`、无头、程序化。这仍然是默认。从这个版本起，每个 Claude / Claude2 聊天标签多了一个可切换的执行模式：**Claude Code CLI**。

下面讲讲这两种模式是什么，以及新的那个怎么工作。

## 同一段对话的两种跑法

切换条就在消息列表上方：

| 模式 | 实际跑什么 |
|---|---|
| **Claude Agent SDK** | \`query()\`——无头、程序化 |
| **Claude Code CLI** | PTY 里一个真·交互式 \`claude\` 会话 |

同一个 Claude、同一个项目、同一套工具。唯一的区别是这一轮**怎么被驱动**。

**Claude Code CLI** 模式下，每条消息都会在伪终端里 spawn 一个交互式 \`claude\`，像一个人坐在键盘前那样把 prompt 敲进活着的 REPL，盯着会话的 transcript 判断本轮何时结束，然后退出。它是临时的——没有守护进程、没有常驻进程——但在这一轮的整个过程里，从每一个可观测信号看，它就是一个交互式 Claude Code 会话。

不同的调用路径，未来可能被以不同方式计量。具体选哪个，留给你自己判断；这里要说的是：你现在有了这个选择，per-tab、一键即达。

## 同一份会话，无缝切换

让这一切不痛苦的关键细节是：**两种模式读写的是同一个会话文件。**

SDK 的 \`query({ resume })\` 和 CLI 的 \`claude -r\` 操作的是**同一份** \`~/.claude/projects/.../<id>.jsonl\` transcript——它们底层就是同一个 \`claude-code\` 二进制。于是：

- 一条连续的对话、一份历史。不存在「SDK 历史」和「CLI 历史」两套。
- 你可以在对话中途把某个标签在 **Claude Agent SDK** 和 **Claude Code CLI** 之间来回切，下一轮直接续上。
- 聊天面板永远从那一份 transcript 渲染，所以 UI 在两种模式间不会有任何变化。

你不是在两个产品之间做选择。你是在选**下一条消息**怎么跑。

## 一个你真能看见的终端

用代码驱动一个交互式 TUI，是那种 demo 里跑得好好的、然后凌晨两点悄悄卡在一个对话框上的活儿。所以 CLI 模式不是个黑盒——它在聊天角落带了一个小小的浮动终端。

- 它显示真 \`claude\` 会话的**逐字实时**输出。（聊天面板从 transcript 渲染、是块级的——逐字打字机的体感由浮动窗提供。）
- 它在一轮运行时**自动展开**，结束后自动折叠。
- 万一某轮卡住了——比如 Claude 在等一个驱动没预料到的对话框——你会在聊天气泡里收到提示，*而且*可以**直接在那个终端里打字**，手动把它推过去。Human-in-the-loop，正好在你想要它的地方。

任何时候按 \`ESC\` 都能中断；会话会被干净回收，不留孤儿进程。

## 试一下

更新 Cockpit，打开一个 Claude 或 Claude2 聊天，在消息列表上方找到 **Claude Agent SDK / Claude Code CLI** 切换条。切到 **Claude Code CLI** 发一条消息——看着浮动终端去驱动一个真正的交互式会话。

---

**试试看：** \`npm i -g @surething/cockpit\` · [GitHub](https://github.com/Surething-io/cockpit) · [在线体验](/try)`,
      },
    },
  },

  {
    slug: 'code-graph-for-ai-agents',
    date: '2026-05-22',
    keywords: [
      'code graph',
      'code knowledge graph',
      'AI code agent',
      'AI code intelligence',
      'code graph for AI',
      'tree-sitter',
      'project graph',
      'code navigation AI',
      'conventional coupling',
      'Claude Code GUI',
      'OpenCockpit',
      'Cockpit',
    ],
    content: {
      en: {
        title: 'What is a Code Graph (and why your AI needs one)',
        description:
          "A code graph is the structured map of your project's symbols and their relationships — who calls whom, what depends on what, which files always get edited together. It is exactly the missing layer between an AI agent and your codebase, and the reason `grep` is the agent's ceiling on the questions that matter most.",
        readingTime: '6 min read',
        body: `A **code graph** is a structured map of your project's symbols and the relationships between them — who calls whom, what depends on what, which files always get edited together. It is the kind of mental model a human builds before refactoring. For an AI agent still doing \`grep -r\` to find anything, it is the missing layer.

Here is why that matters in practice.

## A small disaster

Last week I added a new slash command to Cockpit. Code change was four lines. I tested it. Worked. Committed. Pushed.

Next morning a teammate messaged: *"I can't see it in the autocomplete menu though?"*

The new command had to be registered in **two** files. One for the prompt expansion, one for the menu listing. They don't import each other. They don't share a function. They just have to stay in sync — a convention nobody documents, that the codebase enforces nowhere.

Before I made that change, I had asked the agent: *"If I change this function, what else needs updating?"* It ran \`grep\`, found five callers, summarized them confidently. The menu file wasn't a caller. \`grep\` couldn't see the relationship. Neither could the agent.

## Why grep is the agent's ceiling

When an AI agent is just driving \`grep\`, it inherits \`grep\`'s blind spots:

- **Relationships are invisible.** \`grep\` knows the string \`createOrder\` shows up 12 times. It does not know which of those are *calls* and which are comments, tests, or unrelated strings.
- **Conventions are invisible.** Two files with the same constant — one defines it, one mirrors it — look like two unrelated occurrences. \`grep\` cannot encode "these must stay in sync."

Most AI exploration runs on \`grep\` because most code questions are simple lookups. The 10% that aren't are exactly where you needed the help most.

## What a code graph gives the agent

\`/cg\` is the slash command. Type it in any Cockpit chat:

\`\`\`
/cg if I rename createOrder, what breaks?
\`\`\`

You did not learn a new tool. You did not install anything. You typed three letters. The agent now answers as if it had just spent an hour reading the codebase.

Six question shapes get sharper answers:

| You ask | The agent now actually knows |
|---|---|
| "Where is \`X\` defined?" | the file and line range, in one query |
| "Who uses \`X\`?" | real callers, not string matches |
| "What does \`X\` depend on?" | the downstream chain |
| **"What does changing \`X\` affect?"** | not just direct callers — the ripple two hops out |
| "What's in this file?" | the symbol outline, without reading the whole file |
| **"What files always get edited alongside this one?"** | the *convention* couplings \`grep\` can never see |

The last one is the one that would have saved me.

## The story, replayed

I redo the change with \`/cg\`:

> *"If I add a new slash command in this file, what else do I need to touch?"*

The agent ran the usual call-graph queries, then ran one more — "what files commonly get edited together with this one?" — and came back with:

> *"Direct callers are five chat routes; signature is stable. **One caveat: \`commands.ts\` in the same module gets edited together with this file in most recent commits. If your change adds a new command verb, you probably need to register it in \`commands.ts\` too — looks like a parallel menu list.**"*

Three lines. The whole future-bug fix.

## Code Map for your eyes, CodeGraph for the agent

If you have used Cockpit's [Code Map](/en/blog/read-code-as-a-map/), you have already seen the human side of this. Code Map renders the same project structure as clickable chips — function callers on the left, callees on the right — so *you* can walk the call graph in five clicks.

CodeGraph is the same idea, made queryable. Same tree-sitter index, same call graph, same git history. Code Map serves it to your eyes; CodeGraph serves it to your agent.

Same fact, two consumers.

## What it doesn't do

\`/cg\` is not a fixer. It does not reach into config files, JSON, or unstructured docs — for those, regular \`grep\` is still the right tool. And if you already know which file to edit, just \`Edit\` it; you don't need an exploration mode.

But the moment your question contains the words "what else" or "who depends on" — that is when the gap between \`grep\` and a code graph shows up.

## Try it

In any Cockpit chat:

\`\`\`
/cg
\`\`\`

That is the entire onboarding. Try it on a function whose impact you don't fully understand. The agent will tell you something you would not have found on your own.

---

\`npm i -g @surething/cockpit\` · [GitHub](https://github.com/Surething-io/cockpit) · [Try Online](/try)`,
      },
      zh: {
        title: 'Code Graph：给 AI 一张项目图谱',
        description:
          '代码图谱（code graph）是你项目里所有符号和它们之间关系的结构化地图——谁调用谁、谁依赖谁、哪些文件总是一起被改。这正是 AI Agent 和你代码库之间缺失的那一层，也是 grep 在最重要的问题上成为 Agent 天花板的原因。',
        readingTime: '6 min read',
        body: `**代码图谱（code graph）**是你项目里所有符号和它们之间关系的结构化地图——谁调用谁、谁依赖谁、哪些文件总是一起被改。这本就是人在重构前画在白板上的那张图。但对一个还在 \`grep -r\` 找东西的 AI Agent 来说，这一层是缺失的。

下面讲为什么这件事在实际工作里很要命。

## 一个小翻车

上周我给 Cockpit 加了一个新的斜杠命令。代码改了四行。本地测了。能跑。提交。推送。

第二天同事消息：*"补全菜单里看不到啊？"*

原来这个新命令需要在**两个文件**登记——一个负责 prompt 展开，一个负责出现在菜单。它们不互相 import，不共用函数，**只是约定要同名同步**。没人文档化这件事，代码里也没编译时检查。

我改之前问过 Agent："如果改这个函数，还要动哪些地方？" 它 \`grep\` 一通，找出 5 个调用方，自信满满总结了一遍。菜单文件不是调用方。\`grep\` 看不见这种关系，Agent 也看不见。

## 为什么 grep 是 Agent 的天花板

当 AI 只能驱动 \`grep\`，它就继承了 \`grep\` 的所有盲区：

- **关系是看不见的。** \`grep\` 知道字符串 \`createOrder\` 出现 12 次，但不知道哪些是真调用、哪些是注释、测试或巧合同名。
- **约定是看不见的。** 两个文件用了同一个常量——一个定义、一个镜像——在 \`grep\` 眼里就是两个无关的出现。它没法表达"这俩必须同步改"。

大多数 AI 探索靠 \`grep\` 也能跑——因为大多数问题确实只是简单查找。但剩下 10% 不能跑的，恰恰是你最需要帮手的时候。

## Code Graph 给 Agent 什么

\`/cg\` 是斜杠模式。Cockpit 任意聊天里输入：

\`\`\`
/cg 如果重命名 createOrder 会牵连什么？
\`\`\`

你没学新工具，没装东西，就敲了三个字符。Agent 现在回答问题的方式，就像它刚花了一小时把代码库读完。

六种问题答得更准：

| 你问 | Agent 现在真的知道 |
|---|---|
| "X 在哪定义？" | 文件 + 行号，一次拿到 |
| "谁用了 X？" | 真实调用方，不混杂字符串巧合 |
| "X 依赖什么？" | 下游链 |
| **"改 X 会影响什么？"** | 不止直接调用方——连两跳传递性影响也展开 |
| "这个文件里有啥？" | 符号大纲，不用读全文 |
| **"哪些文件总和这个一起被改？"** | grep 永远看不到的**约定耦合** |

最后一行就是当初能救我的那条。

## 故事重演

我用 \`/cg\` 重做一次：

> *"我要在这个文件加一个新的斜杠命令，还需要动哪些地方？"*

Agent 跑了常规的调用图查询，又多跑了一个——"这个文件在最近的提交里通常和哪些文件一起改"——回我：

> *"直接调用方是 5 个 chat 路由；签名稳定。**额外注意：同模块的 \`commands.ts\` 最近几次相关提交都和这个文件一起改。如果你加了新 verb，多半也得在 \`commands.ts\` 里登记——看起来是个并行的菜单列表。**"*

三行话。一个本来会发版后才发现的 bug，提前抓住了。

## Code Map 给眼睛，CodeGraph 给 Agent

用过 Cockpit [Code Map](/zh/blog/read-code-as-a-map/) 的人，已经见过这件事的人类版——把同一份项目结构渲染成可点击的代码 chip，左边是调用者，右边是被调用，你五次点击就能走完一条调用链。

CodeGraph 是同一个想法，换成可查询接口。**同一份 tree-sitter 索引、同一张调用图、同一段 git history**——Code Map 端给你眼睛，CodeGraph 端给你的 Agent。

同一个事实，两种消费方式。

## 它不做什么

\`/cg\` 不修代码，不读 JSON / yaml / 文档——那些场景普通 \`grep\` 还是对的工具。如果你已经知道要改哪个文件，直接 Edit 就好，不需要进探索模式。

但当你的问题里出现"还会影响什么""谁依赖"这种字眼——这就是 \`grep\` 和 code graph 差距显形的时候。

## 试一下

任意 Cockpit chat 里：

\`\`\`
/cg
\`\`\`

这就是全部上手成本。挑一个你不完全确定影响范围的函数试试——Agent 会告诉你一些你自己找不到的东西。

---

\`npm i -g @surething/cockpit\` · [GitHub](https://github.com/Surething-io/cockpit) · [在线体验](/try)`,
      },
    },
  },
  {
    slug: 'vibe-coding-needs-taste',
    date: '2026-05-12',
    keywords: [
      'vibe coding',
      'AI coding agent',
      'code taste',
      'code aesthetics',
      'package boundaries',
      'monorepo structure',
      'npm package design',
      'codebase architecture',
      'cohesion',
      'engineering discipline',
      'OpenCockpit',
      'Cockpit',
      'Claude Code',
    ],
    content: {
      en: {
        title: 'Vibe coding needs a bit of taste',
        description:
          "Vibe coding's hidden cost isn't speed — it's entropy. Cockpit's repo today is two piles: `packages/feature/` is the business, `packages/shared/` is the common floor, arrows go one way. From that picture, three old-school engineering habits — putting things in the right place, drawing real boundaries, deleting what doesn't fit — become more valuable when the agent is the one typing.",
        readingTime: '6 min read',
        body: `The agent finishes a run. Diff is a hundred-something lines. Looks fine. You hit approve.

Two weeks later, something's broken somewhere and you can't find it. Every function is reasonable. No name is bad enough to need changing. You just don't really want to open this repo anymore.

What's hard about vibe coding isn't making the agent faster. It's the codebase still looking like a codebase after the agent has run a thousand times.

## The current shape

\`\`\`
src/                       Next.js entry, no business code
 │
 ▼
packages/feature/          agent  console  explorer
                           workspace  review  comments  skills
 │                         (may reference each other, must stay acyclic)
 ▼
packages/shared/           ui  utils  i18n
                           (leaf; cannot import feature)
\`\`\`

Three slots, that's the whole repo:

- \`src/\` is what Next.js itself needs — page files, thin shim routes that forward to the API handlers. No business code.
- \`packages/feature/\` is the business. Seven packages, each a standalone npm package.
- \`packages/shared/\` is the common floor. Three packages: UI components, utilities, the i18n dictionary.

Arrows go one way: anything on top may depend on anything below, but the bottom never imports up. ESLint watches this. Write it the wrong way and lint refuses to pass.

Every point that follows lands on a specific spot in that picture.

## A few old-school things

**Where a thing goes.** Chat — API, UI, state, scheduled jobs, slash commands — all lives under \`packages/feature/agent\`, one folder. To change anything about chat you don't hunt across the repo. This isn't for tidiness. It's so a person — or an agent — can finish a job with one folder's worth of attention.

**Boundaries.** \`shared/\` is not allowed to import from \`feature/\`. This rule isn't in a README that humans are supposed to remember; it's in ESLint, enforced by a tool. "Be considerate to the next reader" isn't a slogan — it's a lint error.

**Delete with conviction.** The dev dependencies I've removed weren't bad because they were old. They were bad because they didn't fit anywhere in the picture — not part of any feature, not part of the shared floor. Anything that doesn't fit the picture shouldn't be in the repo.

**Don't invent vocabulary.** The picture has two nouns: feature and shared. I didn't use domain, didn't use module, didn't use app or infra. An npm package is a contract everyone already understands; "feature" and "shared" are plain English. Every noun you'll find in this repo is either npm's own term or a word a middle schooler reads without thinking.

## Three pictures from this refactor

**\`src/\` got emptied out.** It used to hold components, hooks, contexts — the lot. Now it's only the top slot of the diagram — Next.js's entry. The business sits in the middle slot. When you open the repo you can tell at a glance where to look, because framework noise and business work are physically separated.

**Only two horizontal piles.** Adding a third is easy: feature, shared, then "infra"? Then "core"? Every new name needs explaining, and every explanation eventually triggers "well, which pile does this go in?" I held the line at two. Anyone who has ever installed an npm package needs zero extra training to read this repo — \`package.json\`, \`exports\`, \`dependencies\` — they already know how those work. I didn't make them learn anything new.

**The moment of deletion.** A test framework that hadn't been run in a year, a component sandbox nobody opened, a browser-automation harness from a finished experiment. One commit, all gone. I hesitated for two seconds before deleting, and nobody missed any of it after. Admitting I'd added something I shouldn't have is a thing I do faster every year.

## How the agent moves inside this picture

A human reads code with intuition. An agent reads whatever fits in its context window. The picture itself helps the agent in two specific ways.

**Blast radius has a ceiling.** When the agent is changing chat, it opens \`packages/feature/agent\` and nothing else. It can't see, and doesn't need to see, console or explorer. The physical separation makes "agent casually broke an unrelated feature" structurally hard, not just unlikely.

**The arrow direction teaches the agent how to write.** When the agent is writing inside \`shared/\`, it can't see any feature — it literally cannot import a feature's internals. That forces it to write something genuinely general. When it's writing inside \`feature/\`, it knows it can't reach beyond \`shared/\`, and that knowledge lets it work without hedging.

A codebase's habits, the agent learns fast. That shortcut you took six months ago — it's already in the context window, presented as the project's style, and the agent will follow it. A repo without taste teaches the agent to be tasteless, fast. The flip side: when the words in the repo are ones the agent has read a million times — \`package.json\`, \`exports\`, \`dependencies\` — it just works. It hasn't read your homemade module system.

## Last thing

Taste isn't a synonym for slowness. It's what lets "fast" last past the second month.

Set the boundaries, keep the vocabulary as small as you can, pull out what isn't being used. Let the agent run inside that shape — it can keep up. A year later you come back and you still want to open the repo. That's not a thing that lands in the changelog, but it's the thing that decides whether this project stays fun to work on.

---

\`npm i -g @surething/cockpit\` · [GitHub](https://github.com/Surething-io/cockpit) · [Try Online](/try)`,
      },
      zh: {
        title: 'Vibe coding 需要一点品味',
        description:
          '现在的 Cockpit 仓库就两堆代码：`packages/feature/` 是业务，`packages/shared/` 是公共底子，箭头只有一个方向。从这张图能讲清楚为什么 vibe coding 时代反而更需要品味——把东西放对、划清边界、敢删没用的东西，三件老掉牙的事在 agent 改代码的当下比以前更值钱。',
        readingTime: '阅读约 6 分钟',
        body: `agent 跑完一轮，diff 一百多行，看着没毛病，你点了通过。

两周以后某个角落坏了。回去看，每个函数都说得过去，没有哪个名字烂到必须改，但你就是不太愿意再打开这个仓库。

vibe coding 真正难的不是怎么让 agent 跑得更快。是跑了一千次以后，仓库还像个仓库。

## 现在的架构

\`\`\`
src/                       Next.js 入口，没有业务
 │
 ▼
packages/feature/          agent  console  explorer
                           workspace  review  comments  skills
 │                         （彼此可以互相依赖，必须无环）
 ▼
packages/shared/           ui  utils  i18n
                           （叶子，不允许反过来 import feature）
\`\`\`

整个仓库就这三块：

- \`src/\` 是 Next.js 框架自己需要的入口——页面文件、API route 的转发 shim——没有业务代码。
- \`packages/feature/\` 是业务，七个包，每个包是一个独立的 npm package。
- \`packages/shared/\` 是公共底子，三个包：UI 组件、工具函数、翻译字典。

箭头只有一个方向：上面的可以依赖下面的，下面的不允许反过来 import 上面。这一条规则由 ESLint 盯着，写反了过不了 lint。

后面要说的事情，全都落在这张图上的某个具体位置。

## 老掉牙的几件事

**一个东西该放哪儿。** 聊天功能的 API、UI、状态、定时任务、slash 命令，全在 \`packages/feature/agent\` 一个目录里。要改聊天的任何东西都不用满仓库找。这不是为了"看着整齐"，是让人——和 agent——能用一个文件夹的注意力做完一件事。

**边界。** \`shared/\` 不允许反向 import \`feature/\`。这条规则不是写在 README 里靠人记，是写在 ESLint 里靠工具卡。"对下一个读代码的人客气" 不是口号，是 lint 报错。

**该删要狠。** 删掉过的那十几个开发依赖，它们的问题不是"老"，是它们归不进图里任何一个位置——既不是某个 feature 的事，也不是 shared 的公共底子。归不进图的东西，就说明它不该留。

**不造词。** 图里只有两个名词：feature 和 shared。我没用 domain，没用 module，没用 app 和 infra。npm package 是一个所有人都懂的契约，feature 和 shared 是大白话。仓库里你能查到的所有名词，要么是 npm 自己的术语，要么是中学生都看得懂的英文单词。

## 这次重构里的三张画面

**\`src/\` 被搬空了。** 之前那里堆着组件、hook、context，一锅端。现在它只是图最上面那一小格——Next.js 的入口。业务全在中间那一格。打开仓库的人一眼就知道往哪儿看，因为框架噪音和业务被物理分开了。

**横向只有两堆。** 加第三堆很容易：feature、shared，再来一个 "infra"？再来一个 "core"？每一个新名字都要解释，解释就会引来"那这个东西该放哪一堆"的问题。我坚持两堆。装过 npm 包的人打开仓库不需要任何额外培训——\`package.json\` 怎么读、\`exports\` 怎么写、\`dependencies\` 怎么追，他都本能地知道。我没让他多学任何东西。

**删 devDep 那一刻。** 一个一年没跑过的测试框架、一个没人打开过的组件 demo 工具、一个废弃实验留下的浏览器自动化。一个 commit 全删。删之前犹豫过两秒，删之后没人想起它们。承认当初加错了，是我这两年做得越来越快的一件事。

## agent 在这张图里改代码

人读代码靠直觉。agent 读代码靠塞进上下文的那部分。这张图本身就在两个地方帮 agent。

**爆炸半径有上限。** agent 要改聊天，它打开 \`packages/feature/agent\` 这一个目录就够。它看不到、也不需要看到 console 或 explorer。物理隔离让"agent 顺手把一个无关功能改坏"这件事在结构上变得难以发生。

**箭头方向能教 agent 怎么写。** agent 在 \`shared/\` 里写代码时，看不到任何 feature——它根本没法 import 某个 feature 的内部细节。这强迫它写出真正"通用"的东西。反过来在 \`feature/\` 里写时，它知道改不到 shared 以外的世界，下手就更放。

代码库有什么习惯，agent 会学得飞快。你半年前留下的临时方案，它会当成"这个项目的写法"沿用下去。库里没品味，agent 就跟着没品味，速度比人快得多。反过来，库里用的都是 agent 见过几百万遍的那些词——\`package.json\`、\`exports\`、\`dependencies\`——它直接上手。它没读过你自创的模块体系。

## 最后

品味不是慢工细活的代名词。它是让"快"能撑过第二个月的那个东西。

把边界划清，词汇压到最小，没在用的东西早点拔掉，剩下的让 agent 去跑就行——它跑得动。一年以后回来，你还愿意打开这个仓库。这件事不会写进 changelog，但它决定了你做这个项目快不快乐。

---

\`npm i -g @surething/cockpit\` · [GitHub](https://github.com/Surething-io/cockpit) · [在线体验](/try)`,
      },
    },
  },
  {
    slug: 'read-code-as-a-map',
    date: '2026-05-07',
    keywords: [
      'Code Map',
      'code visualization',
      'call graph',
      'onboarding new codebase',
      'reading code',
      'code review AI',
      'AI generated code review',
      'function caller callee',
      'tree-sitter code analysis',
      'code navigation',
      'OpenCockpit',
      'Cockpit',
      'Claude Code',
    ],
    content: {
      en: {
        title: 'Read code as a map, not a tree',
        description:
          "The file tree shows you where bytes are stored. It does not show you how the code actually moves. Cockpit's new Code Map turns any source file into a canvas of function chips — callers on the left, callees on the right, click a pin to jump. Five clicks across an unfamiliar repo and you've walked the auth flow. Here is what that looks like in five real scenarios.",
        readingTime: '7 min read',
        body: `You clone a new repo. \`npm install\`. \`npm run dev\`. It works.

Now you have to actually **read** it.

The file tree opens. 47 folders. 312 files. Some named \`utils\`, some named \`lib\`, one called \`core\` and another called \`kernel\` (you suspect they overlap). Where do you start? Probably \`index.ts\`. After 20 minutes you've drifted three folders deep, you have 11 tabs open, and you still don't know which function is the entry point for the bug you came to fix.

The file tree is showing you **where files are stored**, not **how the code actually moves**.

## A different unit

Cockpit's new Code Map switches the unit. Instead of "files in folders", you see **functions, with their connections.**

Every function becomes a card on the canvas:

- The **body** — the actual code, syntax-highlighted — sits in the middle.
- On the **left**: every function that calls *this* one. (Callers.)
- On the **right**: every function that *this* one calls. (Callees.)
- Each entry on either side is clickable. Click and the canvas pans to that function.

That's the whole interface. The file tree is still there if you want it. But the moment you click into a file, you don't see "lines 1–840 of \`payment.ts\`." You see four chips: \`chargeCard\`, \`refund\`, \`webhookHandler\`, \`recordLedgerEntry\` — each with their own incoming and outgoing arrows.

## Day one in a new repo

This is the moment Code Map was built for. You join a project at 9am. By 10am you're supposed to "have a look at the auth flow." With the file tree, that's a 90-minute scavenger hunt. With Code Map:

1. Open the file you suspect is the entry point — \`routes/auth.ts\`.
2. The five route handlers each appear as their own chip.
3. Pick the one you care about: \`loginHandler\`. Its chip lights up.
4. The right column shows it calls \`validateCredentials\`, \`issueToken\`, \`recordLogin\`. Click \`validateCredentials\`.
5. The canvas pans. Now \`validateCredentials\` is the centre chip. Its callees are \`hashPassword\` and \`lookupUser\`. Its callers — left column — show you it's also called from \`resetPassword\`, which you didn't know existed.

In five clicks you've walked the auth tree. You haven't \`grep\`-ed for "login". You haven't gotten lost in \`utils/index.ts\`. The map you needed was always there in the code — you just needed someone to draw it.

## Following a call you don't trust

This is the thing every senior engineer secretly does and no junior is ever taught: when you're not sure why a function is being called, you walk **up** the call chain until you understand the entry point.

The traditional way is **grep + intuition**. \`grep -r 'createOrder'\` returns 23 hits. 19 are in tests. 2 are in comments. 2 are real call sites. You open both, scroll around, try to figure out which "happens first."

In Code Map, \`createOrder\`'s left column *is* the answer. Sorted, deduped, no test files unless you want them. Click each one to see the actual line. The whole "where does this get called" question is a 10-second visual inspection instead of a five-tab dig.

## Reviewing AI-generated PRs

You asked Claude to "fix the rate-limiter bug." It produced 8 file changes across 3 directories. The diff looks reasonable. You hit Approve.

You shouldn't.

Switch the same files into Code Map. Now the diff isn't a list of \`+/-\` lines — it's a chip view where the **changed functions are highlighted**, with their callers and callees still drawn around them. You can immediately see:

- The agent edited \`rateLimit\`. Its callers are \`apiHandler\` and \`webhookHandler\`. Did the change break the webhook path? Click the webhook caller, read the chip, done. 30 seconds.
- It also touched \`getClientIp\`, which has *eleven* callers — half of them in the auth subsystem. The agent didn't mention this. You probably want to read those eleven before approving.

For PRs you wrote yourself, this is overkill. For PRs an agent wrote at 3am while you were asleep, this is the difference between "I trust it" and "I should trust it."

## Tracing a bug across files

A user reports: "Sometimes when I refresh, the cart loses one item." You have a guess: something racy in \`syncCart\`. Open \`syncCart\` in Code Map.

Five callees. One is \`fetchCart\`. Two are flavours of \`mergeCart\`. One looks fishy: \`dedupeItems\`. Click. Its body shows a \`Set\` keyed on \`id\` — but the bug report mentions duplicate ids with **different sizes**. Found it.

Three clicks. No \`grep\`. No "open ten files in tabs and scroll." The map made the buggy node visible because the chips next to it were the right context.

## On the train, no LSP, no problem

Code Map runs on your laptop, parsed by tree-sitter. No language server, no project index, no background daemon. Open a folder, get a chip view. Close your laptop, fly to Berlin, open it on the plane — same chip view, no indexing wait.

This matters more than it sounds. LSP-based tools (VSCode's "find references", JetBrains' "show callers") all need a fully booted project: \`tsconfig\` resolved, \`pip install\` done, \`go.mod\` complete. Code Map skips that. It reads your files the way a careful human reader would. If they parse, you get a chip view. That's it.

It works on **TypeScript / JavaScript, Python, Go, Rust** today. As a user, that's all you need to know.

## When *not* to use it

To be fair: Code Map isn't trying to be your editor. If you're writing new code, you're in the regular Explorer with a cursor and the LSP popping up types. Code Map is for the moment **before** you write — when you need to read first.

A useful split:

- **File tree + editor** — when you know what you're changing and where.
- **Code Map** — when the question is "what calls what, and where do I start?"

You'll toggle between them all day. Both views look at the same files. They just answer different questions.

## Try it

Open Cockpit, go to Explorer, open any source file, hit the **Code Map** toggle. The chip view replaces the editor pane — same file, different lens. Click a callee pin to fly to the next function. Toggle back when you're done.

That repo you've been meaning to read since January? It's a five-minute walkthrough now.

---

\`npm i -g @surething/cockpit\` · [GitHub](https://github.com/Surething-io/cockpit) · [Try Online](/try)`,
      },
      zh: {
        title: '把代码读成地图，而不是树',
        description:
          '文件树告诉你字节存在哪里，但不告诉你代码如何流动。Cockpit 新的 Code Map 视图把任意源文件渲染为函数 chip 画布——左侧列出谁调用了这个函数，右侧列出它调用了谁，点击 pin 即可跳转。陌生代码库五次点击就能走完一遍鉴权流程。下面是 5 个真实使用场景。',
        readingTime: '阅读约 7 分钟',
        body: `你 clone 了一个新仓库。\`npm install\`、\`npm run dev\`，跑起来了。

接下来要真正**读懂**它。

文件树展开：47 个文件夹、312 个文件。其中几个叫 \`utils\`、几个叫 \`lib\`、一个叫 \`core\` 还有一个叫 \`kernel\`（你怀疑它们重叠）。从哪开始？大概 \`index.ts\` 吧。20 分钟之后你已经飘进了第三层子目录，开了 11 个 tab，仍然不知道你要修的那个 bug 入口在哪个函数里。

文件树告诉你的是**字节存在哪里**，而不是**代码怎么流动**。

## 换一个单位

Cockpit 新的 Code Map 把"单位"换掉了。你看到的不再是"文件夹里的文件"，而是**函数 + 它们之间的连线**。

每个函数变成画布上的一张卡片：

- **中间是函数体** —— 真实代码、语法高亮。
- **左侧**：所有调用这个函数的地方（caller）。
- **右侧**：这个函数调用的所有目标（callee）。
- 两侧每一项都可点击。点一下，画布平移到那个函数。

整个界面就这么简单。文件树还在，想要随时切回去。但只要你点进一个文件，你看到的就不再是 "\`payment.ts\` 第 1–840 行"，而是四张 chip：\`chargeCard\`、\`refund\`、\`webhookHandler\`、\`recordLedgerEntry\` —— 每张都画着自己的进出箭头。

## 场景一：新仓库的第一天

这是 Code Map 最初被造出来要解决的场景。早上 9 点入职，10 点你被要求"看一下我们的鉴权流程"。靠文件树，这是一场 90 分钟的"找地鼠"。靠 Code Map：

1. 打开你猜是入口的文件 —— \`routes/auth.ts\`。
2. 文件里五个路由处理函数各自变成一张 chip。
3. 选你关心的那个：\`loginHandler\`。这张 chip 高亮。
4. 右侧列出：它调用了 \`validateCredentials\`、\`issueToken\`、\`recordLogin\`。点击 \`validateCredentials\`。
5. 画布平移。现在 \`validateCredentials\` 在中间。它的 callee 是 \`hashPassword\` 和 \`lookupUser\`；它的 caller —— 左侧 —— 告诉你它还被 \`resetPassword\` 调用，而你之前根本不知道有这个函数。

五次点击就把鉴权树走完了。没 \`grep\` 过 "login"，没在 \`utils/index.ts\` 里迷路。**这张地图本来就藏在代码里 —— 只是需要有人把它画出来。**

## 场景二：追一个你不放心的调用

这是每个资深工程师都会偷偷做、却没人教新人的事：当你对一个函数为什么被调用感到不安，你会**沿着调用链往上走**，直到看清入口。

传统做法是 **grep + 直觉**。\`grep -r 'createOrder'\` 命中 23 次：19 个在测试里，2 个在注释里，2 个是真正的调用点。你打开两个，上下翻找，琢磨哪个"先发生"。

在 Code Map 里，\`createOrder\` 左侧那一列**就是答案**。已排序、已去重，默认不混测试文件（除非你要看）。点每一项就跳到具体那行。"这个函数到底是从哪里被调用的"这个问题，从五个 tab 的挖掘变成 10 秒钟的视觉检查。

## 场景三：评审 AI 写的 PR

你让 Claude "修一下限流器的 bug"。它给你交出 8 个文件、3 个目录的改动。Diff 看上去合理。你正打算 Approve。

**先别。**

把同样这些文件切到 Code Map。Diff 不再是一长串 \`+/-\` 行，而是一张 chip 视图，**改动过的函数被高亮**，周围还画着它们的 caller 和 callee。你立刻能看到：

- Agent 改了 \`rateLimit\`。它的 caller 是 \`apiHandler\` 和 \`webhookHandler\`。这次改动会不会把 webhook 那条路径搞坏？点进去，读 chip，30 秒搞定。
- 它还改了 \`getClientIp\`，这个函数有**11 个 caller**，其中一半在鉴权子系统里。Agent 没在 PR 里提这件事。你大概率得先把这 11 处都看一遍再 approve。

你自己写的 PR 这么做有点小题大做。但凌晨 3 点 Agent 趁你睡觉时写的 PR，这就是"我相信它"和"我应该相信它"之间的差别。

## 场景四：跨文件追 bug

用户报告："偶尔刷新一下，购物车会丢一件商品。"你的猜测是 \`syncCart\` 里有竞态。在 Code Map 里打开 \`syncCart\`。

五个 callee：一个 \`fetchCart\`、两个 \`mergeCart\` 的变体、一个看上去可疑的 \`dedupeItems\`。点击 \`dedupeItems\`，函数体里有一个以 \`id\` 为 key 的 \`Set\` —— 但 bug 报告里说有些商品 id 相同、**尺寸不同**。**抓到了。**

三次点击。没 \`grep\`、没有"开十个 tab 上下翻滚"。地图把"出 bug 的那一节"放在你眼前，是因为它周围的 chip 给了你正确的上下文。

## 场景五：飞机上、没有 LSP，照样能读

Code Map 完全跑在你笔电上，由 tree-sitter 解析。没有 language server、没有项目索引、没有后台守护进程。打开一个目录，立刻有 chip 视图。合上电脑、飞去柏林，飞机上打开同一个目录 —— 还是那张 chip 视图，零索引等待。

这件事比听上去重要。基于 LSP 的工具（VSCode 的 "find references"、JetBrains 的 "show callers"）都需要项目完全启动：\`tsconfig\` 解析完、\`pip install\` 装完、\`go.mod\` 完整。Code Map 跳过这些前置。它像一个仔细的人类读者那样读你的代码：能解析就能出 chip 视图，仅此而已。

目前支持 **TypeScript / JavaScript、Python、Go、Rust**。作为用户，你只需要知道这一句。

## 什么时候*不*用它

老实说：Code Map 没打算取代你的编辑器。如果你正在**写**新代码，你应该在 Explorer 的常规视图里，带着光标和 LSP 类型提示。Code Map 是为了你**写之前**的那一刻 —— 你需要先读懂。

一个有用的分工：

- **文件树 + 编辑器**：你已经知道要改什么、改在哪。
- **Code Map**：问题是"什么调用了什么，我该从哪里下手？"

你会一整天在两者之间来回切。它们看的是同一份文件，回答的是不同的问题。

## 上手

打开 Cockpit → Explorer → 打开任意源文件 → 点 **Code Map** 切换。Chip 视图替换原来的编辑器面板 —— 同一个文件，换一个镜头。点击 callee pin 飞到下一个函数。读完了切回来。

那个你从一月就想读、一直没动的仓库？现在是 5 分钟的 walkthrough。

---

\`npm i -g @surething/cockpit\` · [GitHub](https://github.com/Surething-io/cockpit) · [Try Online](/try)`,
      },
    },
  },
  {
    slug: 'deepseek-in-cockpit',
    date: '2026-04-30',
    keywords: [
      'DeepSeek',
      'deepseek-v4-pro',
      'deepseek-v4-flash',
      'agentic coding',
      'OpenCockpit',
      'Cockpit',
      'AI coding assistant',
      'multi-model',
      'cheap AI coding',
    ],
    content: {
      en: {
        title: 'Use DeepSeek inside Cockpit — and keep all your Claude habits',
        description:
          'Cockpit now talks to DeepSeek. Open a tab, paste a key, and DeepSeek-v4 edits your files, runs your terminal, reviews your diffs — exactly the way you already use Claude. Here is how to set it up in under a minute and what to expect.',
        readingTime: '4 min read',
        body: `If you already use Cockpit with Claude, you have a workflow: open a tab, ask the agent to fix a bug, watch it edit files, run tests, hand you a clean diff. Slash commands like \`/qa\` and \`/fx\` are muscle memory.

Now you can do all of that with **DeepSeek** instead — usually at a fraction of the cost. As of v1.0.195, DeepSeek sits next to Claude in the new-tab menu, and everything you already know how to do works exactly the same.

## Setup: under a minute

1. **Get a key.** Go to [platform.deepseek.com/api_keys](https://platform.deepseek.com/api_keys), create one, copy it.
2. **Open a DeepSeek tab.** Click the \`+\` on the tab bar, pick **DeepSeek**.
3. **Paste the key.** A blue **Set API key** pill appears in the chat header. Click it, paste, hit **Save**.
4. **Send a message.** That's it.

Everything runs locally. Nothing leaves your machine except the request to DeepSeek itself.

## Pick a model from the same pill

Once the key is in, that pill turns into the current model name. Click it again to switch:

- **\`deepseek-v4-pro\`** — the default. Use it for the kind of tasks you'd give Claude Sonnet: refactors, debugging, multi-file edits, writing tests.
- **\`deepseek-v4-flash\`** — faster and cheaper. Great for "do this small thing" tasks: rename a function, write a one-off script, summarize a file.

You can have one tab on \`pro\` and another on \`flash\` at the same time. Each tab remembers its own model.

## What it can do (spoiler: everything)

The whole point of plugging DeepSeek into Cockpit is that **nothing else changes**. In a DeepSeek tab the agent can still:

- **Read and edit files** in your project.
- **Run terminal commands** — install deps, run tests, start a dev server.
- **Search the codebase** with Grep / Glob.
- **Browse the web** — \`WebFetch\` and \`WebSearch\` work the same.
- **Run your slash commands** — \`/qa\` to clarify before coding, \`/fx\` to diagnose a bug, \`/cg\` to explore the project graph.
- **Spawn sub-agents** for parallel research / refactor work.
- **Take screenshots / pasted images** as input.
- **Pick up where you left off** — close the tab, reopen tomorrow, the conversation is right there.

If you've built habits around Cockpit's Claude experience, your habits transfer over wholesale. Same buttons, same shortcuts, same flow.

## A few real workflows worth trying

**The "cheap second opinion".** Open two tabs side-by-side: one Claude, one DeepSeek-v4-pro. Paste the same prompt into both. Compare answers. You'll learn fast which model your codebase prefers — and the comparison itself usually surfaces a better question.

**Bulk grunt work on Flash.** That afternoon of "rename this prop across 40 files, update the storybook stories, regenerate types" — point a Flash tab at it. It's plenty smart for mechanical changes, and noticeably faster.

**\`/fx\` on DeepSeek.** Bug-evidence mode (\`/fx The login modal sometimes flashes empty\`) works particularly well here — the agent reads the failing path, builds a hypothesis, and stays out of your code until you say go.

**Long sessions without the bill anxiety.** Long agent conversations on Claude can get expensive once the context grows. DeepSeek's pricing means you can let a session breathe — keep iterating, keep showing it more files, keep refining — without watching the meter.

## Things kept clean and separated

Two practical promises about how DeepSeek lives next to Claude on your machine:

- **Conversations don't mix.** A DeepSeek session is stored separately from your Claude history. Searching Claude history won't surface DeepSeek replies, and vice versa.
- **Credentials don't leak.** Your Claude login and your DeepSeek key live in different places. One has nothing to do with the other.

You can swap between Claude and DeepSeek tabs all day and never worry about cross-contamination.

## How to start right now

\`\`\`bash
npm i -g @surething/cockpit
cock
\`\`\`

Open the app, pick **DeepSeek** from the new-tab menu, paste your key, ask it to fix something. If it understands your codebase as well as Claude does, the cost per task may surprise you.

---

\`npm i -g @surething/cockpit\` · [GitHub](https://github.com/Surething-io/cockpit) · [Try Online](/try)`,
      },
      zh: {
        title: 'Cockpit 用上 DeepSeek：你的 Claude 习惯一个都不用改',
        description:
          'Cockpit 现在能直接对接 DeepSeek：开个 Tab、贴个 Key，DeepSeek-v4 就能像 Claude 一样改你的文件、跑你的终端、评你的 diff。这篇讲怎么 1 分钟内配好，以及配好之后能做什么。',
        readingTime: '阅读约 4 分钟',
        body: `如果你已经在用 Cockpit + Claude，你已经有了一套工作流：开个 Tab、让 Agent 修个 Bug、看它改文件、跑测试、给你一份干净的 diff；\`/qa\`、\`/fx\` 这些斜杠指令早就是肌肉记忆。

现在你可以把这一整套照搬到 **DeepSeek** 上跑 —— 而且通常便宜很多。从 v1.0.195 开始，DeepSeek 跟 Claude 一起出现在新 Tab 菜单里，你已经会的所有操作都不用改。

## 配置：不到一分钟

1. **拿一个 Key。** 去 [platform.deepseek.com/api_keys](https://platform.deepseek.com/api_keys) 新建一个，复制下来。
2. **开一个 DeepSeek Tab。** Tab 栏点 \`+\`，选 **DeepSeek**。
3. **贴 Key。** 聊天面板顶部会出现一颗蓝色胶囊 **Set API key**，点它，把 Key 粘上去，**Save**。
4. **发消息。** 就这样。

一切都在本地完成。除了发给 DeepSeek 的那次请求，没有任何东西会离开你的电脑。

## 模型在同一颗胶囊里切换

Key 配好之后，那颗胶囊会显示当前模型名。再点它就能切换：

- **\`deepseek-v4-pro\`** —— 默认。给它的活就是你平时给 Claude Sonnet 的活：重构、Debug、多文件改动、写测试。
- **\`deepseek-v4-flash\`** —— 更快、更便宜。适合 "顺手做个小事"：改个函数名、写个一次性脚本、总结一个文件。

你可以一个 Tab 用 \`pro\`、另一个用 \`flash\` 同时开着。每个 Tab 自己记住自己用的模型。

## 它能做什么（剧透：都能）

把 DeepSeek 接进 Cockpit 的核心理由就是：**别的什么都不用变**。在 DeepSeek Tab 里，Agent 一样能：

- **读你的文件、改你的文件**。
- **跑终端命令** —— 装依赖、跑测试、起 dev server。
- **搜代码** —— Grep / Glob 都在。
- **上网** —— \`WebFetch\`、\`WebSearch\` 一样工作。
- **跑你的斜杠指令** —— \`/qa\` 上线前对齐需求、\`/fx\` 排查 Bug、\`/cg\` 探索项目图谱。
- **派生子 Agent** 做并行调研或重构。
- **吃截图 / 粘贴的图片** 作为输入。
- **断线续聊** —— 关掉 Tab，明天再打开，对话还在原地。

如果你已经围绕 Cockpit 的 Claude 体验养出了一整套习惯，这些习惯整个搬过来就行。同样的按钮、同样的快捷键、同样的流程。

## 几个值得一试的真实玩法

**"廉价的二次意见"。** 并排开两个 Tab：一个 Claude、一个 DeepSeek-v4-pro，把同一个 Prompt 粘进去，看两边的答案。你很快会摸出你的代码库更对哪个模型的胃口 —— 而且对比本身往往能让你想出更好的问题。

**Flash 跑批量琐事。** 那种 "把这个 prop 在 40 个文件里改名、顺带更新 storybook、再重新生成 types" 的下午活儿，丢给 Flash Tab。机械改动它够聪明，而且明显更快。

**用 DeepSeek 跑 \`/fx\`。** Bug 证据链模式（\`/fx 登录弹窗有时候会闪一下空白\`）在这里特别好用 —— Agent 读一遍调用路径、给出假设、在你点头之前一行代码都不动。

**长 Session 不再心疼账单。** 在 Claude 上长对话一旦上下文堆起来就开始烧钱。DeepSeek 的定价让你可以放手让 Session 自然展开 —— 多迭代几轮、多塞几个文件给它看、慢慢打磨 —— 不用一直盯着计价器。

## 干净的隔离

两个实际承诺，关于 DeepSeek 在你机器上跟 Claude 怎么共处：

- **对话不串。** DeepSeek 的会话跟 Claude 的历史分开存。在 Claude 历史里搜不到 DeepSeek 的回复，反之亦然。
- **凭据不串。** 你的 Claude 登录和你的 DeepSeek Key 存在不同的地方，互不干涉。

你可以一整天在 Claude Tab 和 DeepSeek Tab 之间来回切，完全不用担心污染。

## 现在就开始

\`\`\`bash
npm i -g @surething/cockpit
cock
\`\`\`

打开应用，从新 Tab 菜单里选 **DeepSeek**，把 Key 粘进去，让它修点东西。如果它对你的代码库的理解能跟 Claude 持平，每个任务的成本可能会让你有点意外。

---

\`npm i -g @surething/cockpit\` · [GitHub](https://github.com/Surething-io/cockpit) · [在线体验](/try)`,
      },
    },
  },
  {
    slug: 'chat-to-skill',
    date: '2026-04-30',
    keywords: [
      'Claude Code skills',
      'SKILL.md',
      'Claude Code custom commands',
      'Claude Code memory',
      'prompt library',
      'AI workflow capture',
      'slash commands',
      'OpenCockpit',
      'Cockpit',
      'team prompt sharing',
      'Anthropic skills',
    ],
    content: {
      en: {
        title: 'From chat to skill: turn yesterday\u2019s 28-minute debug into today\u2019s /command',
        description:
          'Every productive Claude Code session ends with dark knowledge that dies with the conversation. Cockpit\u2019s Skills feature lets the agent crystallize a chat into a SKILL.md \u2014 saved to *your* knowledge base (not a Cockpit-owned folder), then registered as a slash command. Your notes stay where they live; Cockpit just holds the pointer.',
        readingTime: '6 min read',
        body: `Yesterday I spent **28 minutes** walking Claude through our OAuth refresh-token flow. Token endpoint, leeway window, two custom claims, the one staging-only quirk. Bug found, fixed, shipped.

This morning, almost the same problem in a sister service. I open a new chat. The agent has no memory of yesterday. **28 minutes again.**

This isn't an Anthropic limitation. Stateless agents are the right default — you don't want yesterday's wrong assumption haunting tomorrow's session. The fix isn't "give the AI memory". The fix is **give yourself memory, in a place the agent will read again.**

That place is a Skill.

## What a Skill actually is — and where it should live

A Skill is one Markdown file. Nothing more. Cockpit only cares about two things: the file's content (which becomes the system prompt for \`/skill-name\`) and an absolute path that points at it.

The path is the design choice that matters most. Anthropic's reference convention puts skills under \`~/.claude/skills/\`. Cockpit deliberately does **not** do that. **We don't scan your home directory. We don't reserve a folder name. Your skills can live anywhere you want them to live**:

- \`~/Notes/Skills/oauth-debug/SKILL.md\` — alongside your personal notes
- \`~/Documents/team-playbooks/oauth-debug/SKILL.md\` — in a synced folder
- \`~/Work/our-handbook/skills/oauth-debug/SKILL.md\` — inside a git'd team repo
- \`~/Obsidian/Vault/Skills/oauth-debug/SKILL.md\` — inside your Obsidian vault
- \`./.claude/skills/oauth-debug/SKILL.md\` — Anthropic-style, if you prefer

Each skill is its own folder. The Markdown file is named \`SKILL.md\` (Anthropic convention) and the folder name becomes the slash command. Why a folder per skill? Because skills frequently grow companion files — example transcripts, a reference cheat-sheet, a small Python helper the agent calls — and a folder gives them somewhere natural to live.

**Why this matters:** your knowledge already lives somewhere. You have a vault, a notes app, a docs repo, a "Skills" folder you've curated for years. Forcing skills into \`~/.claude/skills/\` would be Cockpit picking a fight with your existing system. We register pointers instead.

### The two-step flow

**Step 1.** Write the SKILL.md in *your* knowledge base. Whatever path you want.

**Step 2.** In Cockpit's Skills sidebar, click **+ Add Skill** and paste the absolute path. Cockpit stores it in \`~/.cockpit/skills.json\` (one file, one list of pointers, easy to back up).

That's it. The skill is now in your slash autocomplete. You can edit the source file directly in your editor of choice — Cockpit watches it, picks up changes on save, no re-import.

What Cockpit then gives you on top:

- **Skills sidebar**: lists every registered skill with name, description, icon, source path, last-used time
- **Slash autocomplete**: \`/\` in chat shows your skills mixed with built-ins like \`/qa\`, \`/fx\`, \`/cg\`
- **Validity checks**: red "Invalid" badge if the source file disappears (you renamed it, moved the vault) — fix the path, skill comes back
- **Preview**: click any skill to render the markdown full-page, or view raw source

## The crystallize loop

The trick isn't writing skills by hand. The trick is **letting the conversation that just succeeded write the skill for you.**

Three messages at the end of every productive session:

**Message 1 — distill**

> Summarize what we just figured out about the OAuth refresh flow.
> Be concrete: file paths, the actual root cause, what we'd check next time.

The agent now has the conversation in its working set. It knows what mattered.

**Message 2 — encode**

> Now write that as a SKILL.md I can drop into my knowledge base
> at \`~/Notes/Skills/oauth-debug/SKILL.md\` (or wherever you keep yours).
>
> Shape: when invoked, it should take a problem statement, walk
> through the same diagnostic steps we used today, in the same order,
> and refuse to suggest a fix until step 4. Keep it under 40 lines.

You'll get something like:

\`\`\`markdown
---
name: oauth-debug
description: Diagnose OAuth refresh-token failures using our debugging playbook.
---

You diagnose OAuth refresh-token failures in our auth stack.

When the user describes a failure, follow these steps in order:

1. Confirm which environment (dev / staging / prod). Staging has a
   60-second leeway window; prod does not.
2. Read \`auth/refresh.ts\` and identify which JWT claims are validated.
   We require both \`sub\` and \`tid\` — never just \`sub\`.
3. Ask for the failing access token (or a redacted version). Decode
   the header and check \`alg\` — must be RS256, never HS256.
4. Check \`auth/keys.ts\` for the JWKS cache TTL. We've shipped 3 bugs
   in 2 years from stale JWKS.
5. Only after the above, propose a fix.

Never edit code until step 5. If the user asks earlier, restate the
remaining diagnostic steps and ask them to choose.
\`\`\`

**Message 3 — save**

> Save it to that path.

If you've given the agent write access to your knowledge base, it'll create the file itself. Otherwise copy-paste from the chat into your editor — same outcome. The file now lives in your notes, indexed by your existing search, version-controlled by your existing git, synced by your existing Dropbox / iCloud / Syncthing. *It is not Cockpit's data.*

**Then register it.** In Cockpit's Skills sidebar, click **+ Add Skill**, paste the absolute path:

\`\`\`
/Users/you/Notes/Skills/oauth-debug/SKILL.md
\`\`\`

(One-time, takes 5 seconds.) From here on, \`/oauth-debug\` shows up in the slash autocomplete in every chat. Edit the source file from anywhere — your editor, another machine, a teammate's PR — and Cockpit picks it up on next file-system event.

## Tomorrow morning

\`\`\`
/oauth-debug Token refresh failing intermittently in staging only.
\`\`\`

The agent enters the **same posture** you trained yesterday. Same checks, same order, same refusal to jump to fixes. Not because it remembers — because **you wrote yesterday down, somewhere it will read again.**

The 28 minutes from yesterday is now a 30-second invocation.

## Three skill shapes that earn the file

Not every conversation deserves a skill. The ones that do tend to fall into three shapes:

**Diagnostic skills** — \`/oauth-debug\`, \`/db-deadlock\`, \`/cors-issue\`, \`/flaky-test\`. Freeze a debugging procedure. The agent gets a checklist instead of guessing.

**Convention skills** — \`/our-pr-style\`, \`/our-test-style\`, \`/our-error-handling\`. Freeze your team's tribal knowledge. New contributor on day one types \`/our-pr-style\` and the agent writes PRs that pass review without 4 rounds of nitpicks.

**Onboarding skills** — \`/our-stack\`, \`/our-deploy-flow\`, \`/where-does-X-live\`. Explain your codebase to a fresh agent. This is the highest-leverage one — every new chat in your repo starts with the right map.

If a conversation doesn't fit one of these, it's probably a one-off. Don't crystallize it.

## Skills as team assets

Because skills are just markdown files at paths *you* choose, the team-asset story falls out for free. Pick a repo your team already trusts:

\`\`\`
~/Work/our-handbook/skills/
├── README.md             # how to write a skill, how to register it
├── oauth-debug/
│   ├── SKILL.md
│   └── examples.md       # optional companion files the skill can reference
├── our-pr-style/
│   └── SKILL.md
└── our-deploy-flow/
    ├── SKILL.md
    └── runbook.md
\`\`\`

Now skills are diffable, reviewable, \`git blame\`-able. The senior engineer's "always do X but never Y" stops being a Slack DM and becomes a PR with a reviewer. New hires \`git pull\`, click **+ Add Skill** three times, and the team's tacit knowledge is in their slash menu before the end of day one.

Cockpit's LAN-shared review surface (see [our previous post](/en/blog/claude-code-gui-comparison/)) makes the inner loop tighter: write a skill in chat, share the review page over LAN, teammate comments line-by-line, send their comments back to the agent as context, agent revises the skill, you commit.

The point: **skills don't make a copy of your team's knowledge. They reference it.** When the handbook updates, the skill updates. There's only one source of truth, and it's already where your team keeps source of truth.

## Meta: a skill that writes skills

Once you have one skill, you can write a meta-skill:

\`\`\`
/distill Read the last 50 messages in this conversation. If you
spot any pattern that repeated 3+ times, propose a SKILL.md for
it. Don't save automatically — show me the draft first.
\`\`\`

Now your Skills sidebar fills itself, slowly, from the conversations you actually have. Cockpit ships \`/qa\`, \`/fx\`, \`/cg\` as opinionated defaults — but the **best skills in your sidebar a year from now will be ones you didn't write by hand.**

## The bigger principle

Stateless agents are correct. They reset between conversations because that's how you avoid yesterday's wrong assumption breaking tomorrow's session.

But your **team** isn't stateless. Your team learns. The question is whether that learning lives in three engineers' heads or in 12 reviewed Markdown files in \`./.claude/skills/\`.

Skills are how you make that choice explicit.

---

\`npm i -g @surething/cockpit\` · [GitHub](https://github.com/Surething-io/cockpit) · [Try Online](/try)`,
      },
      zh: {
        title: '把对话沉淀成技能：让昨天那 28 分钟变成今天的 /命令',
        description:
          '每一次高质量的 Claude Code 对话都会沉淀一堆"暗知识"，默认情况下随对话一起死掉。Cockpit 的 Skills 功能让 Agent 把对话浓缩成一个 SKILL.md —— 存到 *你的* 知识库（而不是 Cockpit 强占的目录），再注册为斜杠指令。你的笔记还住在它本来该住的地方，Cockpit 只是持有那个指针。',
        readingTime: '阅读约 6 分钟',
        body: `昨天我花了**整整 28 分钟**带着 Claude 把我们 OAuth 刷新令牌流程过了一遍。Token 端点、leeway 窗口、两个自定义 claims、那个仅 staging 环境才有的怪癖。Bug 找到、修掉、上线。

今天早上，姊妹服务里几乎一模一样的问题。我打开一个新对话。Agent 对昨天毫无记忆。**又是 28 分钟。**

这不是 Anthropic 的设计缺陷。**无状态 Agent 才是正确的默认值**——你不会希望昨天那个错的假设缠着今天的对话。解法不是"给 AI 记忆"，解法是**给你自己记忆，存在 Agent 明天会读到的地方。**

那个地方就是 Skill。

## Skill 是什么 —— 以及它该住在哪里

一个 Skill 就是一个 Markdown 文件，仅此而已。Cockpit 只关心两件事：文件正文（成为 \`/skill-name\` 的 system prompt）和指向它的绝对路径。

**路径是最重要的设计选择。** Anthropic 的官方约定是把 skills 放在 \`~/.claude/skills/\`。Cockpit **故意不这么做**。**我们不扫你的 home 目录、不预留某个文件夹名、不强占任何位置。你的 skills 想住哪里就住哪里：**

- \`~/Notes/Skills/oauth-debug/SKILL.md\` —— 跟你的个人笔记放一起
- \`~/Documents/team-playbooks/oauth-debug/SKILL.md\` —— 在同步目录里
- \`~/Work/our-handbook/skills/oauth-debug/SKILL.md\` —— 在团队 git 仓库里
- \`~/Obsidian/Vault/Skills/oauth-debug/SKILL.md\` —— 在你的 Obsidian 库里
- \`./.claude/skills/oauth-debug/SKILL.md\` —— 偏好 Anthropic 风格也行

每个 skill 是独立的子文件夹，里面放一个 \`SKILL.md\`（Anthropic 约定），文件夹名就是斜杠命令名。为什么一个 skill 一个文件夹？因为 skill 经常会带"伙伴文件" —— 示例对话、参考小抄、一个被 agent 调用的 Python 辅助脚本 —— 文件夹给它们一个自然的家。

**为什么这事重要：** 你的知识本来就住在某个地方。你有 vault、有笔记软件、有文档仓库、有自己经营多年的"Skills"目录。强迫 skills 进 \`~/.claude/skills/\` 等于让 Cockpit 跟你已有的体系打架。我们选择**只注册指针，不搬运文件。**

### 两步流

**第 1 步：** 在 *你的* 知识库里写 SKILL.md，路径随你定。

**第 2 步：** 在 Cockpit 的 Skills 侧边栏点 **+ Add Skill**，粘贴绝对路径。Cockpit 把它存进 \`~/.cockpit/skills.json\`（一个文件、一份指针列表，备份方便）。

完事。这个 skill 现在出现在你的斜杠补全里。源文件你想用什么编辑器改都行 —— Cockpit 监听文件变化、保存即生效，无需重新 import。

在此基础上，Cockpit 给你的额外能力：

- **Skills 侧边栏**：每个已注册技能的名称、描述、图标、源路径、最后使用时间
- **斜杠补全**：在对话里打 \`/\`，你的技能跟内置 \`/qa\`、\`/fx\`、\`/cg\` 混排出现
- **有效性检测**：源文件不见了（你改名、移动了 vault），红色 "Invalid" 徽标提醒你 —— 改路径就能恢复
- **预览**：点任意技能可以全屏渲染 markdown 或查看原始源码

## 沉淀循环（真正的工作流）

诀窍不是手写 skills。诀窍是**让刚刚奏效的对话自己写 skill 给你。**

每一次成功的对话末尾，加三句话：

**第 1 句：浓缩**

> 总结一下我们刚才搞清楚的 OAuth 刷新流程。
> 要具体：文件路径、真正的根因、下次该先查什么。

Agent 现在把整段对话拉到了工作集里。它知道什么是关键。

**第 2 句：编码**

> 把它写成一个 SKILL.md，我要放进我的知识库
> \`~/Notes/Skills/oauth-debug/SKILL.md\`（或者你自己习惯的位置）。
>
> 形态：被调用时接收一个问题陈述，按今天的同样顺序走完同样的诊断步骤，
> 第 4 步之前不许提修复方案。控制在 40 行以内。

你会拿到类似这样的输出：

\`\`\`markdown
---
name: oauth-debug
description: 用我们的诊断剧本排查 OAuth 刷新令牌失败。
---

你负责诊断我们鉴权栈中的 OAuth 刷新令牌失败问题。

当用户描述故障时，按以下顺序执行：

1. 确认是哪个环境（dev / staging / prod）。Staging 有 60 秒的 leeway
   窗口，prod 没有。
2. 读 \`auth/refresh.ts\`，找出验证了哪些 JWT claims。我们要求同时
   存在 \`sub\` 和 \`tid\`，绝不能只验 \`sub\`。
3. 索取失败的 access token（或脱敏版）。解码 header 检查 \`alg\`，
   必须 RS256，永远不允许 HS256。
4. 看 \`auth/keys.ts\` 里 JWKS 的缓存 TTL。两年里有 3 个 bug 来自
   过期的 JWKS 缓存。
5. 只有走完上面 4 步，才允许提修复方案。

第 5 步前绝不修改代码。如果用户提前要修，复述剩下的诊断步骤让用户选。
\`\`\`

**第 3 句：保存**

> 存到那个路径。

如果你给了 Agent 对你知识库目录的写权限，它会自己建文件。否则从对话里复制粘贴到你的编辑器 —— 效果一样。文件现在住在你的笔记里、被你已有的搜索索引、被你已有的 git 版本化、被你已有的 Dropbox / iCloud / Syncthing 同步。**它不是 Cockpit 的数据。**

**然后注册它。** 在 Cockpit 的 Skills 侧边栏点 **+ Add Skill**，粘贴绝对路径：

\`\`\`
/Users/you/Notes/Skills/oauth-debug/SKILL.md
\`\`\`

（一次性，5 秒钟。）从此 \`/oauth-debug\` 出现在所有对话的斜杠补全里。源文件你在哪儿改都行 —— 你的编辑器、另一台机器、队友的 PR —— Cockpit 在下一次文件系统事件时自动捡起来。

## 明天早上

\`\`\`
/oauth-debug 刷新令牌只在 staging 偶发失败。
\`\`\`

Agent 自动进入你昨天训练过的**同款姿态**。同样的检查、同样的顺序、同样的"先别急着修"。不是因为它记得——而是**你把昨天写下来了，写在了它会再读的地方。**

昨天的 28 分钟，今天浓缩成一次 30 秒的调用。

## 值得"立此存照"的三种 skill 形态

不是每段对话都值得做成 skill。值得的那些通常是这三类：

**诊断型** —— \`/oauth-debug\`、\`/db-deadlock\`、\`/cors-issue\`、\`/flaky-test\`。把一套排查流程冻结成 checklist，Agent 不用再瞎猜。

**约定型** —— \`/our-pr-style\`、\`/our-test-style\`、\`/our-error-handling\`。把团队的部落知识冻结下来。新人入职第一天敲 \`/our-pr-style\`，Agent 写出来的 PR 一次过 review，不用 4 轮 nitpick。

**Onboarding 型** —— \`/our-stack\`、\`/our-deploy-flow\`、\`/where-does-X-live\`。给新 Agent 解释你的代码库。**这一类杠杆最高**——每一次新对话都从一张正确的地图开始。

不属于这三类的对话，多半是一次性的，不必沉淀。

## Skill 作为团队资产

正因为 skill 只是 *你选定路径下* 的 markdown 文件，团队资产这一层几乎是白送的。挑一个团队已经信任的仓库就行：

\`\`\`
~/Work/our-handbook/skills/
├── README.md             # 怎么写技能、怎么注册到 Cockpit
├── oauth-debug/
│   ├── SKILL.md
│   └── examples.md       # 可选：技能可以引用的伙伴文件
├── our-pr-style/
│   └── SKILL.md
└── our-deploy-flow/
    ├── SKILL.md
    └── runbook.md
\`\`\`

Skill 因此可以 diff、可以 review、可以 \`git blame\`。Senior 那句"始终做 X、绝不做 Y"不再是一条 Slack 私信，而是一个有 reviewer 的 PR。新人 \`git pull\` 完，点 3 下 **+ Add Skill**，团队的隐性知识在他第一天结束前就出现在斜杠菜单里。

Cockpit 的局域网共享评审页（参见[上一篇博客](/zh/blog/claude-code-gui-comparison/)）让内循环更紧：在对话里写出 skill、把评审页面分享到局域网、队友逐行评论、把评论喂回 Agent 作为上下文、Agent 修订、你 commit。

关键是：**Skill 不是把团队的知识复制了一份，而是引用了它。** 当 handbook 更新，skill 就更新。**只有一个事实来源**，而且就是你团队本来存放事实的地方。

## 进阶：用 skill 写 skill

有了第一个 skill，下一步可以写一个 meta-skill：

\`\`\`
/distill 读这次对话最近 50 条消息。如果发现任何重复出现 3+ 次的
模式，给我提一个 SKILL.md 草案。先别自动保存，给我看草案再说。
\`\`\`

从此你的 Skills 侧边栏会**自己慢慢长出来**，从你真实进行的对话里长出来。Cockpit 内置的 \`/qa\`、\`/fx\`、\`/cg\` 是有主见的默认值——但**一年后你侧边栏里最好用的那些 skill，多半不是你手写的。**

## 背后的原则

无状态 Agent 是对的。它在每段对话之间重置自己，因为这样昨天那个错的假设才不会污染今天的对话。

但你的**团队**不是无状态的。团队会学习。问题只是：那些学习到的东西，是住在三位工程师的脑子里，还是住在 \`./.claude/skills/\` 下 12 个被 review 过的 Markdown 文件里。

**Skills 就是把这个选择显式化的方式。**

---

\`npm i -g @surething/cockpit\` · [GitHub](https://github.com/Surething-io/cockpit) · [在线体验](/try)`,
      },
    },
  },

  {
    slug: 'parallel-claude-code-sessions',
    date: '2026-04-29',
    keywords: [
      'Claude Code',
      'parallel Claude Code',
      'multi-project AI',
      'Claude Agent SDK',
      'OpenCockpit',
      'Cockpit',
      'AI coding workflow',
    ],
    content: {
      en: {
        title: 'How to run 5 Claude Code sessions in parallel without losing your mind',
        description:
          'Claude Code is incredible at one task at a time — but most engineers want to scope out three features while one is refactoring and another is writing tests. Here is how Cockpit lets you run multiple Claude Code Agent SDK sessions across projects at once, without context-switching pain.',
        readingTime: '6 min read',
        body: `Most Claude Code users hit the same wall after a week:

> *"Once I have 3 projects on the go, my terminal is chaos."*

You spawn one \`claude\` session in project A. Spin up another in project B. Tab back. Forget which one is which. Re-paste your context twice. Your shell scrollback eats half the conversation. Eventually you give up and serialize — one project at a time — and AI productivity collapses to "single-threaded human".

This is exactly the problem **Cockpit** was built to fix.

## The mental model: one cockpit, many flights

Think of each Claude Code session as a flight. With raw \`claude\` CLI you are flying one plane at a time. **Cockpit puts every flight on a dashboard with named tabs, status badges, and notifications.**

Internally each session is a separate Claude Agent SDK process — fully isolated, with its own working directory, its own conversation, its own token budget. Your laptop is the air traffic controller; the AI is the pilot.

## Setting up parallel sessions

Install once:

\`\`\`bash
npm i -g @surething/cockpit
cockpit           # starts the cockpit at http://localhost:3457
\`\`\`

Open three projects:

\`\`\`bash
cockpit ~/work/api-server
cockpit ~/work/web-app
cockpit ~/work/data-pipeline
\`\`\`

Each \`cockpit <dir>\` adds a project tab to the same cockpit. Switching between them is one swipe / one keypress — no terminal juggling. *(The short \`cock\` alias works everywhere too — same command, fewer letters.)*

Inside each project you can spawn multiple Agent sessions. Common pattern:

| Tab | Session 1 | Session 2 |
|---|---|---|
| api-server | Refactor auth middleware | Write tests for refactor |
| web-app | Implement settings page | |
| data-pipeline | Investigate the prod-export bug | |

Each session runs concurrently. When any of them finishes (or asks a question), you get a desktop notification + a red-dot badge on the project tab.

## Why this is more than four terminal tabs

Three reasons it beats raw \`tmux\` / iTerm splits:

1. **Notifications you can trust.** Cockpit knows when an agent has actually paused for input vs. when it's still working. A red dot only shows up when *you* are the bottleneck.
2. **Cross-project session browser.** Cmd+K opens a flat list of every running and recent session across every project. "What was that thing I was debugging yesterday?" → one keystroke away.
3. **Shared shell + bubbles.** Each project gets its own xterm.js terminal, plus optional Browser / PostgreSQL / MySQL / Redis bubbles. The agent can drive any of them. So your "test the new auth flow in Chrome" task doesn't need a separate window.

## Cost: yes, you'll burn more tokens

Be honest about this. Running 5 sessions in parallel means up to 5× token spend. Two ways to keep it sane:

- Reserve cheap models for "always-on" sessions (e.g. \`/qa\` clarification mode), reserve Sonnet/Opus for the deep work tab.
- Use \`/qa\` (clarify-only) and \`/fx\` (diagnose-only) modes generously — they don't write code, so they don't compound.

## What "20× productivity" actually means

We don't actually believe in 20× productivity from AI. What we *do* believe is that AI agents are now I/O-bound on **you, the human**. Every minute you spend re-pasting context, switching terminals, or re-explaining what file you meant is a minute of agent idle time.

A cockpit is just an interface that respects how much I/O bandwidth a human has. Five quiet agents finishing tasks in the background, three coming back to you with questions in priority order — that's the actual upside.

---

**Try it:** \`npm i -g @surething/cockpit\` · [GitHub](https://github.com/Surething-io/cockpit) · [Try Online](/try)`,
      },
      zh: {
        title: '如何同时跑 5 个 Claude Code 会话不疯掉',
        description:
          'Claude Code 一次干一件事很强，但工程师真实场景常是：一个项目重构、一个项目写测试、一个项目排 bug，外加两个新需求脑暴。直接用裸 `claude` CLI 很快就会卡在终端切换上。这篇讲 Cockpit 是怎么用 Claude Agent SDK 把多项目并发会话跑顺的。',
        readingTime: '阅读约 6 分钟',
        body: `用了一周 Claude Code，多数人都会撞上同一个瓶颈：

> *"3 个项目同时进行的时候，我的终端就乱了。"*

A 项目里跑一个 \`claude\`。B 项目里再开一个。切回来、记不清哪个 tab 是哪个、重新粘贴上下文两遍、scrollback 吃掉了一半对话。最后你只能放弃，串行处理 —— 一次只搞一个项目 —— AI 生产力瞬间退化成"单线程人类"。

这正是 **Cockpit** 想解决的问题。

## 心智模型：一个驾驶舱，多个航班

把每一个 Claude Code 会话想成一架飞机。裸用 \`claude\` CLI 时你只能开一架。**Cockpit 把每架飞机摆到一个仪表盘上，有命名 tab、状态徽标和通知。**

内部每个会话都是一个独立的 Claude Agent SDK 进程 —— 工作目录、对话历史、Token 预算彼此完全隔离。你的笔记本是塔台，AI 是飞行员。

## 配置并发会话

安装一次：

\`\`\`bash
npm i -g @surething/cockpit
cockpit           # 启动驾驶舱，http://localhost:3457
\`\`\`

打开三个项目：

\`\`\`bash
cockpit ~/work/api-server
cockpit ~/work/web-app
cockpit ~/work/data-pipeline
\`\`\`

每个 \`cockpit <dir>\` 都会在同一个驾驶舱里加一个项目标签。项目间切换一滑动 / 一快捷键 —— 不再切终端。*（短别名 \`cock\` 同样可用 —— 同一条命令，少打几个字母。）*

每个项目内可以再开多个 Agent 会话。常见组合：

| 项目 | 会话 1 | 会话 2 |
|---|---|---|
| api-server | 重构鉴权中间件 | 给重构补测试 |
| web-app | 实现设置页 | |
| data-pipeline | 排查导出生产数据的 bug | |

所有会话并发执行。任意一个完成或提问时，你会收到桌面通知 + 项目标签的红点徽标。

## 它比开 4 个终端 tab 强在哪

三个理由：

1. **通知可信。** Cockpit 知道 Agent 是真停下等你回复，还是在干活。红点只在 *你* 成为瓶颈的时候出现。
2. **跨项目会话浏览。** Cmd+K 打开一个平铺列表，所有运行中 + 最近的会话一览无遗。"昨天我在调的那个东西去哪了？" —— 一个快捷键就能找回来。
3. **共享终端 + 气泡。** 每个项目有自己的 xterm.js 终端，外加可选的浏览器 / PostgreSQL / MySQL / Redis 气泡。Agent 都能驱动它们。"在 Chrome 里验证新登录流程"这种任务不用额外开窗。

## 代价：Token 会烧得多

实话实说。并行跑 5 个会话意味着 5 倍 Token 消耗。两个手段控制：

- 给"常驻"会话用便宜模型（比如 \`/qa\` 澄清模式），把 Sonnet / Opus 留给主力 tab。
- 大量使用 \`/qa\`（只澄清）、\`/fx\`（只诊断）模式 —— 它们不写代码、不会复利地烧 Token。

## "20× 效率"到底是什么意思

我们不真信"AI 带来 20× 效率"。我们相信的是：**AI Agent 已经被你这个人类的 I/O 卡住了。** 每一分钟你花在重新粘贴上下文、切终端、重解释"我说的是哪个文件"，都是 Agent 的空闲分钟。

驾驶舱不过是一个尊重"人类 I/O 带宽"的界面。五个安静的 Agent 在后台干活，三个按优先级排队回来问你 —— 这才是真正的提升点。

---

**试试看：** \`npm i -g @surething/cockpit\` · [GitHub](https://github.com/Surething-io/cockpit) · [在线体验](/try)`,
      },
    },
  },

  {
    slug: 'claude-code-gui-comparison',
    date: '2026-04-29',
    keywords: [
      'Claude Code GUI',
      'Claude Code desktop',
      'Cursor alternative',
      'Continue alternative',
      'Aider alternative',
      'Claude Code client',
      'AI IDE comparison',
    ],
    content: {
      en: {
        title: 'Claude Code GUI: CLI vs Cockpit vs IDE plugins (2026 buyer’s guide)',
        description:
          'Anthropic ships Claude Code as a CLI by default. If you want a GUI on top, you have four real options today: stick with the CLI, use an IDE plugin (Cursor, Continue), use Aider in a TUI, or run Cockpit. Here is when each one wins.',
        readingTime: '7 min read',
        body: `Anthropic ships Claude Code as a **CLI**. That decision is correct for power users — terminals are scriptable, composable, and don't crash. But it pushes a non-trivial chunk of "obvious wins" onto the user: history search, multi-project tab management, image attachments, in-context code review, embedded terminals.

This post is an honest comparison of the four ways most engineers actually use Claude Code in 2026.

## Option A: stay in the raw CLI

**When it wins:** scripts, CI, one-off refactors, headless servers.

The CLI is the source of truth. Everything else wraps around it. If you live in tmux + Vim and have muscle memory for shell pipes, the CLI is faster than any GUI for short tasks. Anthropic also keeps the CLI on the absolute leading edge — every new SDK feature lands here first.

**Where it hurts:** as soon as you have more than one Claude Code session active, you're in tmux territory. There's no built-in notion of "session inbox" or red-dot. Image attachment is awkward. Cross-project history is a \`grep\` exercise.

## Option B: an IDE plugin (Cursor / Continue / Cline / Roo)

**When it wins:** you mostly edit code in one editor, in one project at a time.

Cursor in particular is a fantastic experience for the *single-file, single-project* loop. The autocomplete is integrated into the cursor (literally), the diff UX is smooth, and you can chat with your project without leaving the editor.

**Where it hurts:**
- Multi-project parallelism is the editor's "open multiple windows" feature, which is exactly the chaos Cockpit was built to fix.
- The agent doesn't easily reach into your terminal, browser, or database.
- You're tied to the editor's update cadence. Want a new Anthropic feature on day 1? You wait.

## Option C: Aider / TUI tools

**When it wins:** you want a chat-driven coding loop without leaving the terminal, but with better history than raw CLI.

Aider is great. It's older, more opinionated about commits, and a good fit for solo OSS work.

**Where it hurts:** still single-project at a time, still terminal-only, still no native multi-modal (browser, DB).

## Option D: Cockpit (a full GUI on top of the official Agent SDK)

**When it wins:**
- You manage 2+ projects in flight every day.
- You want notifications, red dots, and a real "session inbox".
- Your work isn't just code — it touches a browser, a Postgres DB, or a Redis cache, and you'd like the agent to drive those too.
- Your team reviews code together, and you want a shared review surface that doesn't need a SaaS.

**Where it hurts:**
- It's young (v1.0.x). You'll find rough edges.
- It runs locally — there's no cloud sync (yet). Move between machines = re-clone projects.
- You still need Claude Code installed and configured. Cockpit doesn't replace the CLI, it stands on top of it.

## A side-by-side

| | Raw CLI | IDE plugin | Aider | **Cockpit** |
|---|---|---|---|---|
| Multi-project parallel | ❌ tmux required | ❌ multi-window | ❌ | ✅ first-class |
| Cross-project search | grep | per-window | local | ✅ Cmd+K |
| Browser / DB control | ❌ | usually ❌ | ❌ | ✅ Bubbles |
| Code review surface | git tools | PR provider | git | ✅ LAN-shared |
| Slash modes | manual | per-plugin | yes | ✅ \`/qa /fx /cg\` + custom |
| Local-only / no cloud | ✅ | varies | ✅ | ✅ |
| Day-1 SDK features | ✅ | wait | varies | ✅ (uses official SDK) |
| Open source | ✅ | mostly ❌ (Cursor) | ✅ | ✅ MIT |

## How to pick

- **Solo, one repo at a time, mostly editor-bound:** Cursor or your IDE of choice. Stop reading.
- **Solo, terminal-bound, want chat-driven coding:** Aider or raw CLI.
- **Multiple projects in flight, or your work crosses code+browser+DB:** Cockpit.
- **Team that wants a shared review surface without buying a SaaS:** Cockpit (the LAN-share review page is the single feature that justifies it on its own).

The strongest argument *against* Cockpit is also the simplest: if your day is "open one project, do one thing, close laptop", you don't need a cockpit. You need a yoke.

---

Want to try? \`npm i -g @surething/cockpit\` · [GitHub](https://github.com/Surething-io/cockpit)`,
      },
      zh: {
        title: 'Claude Code GUI 全景对比：CLI、Cursor、Aider 还是 Cockpit？（2026 选型指南）',
        description:
          'Anthropic 默认把 Claude Code 当 CLI 发布。要 GUI，2026 年其实就四条路：留在 CLI、用 IDE 插件（Cursor、Continue）、用 Aider TUI、或者上 Cockpit。这篇讲清楚每条路什么时候赢。',
        readingTime: '阅读约 7 分钟',
        body: `Anthropic 把 Claude Code 默认做成 **CLI**。这个选择对硬核玩家是对的 —— 终端可脚本、可组合、不容易崩。但它把一堆"明显该有"的能力推给了用户去自补：历史搜索、多项目 tab 管理、图片附件、嵌入式代码评审、内置终端。

这篇文章是 2026 年大家实际怎么用 Claude Code 的诚实对比。

## 方案 A：留在裸 CLI

**赢在：** 脚本、CI、一次性重构、无头服务器。

CLI 是真理之源。其他一切都是包在它外面的壳。如果你住在 tmux + Vim 里，对 shell 管道有肌肉记忆，那么短任务上 CLI 比任何 GUI 都快。Anthropic 还把 CLI 放在最前沿 —— 每个新 SDK 能力都先到这。

**痛点：** 一旦同时有 2 个以上的 Claude Code 会话，就要回到 tmux 那一套。没有"会话收件箱"、没有红点提示。图片附件麻烦。跨项目搜索靠 \`grep\`。

## 方案 B：IDE 插件（Cursor / Continue / Cline / Roo）

**赢在：** 你主要在一个编辑器里、一次只做一个项目。

Cursor 在 *单文件、单项目* 循环里体验极佳。补全直接缝在光标里、diff UX 流畅、不离编辑器就能跟项目聊天。

**痛点：**
- 多项目并行 = 多开窗口，正是 Cockpit 想解决的乱。
- Agent 不太容易够到你的终端、浏览器、数据库。
- 你被编辑器的更新节奏绑死。想要 Anthropic 第 1 天的新能力？等吧。

## 方案 C：Aider / TUI 工具

**赢在：** 你想在终端里跑对话式编码循环，但比裸 CLI 多一些历史管理。

Aider 很好。老牌、对 commit 有自己的脾气，适合个人 OSS 项目。

**痛点：** 还是单项目、纯终端、没有原生多模态（浏览器、DB）。

## 方案 D：Cockpit（官方 Agent SDK 上的完整 GUI）

**赢在：**
- 你每天同时跟进 2+ 个项目。
- 你想要通知、红点、真正的"会话收件箱"。
- 你的工作不只是代码 —— 还涉及浏览器、Postgres、Redis，希望 Agent 也能驱动它们。
- 你的团队需要一起做 review，想要一个不用上 SaaS 的共享评审面。

**痛点：**
- 还很年轻（v1.0.x）。会有粗糙的地方。
- 纯本地运行 —— 暂时没有云同步。换机器 = 重新 clone 项目。
- 仍然需要装好 Claude Code。Cockpit 不替代 CLI，是站在 CLI 上面。

## 对比表

| | 裸 CLI | IDE 插件 | Aider | **Cockpit** |
|---|---|---|---|---|
| 多项目并行 | ❌ 需要 tmux | ❌ 多窗口 | ❌ | ✅ 一等公民 |
| 跨项目搜索 | grep | 各窗口独立 | 本地 | ✅ Cmd+K |
| 浏览器 / DB 控制 | ❌ | 通常 ❌ | ❌ | ✅ Bubbles |
| 代码评审面 | git 工具 | PR 平台 | git | ✅ 局域网共享 |
| 斜杠模式 | 手动 | 各插件 | 有 | ✅ \`/qa /fx /cg\` + 自定义 |
| 纯本地 / 不上云 | ✅ | 不一定 | ✅ | ✅ |
| 新 SDK 能力第一天可用 | ✅ | 等 | 不一定 | ✅（用官方 SDK） |
| 开源 | ✅ | 多数 ❌（Cursor）| ✅ | ✅ MIT |

## 怎么选

- **独立开发者，单仓为主，重度编辑器派：** Cursor 或你顺手的 IDE，文章读到这就够了。
- **独立开发者，终端派，想要对话式编码：** Aider 或裸 CLI。
- **同时跟进多项目，或工作横跨代码 + 浏览器 + 数据库：** Cockpit。
- **团队想要一个共享评审面，但不想买 SaaS：** Cockpit（局域网共享评审页这一项就够买单了）。

反对 Cockpit 最强的论点也最朴素：**如果你一天就是"打开一个项目、干一件事、合电脑"，你不需要驾驶舱，你需要的是一根操纵杆。**

---

想试？\`npm i -g @surething/cockpit\` · [GitHub](https://github.com/Surething-io/cockpit)`,
      },
    },
  },
];

export function getPostBySlug(slug: string): Post | undefined {
  return posts.find((p) => p.slug === slug);
}

export function getAllSlugs(): string[] {
  return posts.map((p) => p.slug);
}
