TaskLens:基于 Tauri 与 React 的任务中心 Markdown 工作台项目

以任务为中心浏览工作记录、沉淀经验与成果的本地 Markdown 工作台|作者个人站:https://softwarescience.top/projects/tasklens

分支1Tags1

TaskLens · 任务透镜

作者个人站:https://softwarescience.top/ · 本项目主页:https://softwarescience.top/projects/tasklens(版本、截图与下载都在那儿)

从任务视角看待工作、经验与成果的本地 Markdown 工作台。

作者 创意 / 创建者:CodeLife Leon 开发者:OMP

Tolaria 以「笔记」为中心浏览知识库;TaskLens 以「任务」为中心:选中一个任务,就能同时看到它的工作记录(每日日志里提到它的内容)、沉淀出来的经验笔记、产出的成果笔记,以及所有引用它的笔记。

工作日志 ──┐
经验笔记 ──┼──►  任务  ──►  成果笔记
关联笔记 ──┘
  • 数据即文件:笔记仍然是 vault 里的 .md,与 Obsidian / Tolaria 完全兼容,不锁定格式。
  • 结构化存储:SQLite 索引库保存派生数据(笔记索引、链接图)与真实历史(任务变更事件),随时可删可重建。
  • 技术架构与 Tolaria 一致:Tauri v2 + React 19 + TypeScript + Vite 7 + Tailwind v4 + CodeMirror 6。
  • 一套核心,两种入口:同一个 Rust 核心既通过 Tauri IPC 驱动桌面窗口,也通过 tasklens-cli 提供 stdio JSON 接口(浏览器调试与后续 agent 集成共用)。

目录

TaskLens/
├── core/                  # 纯 Rust vault 核心(无 Tauri 依赖,可独立测试)
│   ├── src/frontmatter.rs # 逐行手术式 frontmatter 读写(保留注释、引号、行尾、键序)
│   ├── src/vault.rs       # 扫描、索引、读写、链接提取、搜索
│   ├── src/brief.rs       # task_brief:一次调用拿全任务上下文(AI 的信息梳理原语)
│   ├── src/db.rs          # SQLite 索引 + 事件日志
│   ├── src/git.rs         # Git 同步(--ff-only 拉取、永不强推)
│   ├── src/agent.rs       # Pi agent 会话:内置宿主(SDK)或外部 `pi --mode rpc`,双向 JSONL
│   ├── src/mcp.rs         # MCP server(stdio JSON-RPC,17 个工具)
│   ├── src/extension.rs   # 内嵌 Pi 扩展源码,一键安装到 vault(外部命令用)
│   ├── src/settings.rs    # 安装级设置(vault、主题、Git、AI;API Key 只写不读)
│   ├── src/dispatch.rs    # 唯一命令表(Tauri / CLI / MCP 共用)
│   └── src/bin/           # tasklens-cli、tasklens-mcp
├── src-tauri/             # 桌面外壳:命令转发 + vault 文件监听 + agent 进程
├── agent-host/            # 内置 agent 宿主:用 pi SDK 起会话,工具走 customTools
├── pi-extension/          # pi.dev 扩展:同一批 vault 工具的外部挂载形态(运行时零依赖)
├── src/                   # React 前端
│   ├── lib/ipc.ts         # 唯一 IPC 边界(桌面 invoke / 浏览器 HTTP 同一套命令)
│   ├── lib/agent.ts       # agent 传输层(桌面事件 / 浏览器 SSE,同一份事件流)
│   ├── lib/vault.ts       # 派生模型:类型分桶、链接解析、任务上下文
│   ├── lib/ai-prompts.ts  # 知识闭环快捷动作(填报 / 梳理 / 沉淀 / 周报 / 体检)
│   ├── state/             # useVault、useNoteDocument、useGit、useSettings、useAgent
│   └── components/        # 侧栏 / 列表 / 看板 / 任务工作台 / 日志页 / AI 面板 / 设置
└── tools/                 # 图标生成、vault 调试、agent 与 MCP 冒烟脚本

运行

依赖:Node 22+ 或 bun、Rust 工具链;内置 AI 宿主需要一个 JS 运行时(bun 优先,退回 node)。

npm install || bun install     # 仓库带 bun.lock,两种都行
npm run tauri:dev              # 桌面窗口
npm run dev                    # 只起浏览器调试版(需 TASK_LENS_VAULT=/path/to/vault)

脚本一律写成直接命令(脚本之间不做包管理器嵌套),所以 npm run … 与 bun run … 行为完全一致:

脚本 作用
dev 打包内置宿主 agent-host/host.mjs → 编译 tasklens-cli → 起 vite(浏览器调试)
build 同上 + tsc -b + vite build,产出前端 dist/
tauri:dev / package 桌面开发 / 桌面打包(会额外准备 sidecar 与宿主资源,详见「打包与发布」)
build:host / build:cli / build:sidecar 单独重建内置宿主 / CLI / 打包用 sidecar
test / typecheck / check 前端测试 / 类型检查 / Rust 测试 + 类型检查 + 前端测试
core:build / core:test 编译 tasklens-cli+tasklens-mcp / 只跑 Rust core 测试

AI 面板默认走内置 Pi 组件:应用用 pi SDK 在本地起一个 agent 宿主进程(agent-host/index.ts),由本机 JS 运行时执行——需要 bun 或 node 之一(bun 优先)。在「设置 → AI 助手」里选模型、填 API Key 即可对话,不需要安装 pi 命令。

agent-host/host.mjs(宿主单文件 bundle)与 src-tauri/binaries/tasklens-cli-*(sidecar)是构建产物,由 npm run build:host / npm run build:sidecar 生成;tauri dev|build 会自动执行,只跑前端时可以手动执行。

想改用别的 agent CLI,就把「外部命令」填上(例如 pi);此时才会用到「安装到 vault」写入的 .pi/extensions/tasklens.ts。

浏览器调试模式需要指定 vault:

TASK_LENS_VAULT=/path/to/vault npm run dev

桌面模式则在应用内选择目录,选择结果记入安装级设置(%APPDATA%/top.softwarescience/settings.json)。

标识符为 top.softwarescience(src-tauri/tauri.conf.json 的 identifier,同时决定配置目录与安装包身份)。改名前的 com.sundata.tasklens 目录仍会被读取一次作为兜底,所以升级后 vault 列表、主题、API Key 不会丢;下一次改动设置时自动写到新目录。设了 TASK_LENS_CONFIG_DIR 时不会读旧目录(隔离开发/测试用)。

打开一个空目录时会问一次「初始化这个目录?」:

  • 可勾选要生成什么——每种类型的标准目录、templates/<类型>模板.md 模板、AGENTS.md(写给 AI/agent 的 vault 约定);默认全选。
  • 已存在的文件一律跳过、从不覆盖(幂等,重复点也不会改坏东西)。
  • 目录与文件名来自「设置 → 类型」,所以自定义类型同样会被建好。
  • 选「暂不」会记住这个 vault(设置里的 initDismissed),不再打扰;空目录里自己加了笔记后也不会再问。

打包与发布

npm run package                # release 编译 + 生成安装包(Windows 上默认 NSIS + MSI)

打包入口都在 package.json(含 "//" 注释,说明每一步做什么、产物在哪、有哪些坑):

脚本 等价命令 用途
package tauri build 打包安装包(默认 NSIS + MSI)
package:nsis / package:msi tauri build --bundles nsis / --bundles msi 只打某一种安装包
package:exe tauri build --no-bundle 只出 tasklens.exe,不打安装包(最快)
package:debug tauri build --no-bundle --debug debug 产物,编译快,产物在 target/debug

一条命令包含四步,全部在 package.json / src-tauri/tauri.conf.json 里声明,不需要手工准备:

步骤 做什么
1. beforeBuildCommand bun run build(= bun build agent-host/index.ts --target=node --outfile=agent-host/host.mjs 把内置 AI 宿主打成单文件 ≈12 MB,再 tsc -b + vite build)+ bun run build:sidecar(node tools/prepare-sidecar.mjs,按 rustc -vV 的 host triple 把 tasklens-cli 拷成 src-tauri/binaries/tasklens-cli-<triple>.exe)
2. 前端 dist/(由上一步的 vite build 产出)
3. Rust release 编译 → target/release/tasklens.exe(首次约 2 分钟)
4. 打包 bundle.resources 带上 agent-host/host.mjs、bundle.externalBin 带上 tasklens-cli.exe,都放进安装目录

产物(Windows,文件名含版本与架构):

文件 大小(参考) 说明
target/release/bundle/nsis/TaskLens_<版本>_x64-setup.exe ~7 MB NSIS 安装包(推荐)
target/release/bundle/msi/TaskLens_<版本>_x64_en-US.msi ~9 MB MSI 安装包(便于域内部署)
target/release/tasklens.exe ~14 MB 免安装运行用;旁边必须同时有 tasklens-cli.exe 与 agent-host/host.mjs(Tauri 构建时已 staged 在 target/release/ 下)

需要交叉编译或其它参数时,仍可直接透传:

npm run package -- --target aarch64-pc-windows-msvc

注意事项:

  • 首次打包会联网下载 NSIS 3.11 与 WiX 3.14 工具链(各几十 MB,之后走缓存)。
  • 目标机器:Windows 10/11;Win11 自带 WebView2,Win10 可能需要装 WebView2 Runtime;AI 功能需要一个 JS 运行时(bun 优先,退回 node)。
  • 未做代码签名:安装时 SmartScreen 会提示「未知发布者」,正式分发需自行配置证书(见下)。
  • 打包前建议关掉正在运行的 tasklens.exe / tasklens-cli.exe,否则 Windows 会锁住 target/ 里的二进制导致编译失败。

代码签名(可选)

证书不在仓库里,需要向 CA 申请、把签名接入构建。先认清 SmartScreen 的现状(官方说明):

  • EV 不再免 SmartScreen:微软已取消「EV 证书自动获得正声誉」,为绕过 SmartScreen 而多花钱买 EV 已不划算(企业采购另说)。
  • 签名后首次下载仍可能提示,但显示的是已验证的发布者名而不是「未知发布者」;声誉随下载量按发布者身份累积,之后签名的新版本可以继承。
  • 不签名则每个版本都从零开始攒声誉,且 Win11 的 Smart App Control 会直接拦下无声誉的未签名可执行文件。

三条路线,按代价从低到高:

路线 成本 适用
不签名 + 内部分发 0 只在自家/客户内网装:把证书或安装包放到内网共享(Trusted Intranet),域内用 GPO 部署 MSI,管理员也可在 Microsoft Security Intelligence 提交文件加速信任
Azure Artifact Signing(原 Trusted Signing) Basic $9.99/月(5,000 次签名)起 个人/小团队首选:证书由微软托管在 FIPS 140-2 L3 HSM,不需要买硬件令牌,可直接接 CI;但需先通过身份验证(个人或组织),审核由第三方执行,材料/周期要有预期
CA 的 OV/EV 代码签名证书 约 $200–300/年起(各 CA 报价与主体审核要求不同) 需要正规「发布者名称」对外分发;自 2023-06 起私钥必须存放在 FIPS 140-2 L2+ 硬件令牌或云 HSM,拿不到裸 .pfx

接好之后在 src-tauri/tauri.conf.json 的 bundle.windows 里二选一(键名以本地 @tauri-apps/cli/config.schema.json 为准):

// A. 令牌 / 证书store 里的证书(signtool 路线)
"windows": {
  "certificateThumbprint": "<证书指纹 SHA-1,去掉空格>",
  "digestAlgorithm": "sha256",
  "timestampUrl": "http://timestamp.digicert.com"   // RFC3161 时间戳,证书过期后签名仍有效
}

// B. 云签名 / 托管签名:让 Tauri 调你自己的签名命令,%1 是要签的文件
"windows": {
  "signCommand": "trusted-signing-cli -e https://<region>.codesigning.azure.net -a <账号> -c <证书配置名> -d TaskLens %1"
}
  • 不要用 npm run tauri signer generate 想着签 exe——那是 Tauri 自动更新(minisign)用的密钥,和 Authenticode 是两回事;本项目没启用 updater,暂时用不到。
  • 签名后自查:"C:\Program Files (x86)\Windows Kits\10\bin\10.0.26100.0\x64\signtool.exe" verify /pa /v target\release\bundle\nsis\TaskLens_0.1.0_x64-setup.exe(本机 Windows SDK 已装,signtool 只是不在 PATH 上)。
  • 证书换新(续费/换 CA)会重置发布者声誉,续签时尽量沿用同一主体与同一张证书。

验证

npm run check                  # Rust 测试 + 类型检查 + 前端测试(一条命令全跑)
cargo test -p tasklens-core    # 82 个 Rust 测试:frontmatter 手术、索引增量、事件、Git 全流程、设置校验、task_brief、改名/移动、agent 启动参数
npm run test                   # 111 个前端测试:markdown 行号、链接解析、任务上下文、日志日期、笔记创建、对话状态机、设置合并
npm run typecheck              # 类型检查(含 pi-extension、agent-host)

冒烟脚本(真实进程,不是 mock):

node tools/mcp-smoke.mjs .dev-vault target/debug/tasklens-mcp.exe        # MCP 握手 + 17 工具 + 错误码
node tools/agent-smoke.mjs .dev-vault "用 vault_task_brief 看看某个任务"  # 真机 agent:起内置宿主、调工具、给答案

手动验证建议先复制一份 vault,避免测试写入真实笔记:

mkdir -p .dev-vault && cp -r <vault>/{任务,工作日志,经验,成果,templates,views} .dev-vault/   # 按你 vault 的实际目录取
TASK_LENS_VAULT=$PWD/.dev-vault TASK_LENS_CONFIG_DIR=$PWD/.dev-config npm run dev

TASK_LENS_CONFIG_DIR 把安装级设置(vault 列表、主题、Git 自动同步)写到指定目录,这样开发/测试不会动到真实安装的配置。

辅助脚本:

  • bun tools/inspect-cli.mjs <vault> '{"cmd":"list_vault","args":{}}' — 直接调用核心 CLI 查看扫描结果
  • bun tools/make-icon.mjs app-icon.png && bunx tauri icon app-icon.png — 重新生成应用图标

开发手册

面向想自己改这个项目的人。先读后面的「存储设计」「Vault 约定」两节了解数据模型,这一节只讲怎么加东西。

0. 架构一页图

                    ┌─ Tauri IPC(vault_command 统一转发)
前端 src/ ──────────┼─ POST /__vault/cmd(浏览器调试,tools/vault-dev-server.ts)
                    └─ /__agent/*(SSE,agent 事件)
                              │
                       core::dispatch::run          ← 唯一命令表,三种入口共用
                              │
        ┌─────────────────────┼──────────────────────┬───────────────┐
     vault.rs            frontmatter.rs           git.rs          db.rs
  扫描/读写/搜索        逐行 YAML 手术            同步(不强推)   SQLite 索引 + 事件
                              │
   agent 侧:pi 扩展(pi-extension/tasklens.ts) ─spawn→ tasklens-cli ─┘
              MCP server(core/src/mcp.rs) ────────→ dispatch::run ─┘

一条铁律:任何写入都先落盘、再 reindex(vault.rs::edit_body 是唯一收敛点),索引 .tasklens/tasklens.db 随时可删可重建,只有 events 表是不可重建的历史。

1. 加一个 vault 命令(Rust → 界面)

步骤 落点 说明
1 core/src/dispatch.rs run() 的 match command 加分支,参数用 root_for/string_arg 等助手,返回值 as_value(...)
2 同文件 COMMANDS 常量 只影响 commands 自省输出,run() 不校验它(task_brief 就没登记)——但请登记,免得漏
3 — Tauri 零改动:src-tauri/src/lib.rs:19 的 vault_command 统一转发,别新增 #[tauri::command]
4 — tasklens-cli 零改动:core/src/bin/tasklens-cli.rs 逐行调 dispatch::run
5 core/src/mcp.rs 的 TOOLS 给 MCP 客户端加一条 Tool{name, description, command, params}
6 pi-extension/tasklens.ts 的 VAULT_TOOLS 给 Pi agent 加一条 ToolDefinition,execute 里 invoke("<命令名>", args, …)
7 src/lib/ipc.ts 的 vaultApi 加一个包装:readNote: (path, root?) => command<NoteContent>("read_note", { path, root })

最小例子(dispatch.rs):

"list_vault" => {
    let root = root_for(args, default_root)?;
    let entries = vault::scan(&root)?;
    Ok(json!({ "root": root.to_string_lossy(), "entries": as_value(entries) }))
}

命令名与工具名不是一回事:vault_read → read_note、vault_write → save_note、vault_toggle_checkbox → toggle_checkbox……映射表见「命令表」与 core/src/mcp.rs。

2. 加 MCP / Pi 工具

  • MCP:Tool{ name, description, command, params },param(name, kind, required, forward, description)。类型只认 string / integer / array / object(没有 boolean/number);forward=false 表示这个参数不传给命令、由工具侧自己消化(vault_list 的 type 就是这样过滤的)。
  • Pi:parameters 用 typebox 的 Type.Object({...}),execute 里先做本地校验(checkPath/checkLine),再 invoke("<命令名>", args, signal, formatter)。
  • 测试是硬契约:core/src/mcp.rs 与 pi-extension/tasklens.test.ts 都断言「恰好 17 个工具、名字与顺序」——加/删工具必须同步这两处,否则 npm run check 直接红。

3. 加一个左侧视图(lens)

lens 是纯前端概念,固定四处:

  1. src/lib/lenses.ts:LensKind 联合加名字;lensTitle() 加 case(穷尽 switch,TS 会报缺分支)。
  2. src/components/ListPane.tsx:取数(filterNotes(index.byType.get(lens.type) ?? [], query))、count 三元链、渲染分支(可复用 NoteCardList)。
  3. src/components/Sidebar.tsx:加一个 Chip(active / onClick / count)。
  4. src/App.tsx:状态与透传已就绪,通常不用改;只有新视图的右侧详情要加分支(按 selected.noteType 分派)。

多数新类型不需要动 lens:设置里新增一条类型(行为 note)后,侧栏会自动多一个入口,列表读 index.byType,详情页由 NoteView + NoteSections 承接——只有需要全新交互(像看板那样)时才走上面四步。

4. 加一个设置项

Rust(core/src/settings.rs)五件套,缺一个就会出现「界面存了、重启丢了」:

  1. 字段进 Settings / AiSettings(#[serde(rename_all = "camelCase", default)]);
  2. Default 给默认值;
  3. apply() 加分支并校验(复用 enum_value / number_value / boolean_value / note_path,错误信息中文,未知键忽略);
  4. Settings::load() 里加磁盘归一化(非法值回落默认);
  5. settings_for_client():普通字段自动带出,敏感字段必须像 api_key 一样手动剔除(apiKeySet 只回传真假)。

前端三层同步:src/types.ts(类型 + SettingsPatch)→ src/state/useSettings.ts(FALLBACK 默认值必须与 Rust Default 对齐)→ 设置界面(顶层放 SettingsDialog.tsx,类型放 TypeSettingsSection.tsx,AI 放 AiSettingsSection.tsx)。mergeSettings 对嵌套对象做浅合并;数组字段(如 types)是整表替换。

5. 笔记类型 / 目录 / 模板

  • 类型是用户维护的数据(Settings.types: NoteTypeDef[]),不是代码常量:{ type, label, color, folder, template, namePattern, behavior, published }。默认五条在 src/lib/constants.ts 的 DEFAULT_TYPES(Rust 侧 settings.rs::default_types() 必须同步),运行时统一走 src/lib/noteTypes.ts(typeDefs / typeDef / typeColor / folderForType / templateForType / nameForType / moveTargetForType / initialContent)。
  • Rust 侧没有类型枚举,一律按 frontmatter type: 字符串处理(core/src/brief.rs 里对 日志/经验/成果 做字面量匹配,其余类型自然落到 backlinks);behavior 只有 task / log / note,且 task 的 id 必须是 任务、log 的必须是 日志——这条约束在 Rust 校验里,改前端也要守住。
  • 颜色通过 syncTypeColors()(useSettings 在设置变化时调用)同步给不看 settings 的哑组件(卡片、搜索命中、徽标),组件里统一用 typeColor(type)。
  • 新建笔记的落点与内容见「类型与模板」一节:folderForType() 决定目录(配置 → vault.ts::defaultFolderFor 的"该类型的家" → TYPE_FOLDERS),createTypedNote()(src/lib/createNote.ts)是唯一的建笔记路径(新建对话框、每日日志、待办→任务都走它),initialContent() 决定用模板文件还是内置模板。
  • 类型目录下面的子目录只是分类:defaultFolderFor 取的是"该类型笔记最多、且父目录不再是该类型的家"的目录,moveTargetForType() 保证标记类型时不会把已有的子分类拍平。

6. 测试与验证

层 做法
Rust 单测写在各文件内的 #[cfg(test)] mod tests(无独立 tests/)。需要 vault 时 tempfile::tempdir() 手工建目录写文件,范本见 core/src/mcp.rs 的 vault() 助手
前端 vitest,测试与源码同目录 *.test.ts(x),纯函数优先;src/test/setup.ts 只装 jest-dom
一条命令 npm run check(= cargo test -p tasklens-core + tsc -b + vitest run)
MCP 冒烟 node tools/mcp-smoke.mjs .dev-vault target/debug/tasklens-mcp.exe
Agent 冒烟 node tools/agent-smoke.mjs .dev-vault "用 vault_task_brief 看看某个任务" deepseek deepseek-v4-flash
dev fixture TASK_LENS_VAULT=$PWD/.dev-vault TASK_LENS_CONFIG_DIR=$PWD/.dev-config npm run dev(.dev-vault 是示例 vault,.dev-config 隔离安装级设置)

7. 调试技巧与坑

调试

  • 桌面窗口开远程调试:WEBVIEW2_ADDITIONAL_BROWSER_ARGUMENTS=--remote-debugging-port=9333 再启动应用,然后用 CDP 连接即可看 DOM / 截图 / 执行 JS。
  • tauri dev 下前端改动走 HMR(改完立刻生效);Rust 改动会自动重编译重启。
  • 直接摸核心:bun tools/inspect-cli.mjs .dev-vault '{"cmd":"list_vault","args":{}}'。
  • 起不来时先清残留:powershell -NoProfile -Command "Get-Process tasklens,tasklens-cli -ErrorAction SilentlyContinue | Stop-Process -Force"(否则 target/debug 里的二进制被锁,cargo 报「拒绝访问」);vite 端口 1420 被占也会让 dev 起不来。

必须知道的坑

坑 说明
frontmatter 是逐行手术 core/src/frontmatter.rs 从不整体重排 YAML:每个字段保留原行、注释、引号、键序、CRLF/BOM、末尾换行。永远不要用 serde 重新序列化整个 frontmatter
行号是绝对文件行 set_body_line / delete_body_line / insert_body_line / toggle_checkbox 收的都是 1-based 含 frontmatter 的行号;删/插会让后面的行号前移,连续操作要先重读或从大到小
路径规则 vault.rs::resolve 拒绝绝对路径与任何非普通组件(.././根),对外统一正斜杠;无 canonicalize,符号链接不在防护范围内
索引可重建、事件不可 notes/links 是派生缓存,schema_version 不符就重建;events 是唯一真实历史,别乱删
.tasklens/ 不进 git 索引目录由 core/src/git.rs 写进仓库 info/exclude(不改 vault 的 .gitignore),也不会被扫描
MCP 参数类型受限 只有 string / integer / array / object;用 boolean 会永远校验失败
Windows 进程 优先真实 .exe 而不是 .cmd 垫片(垫片要过 cmd.exe,会丢 stdio);结束后代要 taskkill /T 杀整棵树——agent 会话已按此实现(core/src/agent.rs)

命令表

tasklens-cli 与 Tauri IPC 共用同一套命令(core/src/dispatch.rs):

分组 命令
vault list_vault reload_entry read_note save_note search task_brief
frontmatter update_frontmatter create_note trash_note
文件名 / 位置 rename_note(folder 可选:省略=原地改名,""=移到知识库根,其余为相对目录;指向旧路径的 wikilink 一并改写)
文件 create_folder(含多级,已存在视为成功) scaffold(批量建目录 + 只补缺失文件,用于初始化空 vault)
正文 append_body_item toggle_checkbox set_body_line insert_body_line delete_body_line replace_section
索引 / 事件 list_events
设置 / AI get_settings update_settings set_theme add_vault remove_vault ai_extension_install
Git git_status git_init git_set_remote git_fetch git_pull git_commit git_push git_sync

AI 助手(内置 Pi)

对话是核心:面板里和 agent 说话,它调用 vault 工具去读、搜索、按行改写,完成任务填报 → 信息梳理 → 经验/成果沉淀的闭环。

怎么接的

层 做法
Agent 进程 core/src/agent.rs 启动 agent:默认是内置宿主(bun/node 跑 agent-host/index.ts,配置经 stdin 的 start 下发);填了外部命令才改走 pi --mode rpc。工作目录 = 当前 vault,stdin 发命令、stdout 收事件(JSONL)。Windows 上裸命令若是 .cmd 会经 cmd.exe 启动。
桌面 Tauri 命令 agent_start/send/stop + agent-event 事件流。
浏览器预览 dev server 起 tasklens-cli --agent-server,命令走 POST、事件走 SSE。
前端 lib/agent.ts 屏蔽两种传输;state/agentEvents.ts 把 pi 的 delta/工具事件映射成对话(纯函数,可单测)。

给 agent 的工具

pi-extension/tasklens.ts(应用内嵌,设置里一键安装到 <vault>/.pi/extensions/)注册 17 个工具,全部经 tasklens-cli 落到同一套 vault 语义:

vault_list vault_read vault_search vault_task_brief vault_events vault_create_note vault_write vault_update_frontmatter vault_replace_section vault_append_item vault_set_line vault_insert_line vault_delete_line vault_toggle_checkbox vault_git_status vault_git_sync

外加四个命令:/tasklens-file(填报任务)、/tasklens-today(梳理今天)、/tasklens-distill(沉淀经验与成果)、/tasklens-sync。

面板里的快捷动作

「填报任务」「梳理今天」「沉淀经验与成果」「写周报」「整理知识库」——每个都是一段写好的提示词,明确告诉 agent 用哪些工具、遵循什么 vault 约定;点一下填进输入框,确认后再发送(不会未经确认就改文件)。任务页和日志页也有直达按钮(AI 沉淀 / AI 梳理)。

模型与凭证(设置 → AI 助手)

  • 运行方式:默认内置 Pi 组件(只挑 JS 运行时 bun/node,设置里显示探测到的路径);填「外部命令」则改为启动该 CLI。
  • Provider / 模型 / 思考强度:留空则沿用 pi 自己的配置(pi --list-models 可查可用模型)。
  • API Key:只写不读——写进本机设置文件,界面只显示「已保存」,任何客户端都拿不回明文。
  • 自定义端点:填写 OpenAI 兼容地址(自建或代理)时通过 OPENAI_BASE_URL 传给模型客户端。
  • 其他:会话目录、附加参数、是否读取 vault 的 AGENTS.md、是否信任 vault 内的 .pi 资源。内置模式不自动发现 .pi/extensions,只加载列表里显式挂载的扩展(避免工具重名)。

MCP server

cargo build -p tasklens-core --bin tasklens-mcp

stdio JSON-RPC(MCP 2024-11-05),17 个工具与上表同名同参。注册示例:

{
  "mcpServers": {
    "tasklens": {
      "command": "D:/Develop/workspace/Files/Tolaria/TaskLens/target/debug/tasklens-mcp.exe",
      "args": ["--vault", "D:/Develop/workspace/Files/Tolaria/SundataWork"]
    }
  }
}

这样 Claude Code / Cursor 等 MCP 客户端也能读任务上下文、检索、按行改写这个 vault。

存储设计

笔记是事实来源,索引是派生物。

层 位置 说明
笔记 <vault>/**/*.md 人可读、可 Git、可被 Obsidian / Tolaria 同时编辑
索引 <vault>/.tasklens/tasklens.db SQLite;删除后下次扫描自动重建
事件 同上 events 表 唯一不可重建的历史:谁在什么时候把哪个字段从什么改成了什么

SQLite 架构(core/src/db.rs):

  • notes(path, …frontmatter json, links json, body, snippet, modified_ms, size, …) — 扫描缓存;只有 mtime/size 变化的文件会被重新解析。
  • links(source, target) — 链接图,用于反向链接与任务上下文。
  • events(path, kind, detail json, at_ms, source) — 生命周期事件;source 区分写入者(app 或未来的 agent)。
  • meta(schema_version) — 版本不匹配时重建派生表,保留 events。

写入路径始终是 先落盘、再更新状态:frontmatter 逐行改写,正文与其它字段字节不变;写完后单文件重新入索引。

Vault 约定

术语:界面上一律叫**「知识库」;代码里保留 Obsidian 的术语 vault(vault.rs、VaultIndex、vaultApi、list_vault、vault_* 工具、--vault / TASK_LENS_VAULT)。两者指同一个东西:装着全部笔记的那个根目录。它不是**密钥保险库,也不加密任何内容。

目录只用于组织,笔记类型一律看 frontmatter 的 type:。下表是默认目录(类型同名目录,落在知识库根下),都可以在「设置 → 类型」里改(留空则跟随知识库现状:已有该类笔记就放它们所在的目录,否则用默认值)。

type 默认目录 关键字段
任务 任务/ status priority category created owner related_to
日志 工作日志/ date
经验 经验/ kind status created related_to(指向任务)
成果 成果/ kind status created related_to(指向任务)
记忆碎片 记忆碎片/ created related_to
  • 目录只做分类,类型只看 type::类型目录下面可以再分子目录(经验/踩坑/、任务/客户项目/),子目录里的笔记仍然是该类型,TaskLens 不会因为目录不同就换类型,标记类型时也不会把已有的子分类拍平。
  • 新建笔记落在哪里:配置的目录 → 该类笔记最多的那个目录(若它的父目录里也直接放着同类型笔记,就继续往上,直到那个目录不再是该类型的"家")→ 默认表。所以 产研工作/工作任务/ 有 12 篇、产研工作/产品研发/ 有 1 篇时,新任务仍然落在 产研工作/工作任务/。
  • 链接使用 wikilink:related_to: ["[[软著工作-任务]]"]。解析顺序为「路径 → 文件名 → 唯一 H1 标题」,因此手写的 [[软著工作]] 也能命中 软著工作-任务.md(标题歧义时不做猜测)。
  • templates/ 下的文件和含 <% %> 的文件被识别为模板,不会进入任务/经验/成果视图。
  • 类型定义见 vault 根目录的 任务.md / 经验.md / 成果.md;Obsidian 模板见 templates/。
  • 重命名 / 移动任务/日志/经验/成果的文件名或目录时,指向旧路径的 wikilink 会被自动改写([[旧]]、[[旧|别名]]、[[旧#小节]]、[[文件夹/旧.md]] 等写法都覆盖;指向 H1 标题的链接不动,因为标题没变)。
  • 标记为:任意笔记的详情页右上角有「标记为…」,可把它改成另一种笔记型类型(经验 / 成果 / 记忆碎片,以及你自建的 note 行为类型)。改的是 frontmatter 的 type;若它不在目标类型的目录树里,会先确认再连文件一起移过去。任务 / 日志 不出现在这个列表里——它们带有看板、状态字段、按天导航等行为约定,只能在新建时选择。

类型与模板(设置 → 类型)

类型由你维护:每条类型 = type(frontmatter 里的值)+ 名称、颜色、默认目录、模板文件、文件名格式、是否显示在侧栏。默认五条(任务 / 日志 / 经验 / 成果 / 记忆碎片),可以新增(如「会议纪要」)、改、删。

  • type 与「行为」创建后不可改:行为 task(看板 + 状态字段)、log(每日日志)、note(分节编辑器)由程序决定,且 task 只能是 任务、log 只能是 日志。改名会孤立已有笔记,所以 id 只读。
  • 默认目录:留空=跟随 vault(该类笔记最多所在目录,父目录也是该类型的"家"时继续往上;没有笔记时用上表默认值);新类型留空且还没有笔记时,创建对话框会让你填目录。
  • 模板:填 vault 内的 Markdown 路径(如 templates/任务模板.md)。占位符 {{title}} {{name}} {{date}} {{type}} {{task}}(关联任务文件名);Templater 的 <% tp.file.title %> / <% tp.date.now(…) %> 会自动转成占位符,其余写法原样保留。一键「生成模板」把内置内容写进 templates/<名称>模板.md(已存在则不覆盖)。
  • 文件名格式:{title} / {date} 组合,例如日志用 {date}-工作日志,会议纪要用 {date} 会议纪要。
  • 侧栏只显示「已显示」的类型;「全部文件」视图列出整个 vault 的文件夹树,可直接新建文件夹(含多级)与在任意文件夹新建笔记(类型、目录都在对话框里选)。
  • 类型列表是整表存的,所以升级不会往你已有的列表里塞新类型;缺哪个内置类型时,列表下方会出现「补回内置类型:…」按钮(只补缺失的那几条,不动你改过的、也不会把删掉的类型偷偷加回来)。

功能范围

已实现:

  • 任务工作台:字段内联编辑(状态 / 优先级 / 类别 / 负责人 / 截止日期)、结构化正文编辑、Markdown 原文编辑器
  • 任务结构化编辑(不需要写 Markdown)
    • 「说明」:直接编辑 ## 背景 一节,失焦或 Ctrl/⌘+Enter 保存
    • 「子任务」:勾选 / 改写 / 删除 / 回车新增,写回 - [ ] 列表
    • 「变更日志」:回车追加、逐条改写或删除
    • 其余章节(## 自定义)原样渲染,不会丢内容
  • 工作记录:自动聚合每日日志中提到该任务的行
  • 工作日志(每日)
    • 生成:日志视图右上角「写今天的日志」——当天已有日志就直接打开,没有就新建 工作日志/YYYY-MM-DD-工作日志.md(目录与文件名格式可在设置里改)
    • 结构继承:新建时复制「最近一篇日志」的 ## 分节(例如 产研 / 项目: / 其他工作),不用每次从空模板开始
    • 按天导航:日志页顶部「前一天 / 后一天」,缺失的日期点开即创建
    • 填写:每一节都是可编辑清单 —— 勾选、回车新增待办、改写文字、删除;Tab / Shift+Tab 调整缩进;行尾「+」在其下方插入子项
    • 待办 → 任务:待办行尾的靶心按钮打开「新建任务 / 关联已有任务」,两条路都把 [[任务]] 写回那一行——任务的「工作记录」随即收录这篇日志,不需要把同一条信息复制进任务的 ## 子任务
    • 进度:页头显示当天待办 / 已完成数量
  • 变更时间线:来自索引库的结构化事件(应用内与外部写入都可追溯)
  • 经验 / 成果 / 记忆碎片:从任务页面一键新建经验与成果(自动带上 related_to),并在任务页面自动回收;随手记的碎片先落成 记忆碎片,想归位时用「标记为…」升级成经验或成果
  • 看板:按状态分列,拖拽卡片即改写 status 并写入变更日志;详情栏可一键收起(进入看板时自动收起,点卡片再展开),看板占满剩余宽度
  • 笔记视图:frontmatter 属性、关联链接、反向链接、wikilink 跳转、Markdown 勾选框可点、「标记为…」改类型(经验 / 成果 / 记忆碎片等 note 型类型;不在目标目录树里时会确认并一起移动文件)
  • 类型设置(设置 → 类型):类型由用户维护——新增/删除、改名称与颜色、指定默认目录与模板、自定义文件名格式、决定是否显示在侧栏;内置「任务」「日志」的行为不可改(看板/每日日志);缺内置类型时一键补回
  • 全部文件视图:整个 vault 的文件夹树,可在任意层级新建文件夹、在任意文件夹新建笔记(类型与目录在对话框里选),不再只能从任务/日志派生
  • 重命名 / 移动笔记:任务 / 日志 / 经验 / 成果页头一键改名,标记类型时可连目录一起移动;指向旧路径的 wikilink(含 |别名、#小节、文件夹/名字.md)自动改写,不留死链
  • 可配置的建卡规则:每种类型的目录、模板文件、文件名格式(设置 → 类型);一键把内置模板写进 templates/ 供 Obsidian 里直接编辑
  • 全库搜索(Ctrl/⌘+K):走索引,无需读取文件
  • AI 助手(内置 Pi):对话式完成任务填报、信息梳理与沉淀;17 个 vault 工具 + 知识闭环快捷动作;模型/API Key/端点可配(Key 只写不读);工具扩展一键安装到 vault
  • MCP server:tasklens-mcp 让任何 MCP 客户端(Claude Code / Cursor …)获得同一套 vault 工具
  • 外部改动:Rust 侧文件监听 + 400ms 防抖重扫
  • 设置(侧栏齿轮)
    • 外观:跟随系统 / 浅色 / 深色;主色(预设 + 取色器),高亮与选中色由主色派生
    • 字体:界面字体(系统 / Inter / 微软雅黑 / 苹方·思源)、代码字体、字号 10–22px(全部文本与 Markdown 正文随之缩放)
    • Git 同步:分支 / 上游 / 变更数 / 领先落后;获取远端状态、拉取、提交、推送、一键同步;远程地址设置
    • 自动同步:可设模式(仅拉取 / 提交并推送)、间隔(1–240 分钟)、打开 vault 时先同步一次
  • Git 安全边界:拉取只用 --ff-only(分叉即停止并提示),推送永不强制;索引库通过仓库级 info/exclude 排除,不会污染提交

尚未实现(有意留白):

  • 附件、图片、公式、Mermaid 渲染
  • 富文本编辑器(说明与子任务是结构化的,其余正文仍是 Markdown 原文编辑)
  • AI 面板的会话恢复(新开会话即新起 pi 会话;历史可在 ~/.pi/agent/sessions 查看)
  • 扩展 UI 对话框(select / confirm / input)——当前只渲染 notify,其余按「忽略」处理并在面板里提示

版本与发版

每次代码改动落地后先判断:这次要不要升版本? 要升就一路做完——绝不停在「代码改了、版本号没动、 标签没打」;判断不用升的,在交付说明里写明理由。

  • 版本号唯一来源:三处必须同号:根 Cargo.toml 的 [workspace.package].version、package.json 的 version、src-tauri/tauri.conf.json 的 version。
  • 标签与版本同号,前缀跟随本仓库既有习惯:v 前缀(如 v0.1.0);一个仓库里不得混用两种写法。
  • 要升的完整序列(缺一步都算没做完):
    1. 在版本号唯一来源处改号;仓库里还有别处重复写了版本(前端 package.json、tauri.conf.json、 README 里的版本引用)的,同一提交里一起改;
    2. 重新构建并验证:bun install(走 npmmirror)→ 按 README「打包与发布」构建桌面端;本机 vite 端口 1420 落在 Windows 保留段时改用生产形态(bun run build + cargo build --manifest-path src-tauri/Cargo.toml --features tauri/custom-protocol);
    3. 提交、git push 当前分支;
    4. 打附注标签(git tag -a <标签> -m "TaskLens 任务透镜 <版本号>")并 git push origin <标签>;
    5. 对外发布的版本要在本仓库留下可查的记录:在 README 的版本历史 / CHANGELOG(没有就新建一节) 写清这一版改了什么、为什么改;并确认分发物(安装包 / 便携包 / 镜像等)的文件名与版本号一致。
  • 判断口径:用户可见的行为 / 界面 / 协议 / 只读数据格式变了,或修了用户能踩到的缺陷 → 升 (补丁位修缺陷,小版本位加能力);纯内部重构、注释、测试、脚本、文档 → 不升。
  • 交付说明里写清两件事:这次要不要升版本(要升就写升到哪个号),以及版本号 / 标签 / 推送 三件事是否都做完;不升就写「不升 + 理由」,不许留白。

关于

这个项目由 CodeLife Leon 独立开发与维护。更多项目、各项目发版信息、赞赏与联系方式都在个人站:

问题反馈与需求建议:优先在仓库提 Issue,也可以邮件 virgil_lgp@163.com。

项目介绍

以任务为中心浏览工作记录、沉淀经验与成果的本地 Markdown 工作台|作者个人站:https://softwarescience.top/projects/tasklens

https://softwarescience.top/projects/tasklens定制我的领域