我在静态页面里藏了一个 AI 编程 Agent

https://elevenbeans.me - 一个纯静态的个人 profile 页面。有头像、有履历、有个暗色模式,平平无奇。

直到你按下 Ctrl+`。

终端来了。一个复古的绿色黑底 shell,带文件系统模拟 —— lscdopen,能浏览我的履历目录。谁还没个装逼的小心思呢对吧。

但故事没完。

如果你在终端里输入 codexclaude 或者 opencode,终端收起来,取而代之的是一个 AI 编程 Agent

一个纯前端、零后端、零 LLM 依赖的 AI Agent 模拟器。长得像 OpenCode TUI,会显示 bash 命令执行、 read 文件、 edit 代码的 tool call 卡片,带打字机回复。

最搞笑的结果:你问 “open the pod bay doors”,它回 “I’m sorry, Dave. I’m afraid I can’t do that.” 然后跑一个 pod_bay_door --status 的 bash 卡片。

为什么做这个

我一直觉得,好的个人网站应该有点彩蛋。不是那种”右键查看源代码”级别的隐藏文字,而是真的藏一个东西,让人意外一下。

去年 AI coding 爆发之后,天天在终端里跟 Agent 对话。那个界面本身就很有辨识度 —— 卡片式的 tool call、waiting animation、markdown 回复。我心想:这个交互形式本身就挺酷的,能不能复刻一个藏在个人主页里?

需求很简单:零依赖、纯前端、一个 HTML 搞定

怎么做的

终端模拟器

终端本身是一个早就有的功能。用一个 overlay div 模拟 CRT 显示器,font-family: monospace,绿色 #00ff41 文字,黑色背景,加上 @keyframes blink 的光标闪烁。

文件系统是个嵌套的 JavaScript 对象:

1
2
3
4
5
6
7
8
const fileSystem = {
'home': {
'elevenbeans': {
'Experience': { '.tag': 'dir', ... },
'Projects': { '.tag': 'dir', ... },
},
},
};

lscdopen 命令遍历这个对象。简单、直白、不用任何库。

从终端到 Agent 的切换

关键代码就几行。拦截 codexclaudeopencode 这三个命令,关掉终端 overlay,打开 Agent overlay:

1
2
3
4
5
const termCmds = {
codex() { closeTerminal(); openCodeAgent('codex'); },
claude() { closeTerminal(); openCodeAgent('claude'); },
opencode() { closeTerminal(); openCodeAgent('opencode'); },
};

Agent 模拟器

Agent 的核心是一个 agentResponses 数组。每个 entry 包含:

  • keywords:触发的关键词列表
  • tools:预定义的 tool call 卡片(bash/edit/read)
  • respond():返回回复文本的函数
  • afterTools():可选,在 tool call 之后追加额外卡片

输入 “hello”,它回一句问候。输入 “run tests”,它展示 5 个通过的测试用例。输入 “fix something”,它 read 一个文件然后 edit。输入 “deploy”,它执行 build、create PR、push 一条龙。

匹配逻辑是简单的 input.includes(keyword) 遍历。命中第一个就返回。没有命中任何关键词就走随机 fallback。

1
2
3
4
5
6
function findAgentResponse(input) {
for (const entry of agentResponses) {
if (entry.keywords.some(k => lower.includes(k))) return entry;
}
// fallback
}

没有 NLP、没有 embedding、没有 LLM。纯 includes

Tool Call 卡片渲染

每个 tool call 渲染一个带图标和内容的卡片:

1
2
3
4
5
6
7
<div class="agent__tool-header bash">
<span class="agent__tool-icon"></span>
<span class="agent__tool-title">Run bash</span>
<span class="agent__tool-arrow"></span>
</div>
<div class="agent__tool-output">$ npm test</div>
<div class="agent__tool-output">PASS ... 5 passed</div>

CSS 用了 GitHub Dark 配色的变体,#0d1117 背景,#c9d1d9 文字,tool call 卡片用 #21262d 背景加 #30363d 边框。

必须避的一个坑

Agent header 有一个 class agent 用来区分用户和 agent 消息的颜色。但它恰好和 Agent overlay 的主容器 class 重名了。

CSS 里 .agent 容器设了 height: 85vh; max-height: 680px; border; overflow: hidden

于是每个消息 header 都继承了 680px 高度和 overflow hidden。消息内容被裁掉了,看起来像坏了。

解法:把消息 header 的 class 从 .user/.agent 改成 .u/.a

花了好久 (不得不人工介入将 model 从 Flash 切成了 Pro) 才定位到这个问题。

最后

这个 Agent 完全是个玩具。它不会真的写代码,不会真的跑测试,所有的 tool call 输出都是预定义的。

但它反应了一个趋势:AI Agent 的交互范式正在变成一种新的 UI pattern。 Tool call 卡片、thinking animation、markdown 回复——这些正在成为用户熟悉的界面语言。

五年后回头看,也许”跟 AI 对话”会和”拖动鼠标”一样自然。

到时候这个彩蛋就是一个时代切片。

代码在 https://github.com/elevenbeans/myprofile - 试试在 elevenbeans.me 按下 Ctrl+Shift+`。