2026-08-10 10:26:33
本文仅代表作者个人观点,如果内容有错误或和你理解上有所出入,欢迎交流与指正。
学习过程参考了 Pi Agent 的源码。按照其分层思路和一些实现方案,笔者自行实现了一个 MVP 版本的 Harness。
本文主要分享一下整个过程中的一些学习心得和个人的一些想法。
“Agent” 从最开始文本对话式,到多模态(语音/图片/视频)智能体,再到现在的 Agent(能够辅助办公、开发、生成完整项目等),不同阶段 Agent 的定义也不一样。
最近的一阶段是 "Model + Harness = Agent"。
之前是高频使用各种智能体辅助写文档,改项目代码。
最近一段时间准备做一些带 Agent 能力的 AI 应用,所以先学习一下 Harness 相关的知识,知己知彼😋。
开局先来一张图
Harness 算是模型的运行时,各家 Agent 的差异也就在这部分,模型都是可以随时切换的。
所以没有 Harness 的「Agent」,大部分就是纯聊天。
先小小总结一下几个主要模块
| 各个模块 | 作用 |
|---|---|
| Model | 读 Context,产出文本或结构化 tool_calls
|
| Context | 本轮真正送进模型的内容,不单单是「用户输入的那一句话」 |
| Tools | 实际做外部调用,真正改文件、跑命令 |
| Harness | 负责 循环、校验、执行、上下文组装、回写、权限、资源控制等行为 |
| Agent | 模型跑在 Harness 上、冲着目标多步往前推进,直到完成或达到终止条件 |
Tools 是模型和外部沟通的桥:模型只负责发出结构化调用,真正去改文件、跑命令、调 API 的是具体 Tool 实现;调度和校验则由 Harness 来做。
Function Calling(现在大部分时候也叫 Tool Calling)解决的问题是:
避免模型用自然语言描述去说明要调用的工具,而是让它输出可解析的调用结构。
没有这套约定时,模型可能写
我需要调用 get_weather(上海) 来获取天气信息
应用侧很难可靠地解析。有了 Function Calling:
tool_calls(调用谁、参数是什么、本次调用的 id)tool 消息写回贴一张官方的图:function-calling#how-it-works
下面是我画的一版 Model / Harness / Tools 怎么流转起来的(对应上面 1→4 步):
下面来个最简 demo(OpenAI Chat Completions,非流式),看一下调用结构:
// 1)准备工具声明(schema)——只描述能力,不含实现
const tools = [
{
type: 'function',
function: {
name: 'get_weather',
description: '查询某城市当前天气',
parameters: {
type: 'object',
properties: {
city: { type: 'string', description: '城市名,如上海' },
},
required: ['city'],
additionalProperties: false,
},
},
},
]
const messages = [
{ role: 'user', content: '上海今天天气怎么样?' },
]
// 2)一次请求:messages + tools
const res = await fetch(`${OPENAI_API_BASE_URL}/v1/chat/completions`, {
method: 'POST',
headers: {
'Authorization': `Bearer ${OPENAI_API_KEY}`,
'Content-Type': 'application/json',
},
body: JSON.stringify({
model: 'gpt-5.6', // 模型 id
messages,
tools,
tool_choice: 'auto', // 模型自己决定是否调用工具,也可设为 none / required / 指定某个工具
}),
})
const data = await res.json()
const message = data.choices[0].message
// ↓ 输出释义见下方示例
一次成功响应(需要调工具时)大致长这样:
// data 示意(省略 usage 等)
{
id: "chatcmpl-xxx",
choices: [
{
index: 0,
finish_reason: "tool_calls", // 因工具调用结束本轮;最终文本常见 "stop"
message: {
role: "assistant",
content: null, // 也可能是一段说明;有 tool_calls 时经常为 null
tool_calls: [
{
id: "call_abc123",
type: "function",
function: {
name: "get_weather",
arguments: '{"city":"上海"}', // 注意:是字符串,不是对象
},
},
],
},
},
],
}
返回结果里,重点关注这几个:
message.tool_calls:决定是否需要执行工具tool_calls[].id:回写结果时需要一一对应上idtool_calls[].function.name / arguments:工具名和参数finish_reason:"tool_calls" 表示「需要执行工具」;"stop" 表示本轮结束Harness 处理工具调用的核心逻辑是:校验 → 执行 → 回写 → 再请求。
// 1)先把本轮 assistant(含 tool_calls)写入 messages,下一轮还要带上
// 保持 OpenAI 原始结构即可
messages.push({
role: 'assistant',
content: message.content,
tool_calls: message.tool_calls,
})
// 2)解析参数 → 校验 → 执行 → 回写
for (const tc of message.tool_calls) {
// 格式化一下参数
const call = {
id: tc.id,
name: tc.function.name,
arguments: JSON.parse(tc.function.arguments || '{}'), // 字符串 → 对象
}
// 按 schema 校验;不合法则回写错误
if (isInvalid(call)) {
messages.push({
role: 'tool',
tool_call_id: call.id,
content: '参数不合法',
})
continue
}
// 执行工具
const result = await executeTool(call)
// 回写结果到上下文中
messages.push({
role: 'tool',
tool_call_id: call.id,
content: result.content, // 字符串
})
}
// 3)带着更新后的 messages 再请求模型
const res = await fetch(`${OPENAI_API_BASE_URL}/v1/chat/completions`, {
// ... 其他参数
body: JSON.stringify({ messages, tools, tool_choice: 'auto' }),
})
const data = await res.json()
// 模型再次回复,可能包含 tool_calls 或继续自然语言回复
const message = data.choices[0].message
下面是第二次请求模型的 messages 示例,包含了工具调用结果:
[
{ "role": "user", "content": "上海今天天气怎么样?" },
{
"role": "assistant",
"content": null,
"tool_calls": [
{
"id": "call_abc123",
"type": "function",
"function": {
"name": "get_weather",
"arguments": "{\"city\":\"上海\"}"
}
}
]
},
{
"role": "tool",
"tool_call_id": "call_abc123",
"content": "{\"temp\":28,\"unit\":\"C\"}"
}
]
tool_calls 参数tool_call_id 回写 → 再请求真正办事的 Agent,需要把上面的流程自动循环起来:组装上下文 → 请求模型 → 有调用就执行回写 → 再请求,直到给出最终答复,或被约束条件强制停掉。
当然我这里只是一个最简单的实现(用「有没有 tool_calls」来决定要不要自动推进),实际的循环推进条件可能还有其它的策略。
async function runAgent(state) {
while (true) {
// 1)组装本轮真正进模型的 Context
const messages = assembleContext(state)
// 2)请求模型
const assistant = await adapter.stream({ messages, tools: state.tools })
// 3)判断有没有 tool_calls
if (!assistant.tool_calls?.length) {
return assistant.content
}
// 4)有调用:校验 → 执行 → 回写
state.messages.push({
role: 'assistant',
content: assistant.content,
tool_calls: assistant.tool_calls,
})
for (const call of assistant.tool_calls) {
const result = await executeTool(call)
state.messages.push({
role: 'tool',
tool_call_id: call.id,
content: result.content,
})
}
// 5)带着工具结果进入下一轮 while
}
return assistant.content
}
和纯对话的差别就在第 4 步:模型输出调用指令,Harness 调度 Tools,用执行结果继续推进。
以 Pi: agent-loop 为例,默认仍是「本轮有没有工具调用」驱动自动续跑,同时还会看下面这些条件:
1)工具结果不一定需要回传
terminate: true 字段来决定是否回传terminate: true,就不需要再请求模型// 示意:结构化输出工具跑完就结束本轮自动续跑
async execute(_id, params) {
return {
content: [{ type: "text", text: "已保存结构化结果" }],
details: params,
terminate: true,
};
}
2)shouldStopAfterTurn(回合结束后可选停)
内置钩子:回合结束后由调用方决定是否继续;也可在这里选择性地压缩上下文。
3)steering / follow-up
这两个都是「往 Loop 里塞用户消息」,时机不同:
| 名词 | 白话 | 典型时机 |
|---|---|---|
| steering | Agent 还在跑时,用户可以临时插一句进来,改变执行方向 | 当前回合工具跑完后、下一次请求模型前注入 |
| follow-up | Agent 准备结束了,队列里还有一句比如「顺便再总结一下」 | 内层循环要退出前从队列里取出;有则继续跑 |
相关 loop 代码大概就 100 来行,比较容易理解。
tool_calls 则)执行工具 → 再请求进行下一轮Loop 能跑起来之后,接着就是:怎么让它停得住。
Prompt 只能做一些软约束:不可逆操作、调用死循环、反复执行等,需要在 Harness 代码里做硬约束。
const controller = new AbortController()
await runAgent({
maxSteps: 8, // 最大执行回合数
timeoutMs: 60_000, // 整次 Run 的超时时间
stopOnToolError: false, // false:错误写回,模型来纠正
signal: controller.signal,
onConfirm: async (call) => {
// 高危工具执行前二次确认;拒绝则写成错误 tool 结果,不调真实 handler
if (call.name === 'rm' || call.name === 'bash') {
return window.confirm(`允许执行 ${call.name}?`)
}
return true
},
})
// controller.abort(); 主动取消
执行工具调用前加一层执行前校验:校验 schema,工具是否存在,参数是否合法等。
异常情况可以回写错误的 Tool 消息,让模型可以自主的重试,用户也能感知到。
循环的终止与工具调用安全边界,靠 Harness 代码硬约束。
执行沙箱等下来仔细研究一下,后面再单开文章。
上下文组装也是 Context Engineering 在 Harness 里的应用。
每次给模型推送的,不只是「用户刚输入的那一句」,而是 Harness 组装出来的完整上下文,常见包括:
role: "tool" 消息const context = assembleContext({
system: [identity, ...rules], // ① 系统提示 + Rules
tools: toolDefs, // ③ schemas(含 MCP 归一后的)
history: messages, // ②④ 历史里已含 tool 结果;或先摘要
user: currentUserMessage, // ⑤ 本轮输入
// skillCatalog / skillBody // ⑥ SKILL:SKILL 目录和相关SKILL 描述,全文按需注入
// memoryHits / ragChunks // ⑥ Memory / RAG:外置命中 → 注入
})
这块的细节实现,待我下去再多研究几个项目,单独出一期文章。
可以看成是解决某类问题或完成某项任务的流程规范文档(SOP)。
SKILL 不是 Tool,可以看做是一大段提示词,实际执行仍是通过内部的指令描述调用 Tool 来完成任务。
常见就是一份 Markdown(可带 frontmatter),大致三块:
| 部分 | 内容 |
|---|---|
| 元数据 |
name、description(何时该用);可选权限(允许的工具) / 依赖说明 |
| 正文 | 步骤、约束、输出格式、决策规则(真正的 SOP) |
| 其它 | (可选) references/、模板、脚本、workflow 等,这块一般是渐进式披露的 |
---
name: weather-brief
description: 用户要天气简报时使用
---
1. 先调 get_weather …
2. 再按固定结构输出 …
学到的几种方式:
| 方式 | 谁决定加载全文 | 全文怎么进 |
|---|---|---|
| 手工 / 显式 | 用户指定(如 /skill:name) |
直接写入 Context |
| Harness 匹配 | 按 SKILL 描述与用户输入做简单的关键词匹配 | 匹配到的正文写入 Context |
| 模型决定 | 模型自己选要加载哪个 | 先把SKILL目录里的(name + description)给模型,模型决策后,再调工具 load_skill,SKILL 正文以 tool 结果回写 |
三种方式的逐步流程(同一例子 weather-brief):
SKILL 可以由 Harness 发现并注入上下文,也可以给模型披露一些基本信息让模型自己决定加载哪个。
MCP(Model Context Protocol)是一套开放协议,用来标准化 AI 应用怎么连接外部系统(数据、工具等)。
本文先只看 Tools 部分:统一发现与调用。
收到 tool_calls 后的执行路径:
Harness → 选则 Client → Server → 返回的结果回写 Context。
| 角色 | 职责 |
|---|---|
| MCP Host | 创建/管理多个 Client,这里就是 Harness 去负责管理 |
| MCP Client | 与单个 Server 的会话连接:提供listTools、callTool 等常用方法 |
| MCP Server | 暴露 Tools 并执行(本地进程或远程服务;另有 Resources / Prompts,本文不展开) |
传输:本地多用 stdio,远程多用 Streamable HTTP;数据传输协议使用 JSON-RPC。
// mcp-server.mjs —— Host spawn 的就是这个进程
import { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js'
import { StdioServerTransport } from '@modelcontextprotocol/sdk/server/stdio.js'
import { z } from 'zod'
const server = new McpServer({ name: 'demo', version: '1.0.0' })
server.registerTool(
'add',
{
description: '计算两个数字之和',
inputSchema: {
a: z.number().describe('加数 a'),
b: z.number().describe('加数 b'),
},
},
async ({ a, b }) => ({
content: [{ type: 'text', text: String(a + b) }],
}),
)
const transport = new StdioServerTransport()
await server.connect(transport) // stdout 专给 JSON-RPC;日志用 console.error
下面提到的 registry 可以看做是 Harness 里负责管理工具注册和发现的模块
包含本地 Tools + MCP Tools 的管理
import { Client } from '@modelcontextprotocol/sdk/client/index.js'
import { StdioClientTransport } from '@modelcontextprotocol/sdk/client/stdio.js'
// 连接 MCP Server
const transport = new StdioClientTransport({
command: 'node',
args: ['./mcp-server.mjs'],
})
const client = new Client({ name: 'my-harness', version: '0.1.0' })
await client.connect(transport) // spawn + initialize
const { tools } = await client.listTools()
for (const t of tools) {
// 写入 Harness 工具表
registry.register({
name: t.name,
description: t.description ?? '',
parameters: t.inputSchema ?? { type: 'object', properties: {} },
source: { kind: 'mcp', client },
})
}
async function executeTool(call: ToolCall): Promise<ToolResult> {
const meta = registry.get(call.name)
if (meta?.source.kind === 'mcp') {
const res = await meta.source.client.callTool({
name: call.name,
arguments: call.arguments,
})
return {
toolCallId: call.id,
content: JSON.stringify(res.content),
isError: Boolean(res.isError),
}
}
return localHandlers[call.name](call)
}
CLient 连接是长生命周期的:
new Client → connect → listTools 注册一次工具;callTool,复用之前的连接;close()。MCP 还包含 Resources 和 Prompt 能力,这块还要再下来研究一下与 Harness 的联动机制
到这里 0-1 搞一个 MVP 版可跑的 Agent 应该是妥妥的了
下一篇(Part 2)大概会是以下的内容:
2026-08-01 20:56:52
如题 😄 嘿嘿,7月底从 美团 离职了。
前端基本已经不需要古法手搓了,准备转 Agent 方向研究了!
顺便搞点 Agent/AI 相关项目练练手。
家人们有合适的机会也可以推一推。🫶
2026-07-29 17:55:45
笔者维护的 VitePress 博客主题 中包含一个「作品/产品卡片」的能力,可以在 markdown 里通过简单的 ::: card 容器语法快速展示项目。为便于在主题之外复用,将其抽离为独立插件 vitepress-plugin-product-card。
3 步接入:
pnpm add vitepress-plugin-product-card
.vitepress/config.ts 中注册 markdown 容器插件import { defineConfig } from 'vitepress'
import { productCardMarkdownPlugin } from 'vitepress-plugin-product-card'
export default defineConfig({
markdown: {
config(md) {
md.use(productCardMarkdownPlugin, {
showCreated: true, // 默认 true
showUpdated: true // 默认 true
})
}
}
})
.vitepress/theme/index.ts 中注册 Vue 组件import DefaultTheme from 'vitepress/theme'
import { registerProductCard } from 'vitepress-plugin-product-card/client'
export default {
extends: DefaultTheme,
enhanceApp({ app }) {
registerProductCard(app)
}
}
::: card 我的产品
- title: sugar-blog
desc: 简约风的 VitePress 博客主题
icon: 📝
iconColor: '#42b883'
link: https://theme.sugarat.top
github: https://github.com/ATQQ/sugar-blog
tags: [VitePress, Vue3, TypeScript]
----
- title: 另一个项目
desc: 单卡覆盖全局默认
github: https://github.com/some/repo
showCreated: false
:::
::: card 我的产品)可选,不写则不渲染分组标题---- 分隔- title: xxx 开头| 字段 | 类型 | 说明 |
|---|---|---|
title |
string |
卡片标题(必填) |
desc |
string |
卡片描述,支持行内 markdown |
icon |
string |
图标:URL / 相对路径 → 渲染 <img>;否则渲染为字符 |
iconColor |
string |
字符 icon 的背景色 |
link |
string |
点击标题跳转的地址 |
github |
string |
GitHub 仓库地址,自动拉取创建/更新时间 |
tags |
string[] |
标签数组 |
showCreated |
boolean |
覆盖全局,是否展示创建时间 |
showUpdated |
boolean |
覆盖全局,是否展示最后更新时间 |
| 参数 | 类型 | 默认值 | 说明 |
|---|---|---|---|
showCreated |
boolean |
true |
全局默认,是否展示创建时间 |
showUpdated |
boolean |
true |
全局默认,是否展示最后更新时间 |
componentName |
string |
ProductCard |
输出 html_block 使用的组件名,与 registerProductCard(app, name) 配合使用 |
@sugarat/theme 中使用
主题内置该插件,直接在文章里用 ::: card 即可。
2026-07-28 17:00:25
为我造过的轮子与写过的项目,书写传记
个人精力有限,感兴趣有能力的朋友可以帮忙迭代维护
2026-06-14 19:00:00
我服务器上有几十个 Web 站点,因为服务器配置较低,都是在本地完成构建后通过 scp 推到服务器,每个项目里就维护了一个 shell 脚本。
大概下面这样:(zx 脚本)
await $`scp ${compressPkgName} ${user}@${fullOrigin}:./`
await $`ssh -p22 ${user}@${fullOrigin} "tar -xf ${compressPkgName} -C ${destDir}"`
这个样子持续了差不多好多年,但始终感觉不优雅,最近刚好 AI 够强,就把一直想实现的一个部署的 CLI 实现了。
服务器只管收 + 解压 + 重启(如果带后端服务)。
于是有了 Kite —— 装一个 CLI 就能跑起 Web 管理端 + Server 后端 + 一键上传。
npm install -g @kitecd/cli
kite serve
启动后浏览器打开 http://127.0.0.1:5431 就是管理后台。
Starting Kite Server...
Runtime: bun v1.3.12
Host: 127.0.0.1
Port: 5431
Web Dir: /Users/sugar/Documents/fe/Kite/packages/cli/dist/web
DB Dir: /Users/sugar/.kite
Admin Token: admin_fb3635137
🦊 Server is running on bun at http://127.0.0.1:5431
🔑 Login Token: admin_fb3635137
线上部署可以通过 pm2,使用 NG 反向代理站点。
kite serve --pm2
只需要录入项目名和部署目即可
项目概览页提供了部署的指令复制即可。
本地项目里执行初始化指令,生成 kite.config.json。
kite init --project proj_669571accfa5 --out ./dist --server http://127.0.0.1:5431 --token kt_a6029f276c354caca86b65960804d22c
{
"projectId": "proj_669571accfa5",
"serverUrl": "http://127.0.0.1:5431",
"outputDir": "./dist",
"files": [
"**/*"
]
}
部署,执行 kite push 即可。
kite push 会自动完成:合并配置 → 打包 outputDir → 上传到 Server → 依次执行 preDeploy / 解压 / postDeploy。
kite push
如果一个项目需要推到多个服务器或者不同项目目录。
会自动扫描符合 kite.config.xx.json 的配置。 kite push --env xx 即可,或者交互式选择。
MiMo 完成了大概 80% 的代码,15% 是 GPT。
这个CLI服务端 支持 Bun 和 Node两个运行时,部分适配靠 GPT 搞定
流程:先使用 plan 模式把需求和实现的核心功能确定下来。
然后拆好 step ,让它挨着执行,然后配合给到的测试用例,验收一下。
然后就是 case by case 的修复问题。
下一个 VibeCoding 的新坑也开好了,猛猛的蹬。
欢迎评论区交流 & 拍砖。
2026-05-06 17:04:53
碎碎念...
本周上班干活的时候,笔记本屏幕直接黑了 GG。最开始我还以为是没电了,结果插上电,重启大法试了一下,屏幕也就只会亮1s。
不过外接显示器能正常用,庆幸核心没搞坏,那 8 成是屏幕相关的问题了,就拿去维修(2周左右要),换了一台旧电脑临时用一下。(公司电脑 💻 (#^.^#))
又得从头来一遍"装机",趁此机会搞个清单,下次就不用对着旧电脑搞了。
看合集直接划到最后😋
状态栏 日历/时间 小组件。平时排期就拿这个看时间。
还有复古的整点报时。
状态栏图标管理工具。
Mac 软件装多了,顶部状态栏里 各种杂七杂八的软件图标,有些不会用到,但占用着很烦。
用这个能方便的管理哪些展示哪些不展示。
都给我去装上 (^▽^)!
简单但强大的截图工具,用了都说好!
支持剪贴板内容转图,截图钉在屏幕上,截图历史回溯,这几个我最常用的功能,。
比较老牌的一个聚合工具平台。
笔者主要就用它内置的 剪贴板 功能,足够简洁,切性能比试用过的其它的专门的剪贴板软件要好。
Arc 浏览器,用习惯了非常方便,支持多端数据同步(不支持插件),支持多身份的设计。竖向的标签排列。
只可惜主创团队,现在不更新功能了,只更新 Chrome 内核版本,转向 Dia 的 AI 浏览器了
不过同事推荐说公司出的这个 AI浏览器也挺好用 Tabbit 浏览器,感兴趣的可以试试
调节显示器亮度,分辨率等非常方便。
日常拿来清理一下垃圾。不然过几天 500G 的盘又满了。
”可爱互动的桌面宠物应用“,基于 Tauri 制作,支持三端!
鼠标和键盘的动作,猫猫都会同步。
赶紧装上,敲键盘又多了一点乐趣
久坐提醒工具。
全屏展示的提醒喝水的软件!。
上班安排起来,给尿喝清亮!
画图神器,工作中拿来画流程图架构图必备。
录屏软件,支持导出多种常用格式,支持基础的编辑。
使用起来非常方便,软件也是开源github:kap的。
聚合翻译软件,速度也快,支持快捷唤起。
Mac,必备,安装软件和各种终端工具。
/bin/zsh -c "$(curl -fsSL https://gitee.com/cunkai/HomebrewCN/raw/master/Homebrew.sh)"
推荐选 Gitee 和 阿里的源。
强大又美观的终端工具,内置 AI 能力集成!
相较于传统的终端工具,其更加智能,编辑指令也非常方便。
笔者主要用这个终端输入提示的功能。
这个功能最早是在 Fig 里,后来被亚马逊收购了。
Mac垃圾清理 & 系统监控软件,开发者友好。
| 系统监控 | node_modules 清理 | 项目统计 |
|---|---|---|
聚合工具平台,笔者主要装了一些开发相关的插件。
代理调试抓包工具!
笔者就常用 TRAE 和 Cursor,自打AI大爆发以来,VS Code 直接不装了。
Node 版本管理工具!
快速切换目录的工具!
npm 镜像源管理。
yrm ls
* npm ---- https://registry.npmjs.org/
cnpm --- http://r.cnpmjs.org/
taobao - https://registry.npmmirror.com/
nj ----- https://registry.nodejitsu.com/
rednpm - http://registry.mirror.cqupt.edu.cn/
npmMirror https://skimdb.npmjs.com/registry/
edunpm - http://registry.enpmjs.org/
yarn --- https://registry.yarnpkg.com
npm i -g @antfu/ni
常用的 ni 装依赖,nr 运行 npm 指令。
使用 killport 可以一步到位,直接杀死占用端口的进程
文件管理软件,可以拿来替代系统默认的 Finder。支持双栏展示,移动和整理文件的时候也比较方便。
多设备之间相互传输文件。足够简单且好用。
| 序号 | 名称 | 介绍 | 链接 |
|---|---|---|---|
| 1 | Itsycal | 状态栏 日历/时间 小组件。平时排期就拿这个看时间。 | https://github.com/sfsam/Itsycal/tree/master |
| 2 | Bartender | 状态栏图标管理工具。 | https://www.macbartender.com/ |
| 3 | Snipaste | 简单但强大的截图工具,用了都说好! | https://www.snipaste.com/ |
| 4 | Alfred | 比较老牌的一个聚合工具平台。 | https://www.alfredapp.com/ |
| 5 | Arc | Arc 浏览器,用习惯了非常方便,支持多端数据同步(不支持插件),支持多身份的设计。竖向的标签排列。 | https://arc.net/ |
| 6 | BetterDisplay | 调节显示器亮度,分辨率等非常方便。 | https://github.com/waydabber/BetterDisplay |
| 7 | Tencent Lemon | 日常拿来清理一下垃圾。不然过几天 500G 的盘又满了。 | https://lemon.qq.com/ |
| 8 | BongoCat | ”可爱互动的桌面宠物应用“,基于 Tauri 制作,支持三端! | https://github.com/ayangweb/BongoCat |
| 9 | HealthTick | 久坐提醒工具。 | https://github.com/lifedever/health-tick-release?tab=readme-ov-file |
| 10 | Shui | 全屏展示的提醒喝水的软件!。 | https://github.com/rock-zhang/Shui |
| 11 | drawio | 画图神器,工作中拿来画流程图架构图必备。 | https://www.drawio.com/ |
| 12 | Kap | 录屏软件,支持导出多种常用格式,支持基础的编辑。 | https://getkap.co/ |
| 13 | Easydict | 聚合翻译软件,速度也快,支持快捷唤起。 | https://github.com/tisfeng/Easydict |
| 14 | Warp | 强大又美观的终端工具,内置 AI 能力集成! | https://www.warp.dev/ |
| 15 | Kiro CLI | 笔者主要用这个终端输入提示的功能。 | https://kiro.dev/downloads/ |
| 16 | EasyDevo | Mac垃圾清理 & 系统监控软件,开发者友好。 | https://easydevo.boringboring.design/ |
| 17 | uTools | 聚合工具平台,笔者主要装了一些开发相关的插件。 | https://www.u-tools.cn/ |
| 18 | Whistle Client | 代理调试抓包工具! | https://github.com/avwo/whistle-client |
| 19 | IDE | 笔者就常用 TRAE 和 Cursor,自打AI大爆发以来,VS Code 直接不装了。 | https://www.trae.ai/https://cursor.com/ |
| 20 | fnm | Node 版本管理工具! | https://github.com/Schniz/fnm |
| 21 | zoxide | 快速切换目录的工具! | https://github.com/ajeetdsouza/zoxide |
| 22 | yrm | npm 镜像源管理。 | https://github.com/i5ting/yrm |
| 23 | ni | 常用的 ni 装依赖,nr 运行 npm 指令。 |
https://github.com/antfu-collective/ni |
| 24 | killport | 使用 killport 可以一步到位,直接杀死占用端口的进程 | https://github.com/jkfran/killport |
| 25 | Marta | 文件管理软件,可以拿来替代系统默认的 Finder。支持双栏展示,移动和整理文件的时候也比较方便。 | https://marta.sh/ |
| 26 | LocalSend | 多设备之间相互传输文件。足够简单且好用。 | https://localsend.org/#/download |