造了个 AI 编程工具的统一入口:kshell(Go + Wails,开源)

一个轻量级 agent 工作台:自动发现你本机装的所有 AI 编程 CLI,把它们的散落会话、工作区、终端和远程服务器收进一个界面。 Go 1.23+ / Wails v2 / React 19 / Bubbletea,Windows · macOS · Linux,MIT 协议。

一、先说说我为什么想造这个工具

不知道你有没有这种感觉------过去一年我装了太多 AI 编程工具。

Claude Code、Codex CLI、Gemini CLI、Cursor、OpenCode,还有些公司内部封装的。它们每一个都有自己的脾气:

  • 会话记录散落在完全不同的目录,用完全不同的格式存着;
  • 有的存 JSONL,有的干脆存进 SQLite;
  • 想恢复一个三天前的会话?先猜猜它写到哪个文件里去了;
  • 我有 12 个项目在跑不同的工具,切一次要开三个终端窗口。

某天我算了一下:我花在"找会话"上的时间,比花在"写代码"上的还多。

市面上的工具要么是"再实现一个 AI 对话协议",要么是绑定死一个生态。而我想要的其实特别朴素:

别再让我记路径了。扫我的磁盘,把所有工具的会话列出来,让我一键回去。

于是有了 kshell。

二、核心设计:发现 → 选择 → 交付

kshell 的定位只有三个动词:

objectivec 复制代码
发现(Discovery)  →  扫磁盘,找出已装工具 + 工作区 + 历史会话
选择(Selection)  →  统一的列表 / 网格视图,跨工具横向比较
交付(Delivery)   →  把会话交还给原生 CLI 继续跑

第三步是刻意的设计选择,值得展开讲。

为什么不自己实现对话协议?

Claude Code 有自己的 ACP、Codex 有自己的 resume 参数、Cursor 又是一套。每家都在长出自己的方言。

kshell 的立场是:不重复实现对话能力,把会话交还给工具自己的 CLI。

  • 终端路径 :点击会话 → 拼出 claude --resume <id> 或 codex resume <id>,扔进 ConPTY/PTY 跑原生交互界面,退出后回到 kshell;
  • 聊天路径(ACP) :对已接入 ACP 的工具(Claude Code、Cursor、CodeBuddy)走 Agent Client Protocol,在窗口内直接对话;工具不支持 ACP 时自动回退到终端。

这样做的收益是显而易见的:kshell 不追着各家协议跑,工具升级了它大概率还活着。而且工具本身的新特性(新的 diff 算法、新的工具调用能力)你第一时间就能用上。

三、架构:一条单向的数据流

复制代码
providers  →  discovery  →  ui / desktop
(识别工具、解析会话)  (扫描与缓存)  (呈现)

整个后端只有 217 个 Go 文件、约 3.4 万行代码,模块划分很克制:

模块 职责 代码量
internal/desktop Wails 绑定层(58 个文件,桌面端最大头) ~10000 行
internal/providers 各工具适配 + Provider 接口 ~5300 行
internal/discovery 扫描与缓存 ~2400 行
internal/terminal ConPTY/PTY 终端后端 ~2000 行
internal/remote SSH 连接存储与候选扫描 ~1900 行
internal/ui Bubbletea TUI 三视图 ~2500 行
internal/acp / internal/chat Agent Client Protocol 客户端 ~2600 行

3.1 Provider 接口:加新工具不用改代码

这是整个项目最想拿出来讲的设计。

scss 复制代码
type Provider interface {
    ID() string
    DisplayName() string
    DetectSpec(home string) DetectSpec
    SessionRoots(home string) []string
    SessionFilePattern() string
    ParseSession(path string, head []byte) (*Session, error)
    NewSessionCmd(ws string, bin string) Launch
    ResumeCmd(s Session, bin string) Launch
}

实现这个接口,你的工具就被 kshell 纳管了。但内置四工具是硬编码的------因为每家的检测与 resume 都有历史包袱,配置表达不了。

真正有意思的是那些可选接口,它们解决了"文件遍历这套抽象不够用"的现实:

go 复制代码
// 工具的 TUI 要跟随 kshell 的浅/深主题
type Themer interface { ThemeArgs(...) ([]string, map[string]string, string) }

// 「会话不在文件里」------比如 opencode 把会话存进了 SQLite
type SessionEnumerator interface {
    EnumerateSessions(home string, bin string) ([]Session, error)
}

// glob 盖不住的深层文件,比如 CodeBuddy 的 subagents/*.jsonl
type PathMatcher interface {
    MatchSessionRel(rel string) bool
}

SessionEnumerator 是我最满意的一个。 opencode 新版把 JSON 会话迁进了 ~/.local/share/opencode/opencode.db,文件遍历这套假设直接失效。kshell 的处理不是加特例,而是让 provider 自己声明"我能枚举":

csharp 复制代码
opencodeSessionsSQL = `select id, directory as cwd, title, time_created as created, time_updated as updated from session where parent_id is null and time_archived is null order by time_updated desc`

⚠️ 这里有个坑,我自己也踩了:这条 SQL 必须写成单行。 Windows 上 opencode 是 npm 的 .cmd 包装脚本,命令行最终交给 cmd.exe 重解析,多行 SQL 会在换行处被截断------症状是"一条会话都查不出来",而且日志里看不出任何异常。代码注释里已经写死了这条规矩:

arduino 复制代码
// 必须写成**单行**:... 换行只影响可读性,绝不要为了排版拆行。

3.2 YAML 声明新工具:零代码扩展

不想写 Go?直接在 ~/.kshell/providers.yaml 里声明:

yaml 复制代码
providers:
  - id: mytool
    name: MyTool
    detect:
      command: mytool
      dirs: ["~/.mytool"]
    sessions:
      glob: ~/.mytool/projects/*/*.jsonl
      format: jsonl
    fields:
      cwd: cwd
      id: sessionId
      timestamp: timestamp
      title: message.content
    resume:
      args: ["--resume", "{id}"]
    verified: false          # 声明式配置一律标未实测

MergeProviders 合并时内置同 ID 优先 ------用户配置可以扩展,但不能篡改内置工具的行为。verified: false 这个字段是刻意的:声明式配置没经过实测,UI 上会明确标出,避免用户误以为和内置工具一样可靠。

3.3 只读文件头部:性能上的关键决策

Claude Code / Codex 的 JSONL 会话动辄好几 MB。kshell 全程只读文件头部字节,各家上限不同:

  • codex:256 KB
  • gemini:2 MB

解析出的元数据(ID、cwd、时间、标题)对这个量级完全够用。整读多 MB 的 JSONL 是纯粹的浪费。

3.4 缓存:mtime + size 双重门控

每次刷新都全盘解析 JSONL 是不可接受的。kshell 的两级缓存都用 mtime + size 双因子判定是否复用:

arduino 复制代码
// internal/discovery/index.go:79
return ok && entry.MTime == mtime && entry.Size == size

为什么两个因子都要?只比 mtime 会漏掉"同一时间戳内被等量改写"的情况;只比 size 会漏掉"内容变了但长度没变"。两者都相同才认为文件未变。

再往上还有一层 snapshot.json 支撑秒开 + 后台刷新------用户看到的是上次的完整结果,同时后台在重扫,扫完再原子替换。感知延迟基本为零。

3.5 终端退出:drain 机制与那 40% 的输出

这是踩过坑才有的代码。

桌面端关闭终端时,直接 Close() ConPTY 会丢掉约 40% 的尾部输出------agent 刚吐完一整段回复,你一关窗口,最后几句就没了。

正确做法是 drain:

复制代码
延迟 80--500ms → 持续收取剩余输出 → 输出排空后再关闭

延迟时长的逻辑是:既要给子进程留出写完缓冲区的时间,又不能让"关终端"这个操作感觉卡顿。

顺便区分两个容易混淆的东西:

  • drain 是"关闭终端时排空缓冲区";
  • ~/.kshell/exit.signal 是"桌面版轮询该文件优雅退出"。

后者是给构建脚本用的,而且构建过程不会退出正在运行的实例------你可以一边用着旧版本一边重新构建。

四、SSH:安全上的几条硬规矩

远程管理这块我定了很明确的底线:

  • 始终走系统 ssh 二进制 ,加 -o BatchMode=yes,复用 ~/.ssh/config、ssh-agent、ProxyJump、known_hosts;
  • 不传密码,不绕过主机密钥校验;
  • 私钥只存路径 ,绝不把密钥内容落盘到 connections.yaml。

候选扫描还做了置信度分级:

来源 置信度
~/.ssh/config 高
.env* / Spring application*.yml 中
docker-compose / Makefile / deploy*.sh / ansible 低
README 等文档 低

低置信度候选默认不勾选 ,必须人工确认后才写入 connections.yaml。自动猜出来的服务器直接给你连上,等于把"连错机器执行 rm -rf"的风险前置到扫描阶段了。

五、两个入口,无模式开关

入口 技术 产物
桌面端 Wails v2 + React 19 + xterm kshell-desktop
TUI Bubbletea kshell

TUI 的定位不是"降级方案",而是键盘流的老手会真的喜欢的形态:

javascript 复制代码
┌ kshell  ●claude ●codex ○gemini        ws: ~/projects/demo ─┐
│ [Sessions] Files  Remote                    (Tab 切换)      │
├──────────────────────┬──────────────────────────────────────┤
│ WORKSPACES           │ PREVIEW                              │
│ ▸ demo          12   │ 会话摘要 / 文件内容 / ssh 输出        │
├──────────────────────┴──────────────────────────────────────┤
│ ↑↓ 移动  ⏎ 进入  / 搜索  n 新建  r 重扫  ? 帮助  q 退出     │
└─────────────────────────────────────────────────────────────┘

n 新建会话、r 重新扫描、/ 搜索过滤、? 帮助------全键盘可达。

六、目前支持的工具

工具 会话存储 终端 resume ACP 聊天
Claude Code ~/.claude/projects/<slug>/*.jsonl 已验证 claude-agent-acp
Codex CLI ~/.codex/sessions/**/*.jsonl 已验证 走终端
Cursor ~/.cursor/projects/*/agent-transcripts/ 内置 cursor-acp
CodeBuddy ~/.codebuddy/projects/*/*.jsonl 已验证 CLI --acp
Gemini CLI ~/.gemini/tmp/ 推断,未实测 走终端
OpenCode SQLite(opencode db ... --format json) 内置(--session) 走终端

⚠️ 表格里的"已验证"和"推断,未实测"是有意区分的。 工具升级会改会话格式,我不敢替厂商打包票。凡是我本机没真实跑过的,都老实标出来。

检测上的一个坑:Cursor 的 shim 会被删光

Cursor 官方更新器会周期性清空安装目录里的入口 shim,只留下 node.exe + index.js 本体。这时候在 PATH 上 Detect 必然落空。

kshell 的兜底策略是:在各个 InstallDirs 里找 node.exe 与主脚本都存活的组合:

c 复制代码
// shim 全部落空时,Detect 会在 InstallDirs 各目录里找
// node.exe 与主脚本存活的组合作为最后兜底(Source=node-entry)。
NodeEntryScript string

于是 Detection 里多了一个 BinArgs 来承载入口前缀参数:

c 复制代码
// BinPath 指向 node.exe 时为 [主脚本名],普通可执行文件为 nil。
// 启动与版本探测都须先拼上这组参数。
BinArgs []string

这里又踩过一次坑:最初版本没拼 BinArgs,子进程继承了别的 cwd,找不到主脚本。所以启动和版本探测都必须先拼上这组参数 ------现在有回归测试盯着(TestDetectAllCarriesBinArgs,还特意隔离了 PATH 以免依赖本机环境)。

七、前端:一个 Store + 常驻 xterm

前端架构刻意做得很薄:

  • 单一 Zustand store (state/store.ts),页签状态持久化到 localStorage;
  • 所有后端调用经 lib/api.ts → 生成的 window.go.desktop.App;
  • 后端推送经 EventsOn:terminal:data / scan:done / chat:update。

有一个性能细节值得单独说:xterm 实例常驻挂载(terminalRegistry),切页签只隐藏不销毁。

早期版本切走终端页签就销毁 xterm,切回来时缓冲区、滚动位置、ANSI 状态全丢,重建还有闪烁。改成常驻挂载后,切页签是零成本操作。

前端 106 个 TS/TSX 文件,106 个测试配套------npm test 走 vitest jsdom 环境。

八、工程实践

测试:206 个 _test.go

Go 侧 206 个测试文件,覆盖重点在容易回归的地方:会话解析、路径匹配、缓存门控、忽略规则继承。

bash 复制代码
go build ./...
go vet ./...
go test ./... -count=1

前端另跑 npm test / npm run build。

顺手修掉的两个真 bug

继承忽略(inherited ignore) :gitignore 命中的是目录时,其下的子项也要继承忽略状态。第一版只判断直接命中,导致"文件树里显示了本该隐藏的文件"。

嵌套 git 的中间层徽章 :仓库套仓库时,中间那层 .git 目录应该显式标出来------只标最外层会让人误以为整棵子树归属同一个仓库。

这两个 bug 都不是逻辑复杂,而是测试没覆盖到组合场景。修完之后补了回归用例。

构建

bash 复制代码
# TUI → dist\kshell.exe
.\build.ps1

# 桌面端 → dist\kshell-desktop.exe(自动构建 frontend)
.\build.ps1 -Desktop

build.ps1 -Desktop 不会请求正在运行的旧实例退出 ,构建期间你可以继续用旧版本。dist 拷贝若因 kshell-desktop.exe 被占用失败,脚本会提示你从托盘退出后重试。

版本

目前 v0.1.3。232 次提交,MIT 协议。

九、明确不做的事

先说清楚不做什么,比列功能更有诚意:

  • ❌ SFTP / 文件传输 ------系统 scp 就够
  • ❌ 端口转发 ------ssh -L 是成熟方案
  • ❌ 云同步------会话文件本机就有,同步等于制造第二份真相
  • ❌ 再实现一遍各家对话协议------这是最大的技术债来源,绕开

十、怎么用

下载:GitHub Releases 或 GitCode Releases,桌面端还可以在「设置 → 通用 → 关于」里检查更新,升级优先走 GitCode 国内源。

从源码构建:

bash 复制代码
git clone https://github.com/kaiys202212/kshell.git
cd kshell
go install github.com/wailsapp/wails/v2/cmd/wails@latest   # 桌面端需要

.\build.ps1            # TUI
.\build.ps1 -Desktop   # 桌面端

写在最后

这个项目从一个具体的痛点长出来:AI 编程工具越多,"找回自己的会话"就越难。

我没有试图造一个更强的 AI 编程工具,而是造了一个中立的入口。谁家工具好用就用谁,kshell 只负责让你能看见它们、方便地回到它们。

如果你也在同时用好几个 AI CLI,欢迎来试,也欢迎提 issue------尤其是新的工具适配,那是最容易一起把事情做好的地方。

仓库地址 :github.com/kaiys202212...


相关推荐
HelloWorld0011 小时前
Muse 登顶 App Store 并开源 SDK:为什么说 AI Agent 正从“屏幕囚笼”走向“现实硬件”?
ai编程
李听到1 小时前
恶意 README 能遥控你的 Agent:提示词注入攻防实录
ai编程
Web3_Basketball1 小时前
3行代码带你跑通Unsloth微调合成数据
ai编程
空心木偶☜2 小时前
Langgraph操作时常见的错误
python·ai·ai编程·langgraph
c萱3 小时前
AI产品经理——03Prompt Engineering提示词工程
ai·prompt·aigc·产品经理·ai编程·ai-native
熊猫钓鱼>_>4 小时前
从闲置平板到家里的“控制大脑“:鸿蒙智慧中控面板完整实战
运维·人工智能·华为·自动化·电脑·ai编程·harmonyos
心理之旅5 小时前
WorkbuddyAI办公----- 不懂代码,也能给自己做一个自动化工具:9 轮对话实录
ai编程
心理之旅5 小时前
从 40 分钟到 38 秒:SAP Business One 物料库存自动取数实战
ai编程
HelloWorld0015 小时前
告别轮询与断流!基于 Spring Boot 3 + SSE + Redis 打造生产级 Agent 流式思考与工具调用中枢
ai编程