lexiang-cli 模块业务边界说明

April 24, 2026 · View on GitHub

目的:明确每个模块的职责范围和边界,避免在错误的层次实现功能。 核心原则:上层可以调用下层,下层不能反向依赖;同层模块之间尽量解耦。


整体架构(从上到下)

┌─────────────────────────────────────────────────────────┐
│  main.rs — 入口:参数解析、命令分发、日志初始化            │
├──────────┬──────────────┬───────────────────────────────┤
│  cmd/    │  serve/      │  shell/                       │
│ CLI命令层 │ JSON-RPC服务层 │ 虚拟Shell引擎                 │
├──────────┴──────────────┴───────────────────────────────┤
│  mcp/ — MCP 协议客户端(HTTP transport + 协议编解码)     │
├─────────────────────────────────────────────────────────┤
│  service/ — 业务逻辑层(block 操作、MDX 转换等)          │
├──────────┬──────────────┬───────────────────────────────┤
│  auth/   │  config/     │  vfs/  daemon/ worktree/ ...  │
│ 认证     │ 配置管理     │ 基础设施                       │
└─────────────────────────────────────────────────────────┘

1. main.rs — 程序入口

职责(main.rs)

  • 初始化日志系统
  • 解析命令行参数,分发到正确的处理函数
  • 命令优先级:block 静态命令 > 动态 MCP 命令 > clap 静态子命令

分发逻辑

1. --help → 显示帮助(含动态命令)
2. lx block <subcmd> → cmd/block/mod.rs(静态实现,优先)
3. lx <namespace> <tool> → cmd/dynamic/mod.rs(动态 MCP 调用)
4. lx < clap 子命令> → Cli::parse() → 各 cmd 模块
5. 无参数 → 默认帮助

边界(main.rs)

  • 只做路由,不做业务逻辑
  • 不直接调用 mcp::McpClient(通过 cmd 层间接调用)
  • 不包含任何 UI 格式化代码

2. cmd/ — CLI 命令层

这是用户通过终端交互的唯一入口层。每个子目录对应一类命令。

2.1 cmd/cli.rs — Clap 命令定义

内容说明
Cli struct顶层命令定义
Commands enum所有静态子命令(mcp, tools, skill, git, worktree, login, serve, sh...)
McpCommandslx mcp list/call
ToolsCommandslx tools sync/list/schema/sync-embedded
SkillCommandslx skill generate/install/uninstall/update/status
WorktreeCommands / GitCommands工作区和 Git 风格操作

边界:纯定义,不含实现逻辑。

2.2 cmd/block/mod.rsBlock 静态命令(重点)

职责(cmd)

block_* 系列 MCP tool 封装为一级 CLI 子命令,提供:

  • 友好的参数解析(--block-id, --file, --format 等)
  • 本地增强能力(MDX 自动检测/转换、格式化输出、树形展示)
  • 错误信息友好化

已实现的静态子命令

命令对应 MCP Tool特殊处理
lsblock_list_block_children树形缩进展示
getblock_describe_block支持 --format mdx 输出 MDX
createblock_create_block_descendant自动 MDX→blocks 转换
updateblock_update_block支持 text 快速更新或 MDX 全量更新
deleteblock_delete_block直接代理
moveblock_move_blocks直接代理
convert(本地)MDX ↔ JSON 双向转换
export/import/tree/table-*(高级)批量导出、表格操作等

边界(重要!)

  • 是 CLI 命令层,不是 JSON-RPC handler
  • 调用 service::block 做业务逻辑,不直接调 mcp::McpClient
  • 输出格式:面向人类(table、tree、color),不是 JSON-RPC response
  • 不要在这里实现 serve/methods/* 的内容

何时添加新的静态子命令?

当某个 MCP tool:

  1. 使用频率高(用户经常手动调用)
  2. 需要本地增强(MDX 转换、批量操作、格式化输出)
  3. 参数复杂,CLI 比 JSON 更易用

否则走 cmd/dynamic 动态命令即可。

2.3 cmd/dynamic/mod.rs — 动态 MCP 命令

职责(dynamic)

根据 schemas/lexiang.json 中的 tool schema,自动生成 CLI 子命令并转发到 MCP Server。

工作流程

lx space list_spaces → 解析 namespace="space", subcommand="list_spaces"
    → 查找对应的 MCP tool name: "space_list_spaces"
    → 构建参数 JSON → 调用 McpClient.call_tool() → 格式化输出

边界(dynamic)

  • 只做"透传 + 格式化",不做业务逻辑增强
  • 不修改、不转换输入输出数据(除了格式化显示)
  • 被 static 实现覆盖时自动绕过(main.rs 中的优先级判断)

2.4 cmd/tools/mod.rs — Tool Schema 管理

函数职责
handle_sync从 MCP Server 同步 schema 到本地
handle_categories列出 tool 分类
handle_list列出某分类下的工具
handle_schema输出完整 schema JSON(给编辑器集成用)
handle_sync_embedded开发用:同步 schema 到 schemas/lexiang.json(编译嵌入)
handle_sync_unlisted同步未列出的 tool schema

边界:只管 schema 元数据,不管 tool 的实际调用。

2.5 cmd/mcp/mod.rs — MCP 通用操作

函数职责
list_tools列出 MCP Server 所有可用工具
call_tool通用工具调用(原始 JSON 参数)

边界:最底层的 MCP 调用封装,供其他 cmd 模块复用。

2.6 其他 cmd 模块

模块职责边界
cmd/gitGit 风格的 workspace 操作(clone/add/commit/push/pull...)不依赖 shell 模块
cmd/shellShell REPL 入口(build_shell/exec_command/start_repl)只组装,不实现命令
cmd/skillAI Agent skill 文件的生成/安装/卸载文件系统操作为主
cmd/update版本检查与更新GitHub API 调用
cmd/output通用输出格式化(table/csv/markdown/json)纯展示逻辑,无业务
cmd/ui交互式 UI 组件终端交互
cmd/utilscmd 层通用工具函数不依赖 business logic

3. serve/ — JSON-RPC 2.0 服务层 ⚠️

职责(serve)

提供 stdio JSON-RPC 2.0 server,让编辑器(VS Code、Neovim、JetBrains)通过 stdin/stdout 调用 lexiang-cli 能力。

架构

编辑器 ──stdin──▶ transport.rs (读请求)

                handler.rs (路由分发)

         ┌──────────┴──────────┐
    methods/*.rs          fallback
   (静态注册handler)    (MCP动态代理)
         │                    │
         ▼                    ▼
   业务逻辑            ctx.mcp_call()

核心文件

文件职责
mod.rsServeState、ServeContext、RpcMethod 注册宏、error codes、run_serve 入口
transport.rsstdio 读写循环(stdin 读请求 → dispatch → stdout 写响应)
handler.rs方法路由:inventory 表匹配 → 未命中则 fallback 到 MCP tool call
protocol.rsJSON-RPC 2.0 数据结构(Request/Response/Error/Notification)

serve/methods/ — 静态 RPC Method Handler

文件注册的方法说明
auth.rsauth/startOAuth, auth/completeOAuthOAuth 认证流程
contact.rscontact/whoami用户信息
entry.rs条目相关方法知识条目 CRUD
file.rs文件上传下载文件操作
lifecycle.rsinitialize, exit生命周期
quota.rs配额查询存储配额
search.rs搜索相关全文/向量搜索
space.rs空间相关知识库列表/详情
team.rs团队相关团队列表/详情

如何添加新 handler

// 在 serve/methods/ 下任意文件
use crate::serve::{JsonRpcResult, ServeContext, rpc_method};

async fn handle_my_method(ctx: &ServeContext, params: Value) -> JsonRpcResult {
    Ok(serde_json::json!({ "result": "..." }))
}

inventory::submit! { rpc_method!("my/domain/method", handle_my_method) }

Dynamic Fallback(关键机制)

handler.rs 中,如果 inventory 表中没有匹配的 method,会自动尝试转为 MCP tool 调用:

"space/list" → MCP tool "space_list"
"entry/content" → MCP tool "entry_content"

这意味着新增 MCP tool 后无需改代码即可在 serve 中使用

边界(重要!⚠️)

可以做不可以做
接收 JSON-RPC 请求,返回 JSON-RPC 响应实现 CLI 命令行的业务逻辑
调用 MCP Server 获取数据直接操作文件系统(除必要的缓存外)
数据适配/聚合(合并多个 MCP 调用结果)包含 CLI 格式的用户交互(prompt、确认)
通过 ctx.mcp_call() 调用 MCP直接引用 cmd/ 层的函数

cmd/ 层的本质区别

维度cmd/serve/
交互方式人类敲命令(终端 TTY)编辑器发 JSON-RPC(stdio)
输出格式table、tree、color、human-friendlyJSON(strict JSON-RPC 2.0)
错误处理友好提示 + exit codeJsonRpcError with code
适用场景开发者手工操作、CI/CD 脚本VS Code/Neovim 插件后端

4. mcp/ — MCP 协议客户端层

职责(mcp)

封装与 Lexiang MCP Server 的 HTTP 通信,提供类型安全的调用接口。

文件职责
client.rsMcpClient:高层客户端,list_tools() / call_tool() / call_raw<T>()
caller.rsMcpCaller trait + RealMcpCaller:抽象接口,方便测试和注入
transport.rsHttpTransport:底层 HTTP 通信(reqwest),认证 header 注入
protocol.rsMCP 协议数据结构定义(ToolSchema、ToolsListResult、ToolCallResult...)
schema/Schema 管理:运行时加载、编译时嵌入、命令生成器(CommandGenerator)
upload.rs文件上传配置和凭证申请

边界(mcp)

  • 纯粹的通信层,不含业务逻辑
  • 不知道"block"、"entry"、"space"是什么,只知道"tool name + args → result"
  • 可被 cmd/serve/service/shell/ 各层引用
  • 不依赖 cmd/serve/

5. service/ — 业务逻辑层

职责(service)

封装特定领域的业务逻辑,对 MCP 的底层调用做组合、转换、增强

5.1 service/block/ — Block 文档操作(当前核心)

service/block/
├── mod.rs          # BlockService(门面),统一入口
├── types.rs        # 数据结构定义(Block、BlockType、BlockChildren...)
├── adapter.rs      # MCP 适配器:BlockService → McpCaller trait 的桥接
├── converter.rs    # MDX ↔ Blocks 转换器(双向)
├── document.rs     # 文档级操作(全量获取、导入导出)
├── reader.rs       # 流式读取(大文档分页)
├── table.rs        # 表格 block 专用操作
├── mdx/            # MDX 编解码
│   ├── mod.rs      # MDX 模块入口
│   ├── parser.rs   # MDX → IR(中间表示)
│   └── emitter.rs  # IR → MDX
└── ir/             # 中间表示(Intermediate Representation)
    └── mod.rs      # IR 数据结构定义

核心设计:MDX ↔ Blocks 双向转换

MDX 字符串 ──parser──▶ IR(中间表示)──emitter──▶ MDX 字符串


              Blocks JSON(MCP API 格式)
  • parser:MDX → IR(支持 heading/paragraph/text/bullet_list/ordered_list/thematic_break/divider/code_block 等)
  • IR:与具体格式无关的树形结构(IRNode / IRNodeKind
  • emitter:IR → MDX 字符串
  • converter:IR ↔ Blocks JSON 双向转换

BlockService 门面模式

impl BlockService {
    // 基础 CRUD(委托给 adapter → McpCaller → MCP Server)
    pub async fn get_block(&self, id: &str) -> Result<Block>
    pub async fn list_children(&self, id: &str, recursive: bool) -> Result<...>
    pub async fn create_child(&self, parent_id: &str, blocks: Vec<BlockInput>) -> Result<...>
    pub async fn update_block(&self, id: &str, updates: &BlockUpdates) -> Result<()>
    pub async fn delete_block(&self, id: &str) -> Result<()>
    pub async fn move_blocks(&self, ...) -> Result<()>

    // 增强能力(MDX 转换、文档级操作)
    pub async fn get_as_mdx(&self, id: &str) -> Result<String>   // get + convert to MDX
    pub async fn create_from_mdx(&self, parent_id: &str, mdx: &str) -> Result<()>  // parse+create
    pub async fn export_document(&self, entry_id: &str) -> Result<...>
}

边界(service)

  • 知道什么是 block,但不知道 CLI 参数怎么传
  • 通过 McpCaller trait 解耦网络层(可替换为 mock 测试)
  • 不包含任何 CLI 输出格式化代码
  • 不包含 JSON-RPC 相关代码

6. shell/ — 虚拟 Shell 引擎

职责(Shell)

实现一个类 Bash 的 Shell,让 AI Agent 用 UNIX 命令风格操作知识库。

shell/
├── bash.rs         # Bash 主入口(REPL / 单次执行)
├── parser/         # 词法分析 + 语法解析(Lexer → AST → Parser)
├── interpreter/    # 解释执行引擎(管道、重定向、变量替换、条件)
├── commands/       # 内置命令实现(ls/cat/grep/find/tree/cd/pwd/mkdir/touch/rm/echo/wc/head/tail/sort/uniq/diff/stat/git-help)
└── fs/             # 虚拟文件系统抽象(IFileSystem trait → InMemoryFs/OverlayFs/MountableFs)

边界(Shell)

  • 独立运行时:有自己的 parser/interpreter/fs,不依赖 cmd/clap
  • 通过 McpCaller trait 与 MCP 交互
  • 内置命令的实现调用 fs/ 抽象层,不直接调 MCP
  • 不被 cmd/serve/ 引用(只有 main.rs → cmd/shell → shell/)

7. 基础设施模块

模块职责被谁使用
auth/OAuth 登录、token 管理、refreshmain, serve, cmd
config/配置文件加载(~/.lexiang/config.toml)、环境变量全局
vfs/虚拟文件系统(FUSE)daemon
daemon/守护进程管理(start/stop/status)main
worktree/本地工作区管理(类似 git worktree)cmd/git
datadir/数据目录管理(~/.lexiang/)auth, config
update/自更新(GitHub releases 检查)cmd/update
skill/Skill 模板渲染cmd/skill
version/版本号管理main

8. 关键规则总结

规则 1:静态优先于动态 ✅

如果某个 tool 有了 cmd/block/ 的静态实现,cmd/dynamic/ 会自动跳过它。 扩展方式:需要本地增强 → 加静态子命令;简单透传 → 用动态命令。

规则 2:serve/cmd/

  • serve/methods/ 是给编辑器用的 JSON-RPC handler
  • cmd/ 是给人用的 CLI 命令
  • 永远不要把 CLI 业务逻辑写到 serve/methods/

规则 3:依赖方向(单向)

main → cmd → service → mcp (client/caller)

        serve → mcp

        shell → mcp
  • 下层不能反向依赖上层
  • mcp 是最底层通信库,可被所有层引用

规则 4:service/ 是业务逻辑的唯一归属

  • MDX 转换、blocks 组装、文档级操作 → service/block/
  • cmd/block/ 只做"解析参数 → 调 service → 格式化输出"
  • serve/methods/ 只做"解析 JSON params → 调 MCP/service → 返回 JSON"

规则 5:新增功能的决策树

需要新功能?
├── 给编辑器插件用? → serve/methods/ 新建 handler
├── 给终端用户用?
│   ├── 需要本地增强(转换/批量/格式化)? → cmd/<domain>/ 新建静态子命令
│   ├── 简单调用 MCP tool? → 走 dynamic 自动生成
│   └── 是全新的命令领域? → cli.rs 加 Commands 枚举 + cmd/<new>/ 模块
├── 给 AI Shell 用? → shell/commands/ 新建内置命令
└── 纯业务逻辑? → service/ 新建或扩展现有模块