把 Gitea 搬进编辑器,并让 AI 助手(CodeBuddy、Copilot、Cursor 等)直接操作 Gitea:
仓库、分支、Issue、Pull Request、通知、工作流 的读写全部通过同一套工具暴露给模型。
- 目标:Gitea 1.26.4(API 依据其 OpenAPI 规范
swagger.v1.json 逐项核对)
- 侧边栏 4 个视图:仓库 / 我的 Issue / 我的 Pull Request / 通知
(仓库节点下含 工作流 分组:工作流定义、最近运行;点作业 / 工作流都在只读标签里看)
- 「仓库」视图按组织分组、当前工作区仓库置顶,仓库名右侧带铭牌
(语言 / 分支数 / 开放 Issue·PR / 工作流状态)
- 仓库可按名称或
owner/repo 搜索(走服务端,覆盖全部仓库而非仅已加载的)
- 列表支持**「加载更多」**,没有条数硬上限
- Issue / PR 详情交互面板:回复、关闭、重新打开、评审、合并、检出分支
- 工作流(Gitea Actions):列出工作流定义与运行记录、触发 / 重跑、在主窗口的
只读标签里查看工作流定义与作业日志(可查找、可并排对比)
- 29 个编辑器命令 + 35 个 AI 工具
- 两种 AI 接入方式:MCP Server(stdio)与 语言模型工具(
vscode.lm.registerTool)
一、快速开始
安装
推荐从扩展市场安装,这样能自动收到更新:
| 客户端 |
装哪里 |
命令 / 页面 |
| CodeBuddy(CN / 国际版) |
Open VSX |
code --install-extension echo-note.gitea-toolkit · 页面 |
| VS Code |
VS Code Marketplace |
code --install-extension echo-note.gitea-toolkit · 页面 |
也可以在各自客户端的扩展面板里直接搜索 Gitea Toolkit。
CodeBuddy 的扩展源是 Open VSX,VS Code 用的是微软官方 Marketplace —— 两者是不同的注册表,
但扩展 ID 相同,所以在各自客户端里用同一条命令即可。
也可以从 Releases 手动下载 .vsix
(离线环境、或想固定版本时):
# VS Code
code --install-extension gitea-toolkit-<版本>.vsix
# CodeBuddy(CN 版)
"/Applications/CodeBuddy CN.app/Contents/Resources/app/bin/code" \
--install-extension gitea-toolkit-<版本>.vsix
核对下载是否与 CI 产出一致:
shasum -a 256 -c SHA256SUMS
手动装 vsix 不会被市场自动更新(编辑器只跟踪从市场安装的扩展)。
想持续拿更新请改用上面的市场安装方式。
Release 只有 .vsix 与 SHA256SUMS 两个附件,不额外提供 zip:
.vsix 本身就是 deflate 压缩的 zip,再套一层只会变大,且 --install-extension 不认 zip。
另外注意,SHA256SUMS 与 vsix 同源生成,只能证明「文件与 CI 产出一致」,
防不了「两者被一起替换」——它不构成防篡改的信任根。
配置
- 命令面板执行
Gitea: 设置访问令牌
- 首次会要求填写实例地址(例如
https://gitea.example.com)
- 令牌在 Gitea 的「设置 → 应用 → 生成令牌」中创建,需勾选
repo、issue、notification 权限
- 令牌保存在系统钥匙串(
SecretStorage),不会写入 settings.json
- 左侧活动栏出现 Gitea 图标,展开即可浏览仓库、Issue、PR 与通知
内网自签名证书场景:把 gitea.verifyTls 设为 false。
更新扩展
分两种情况:
| 安装方式 |
更新方式 |
| 从扩展市场安装(推荐) |
编辑器自动更新,无需任何操作 |
手动装 .vsix |
编辑器不跟踪这类安装,不会自动更新 → 靠扩展内置的检查兜底 |
内置检查(主要服务第二种情况):
- 激活后每天自动查一次 GitHub Releases(配置项
gitea.checkUpdates,默认开启)
- 也可随时手动执行
Gitea: 检查更新
- 发现新版本时可查看变更,或下载
.vsix(下载后建议按「安装」一节核对 SHA256SUMS)
它靠「版本比对」而非「渠道探测」来避免打扰市场用户。 这一点是实测结论:
VS Code 没有公开 API 能判断扩展的安装来源;而内部的 extensions.json 里
metadata.source 实测一律为 gallery —— 两个客户端共 54 个扩展全部如此,
包括明确用 --install-extension <vsix> 安装的那一个。所以渠道不可检测。
改用版本比对达到同样效果,且不依赖任何探测:
- 市场安装的:编辑器把它更新到最新后,检查自然得出「已是最新」→ 静默
- 手动安装的:版本一直停在旧的 → 提示
又因为 CI 在同一个 job 里把同一个版本发到两个市场与 GitHub Releases,
GitHub 不会系统性领先市场,因此不存在「两条通道给出矛盾结论」的问题。
别混淆这两个命令:
Gitea: 检查更新 查的是扩展自身的版本;
Gitea: 检查版本兼容性 查的是服务端 Gitea 的版本与本扩展已核对版本的差异。
自动检查的节流:「上次检查时间」只在请求成功后写入(网络抖动不会白等一天);
同一个新版本只提示一次;除手动触发外,任何失败都只写日志、不打扰用户。
版本兼容性校验
本扩展所有接口调用以 Gitea 1.26.4 的 OpenAPI 规范逐项核对(常量定义在
src/core/version.ts)。服务端版本与已核对版本不一致时会弹窗告警:
| 服务端版本 |
等级 |
行为 |
主次版本与 1.26 一致(如 1.26.4、1.26.10、v1.26.4+dev) |
ok |
静默通过 |
高于已核对版本(如 1.27.0、2.0.0) |
newer |
弹窗提示「接口可能已变化」 |
| 低于已核对版本但 ≥ 1.21.0(LTS) |
older |
弹窗提示「部分新接口能力可能不可用」 |
| 低于 1.21.0 |
unsupported |
弹窗提示「版本过低,功能可能异常」,建议升级服务端 |
| 无法解析 |
unknown |
弹窗提示「请自行确认兼容性」 |
两个 MCP 实现(TypeScript 与 Python)用同一套判定,但把结论交给 AI:
MCP 里没有弹窗,唯一能到达模型的通道是工具返回。所以:
- 首次调用工具时自动读一次
/version,判定不兼容就在返回开头插一段提示,
且开头明确写着「请转达给用户」——否则模型很容易只把它当背景信息;
- 同一进程内只插一次(版本在一次会话里不会变,反复重复既费 token 又会被当噪音);
- 兼容时不插任何内容。想随时复查就用
gitea_get_current_user,它的返回里始终带
一行「服务端版本|兼容性」;
- 两个实现都会把版本与判定写进 stderr 日志,但绝不因此让工具调用失败:
读不到版本(网络不通、反代拦了
/version)就安静跳过。
为什么不在启动时检查:MCP Server 是客户端拉起后立刻握手的,启动期发网络请求会把
「实例连不上」升级成「服务起不来」。探测属于锦上添花,不该拖累启动,因此放在首次
工具调用时惰性进行。
校验时机:
- 登录时(
Gitea: 设置访问令牌 校验令牌成功后)强制重新校验,不受历史记录影响
- 扩展激活时后台静默校验——令牌长期有效,但服务端可能被升级,此时同样需要提醒
- 手动随时可用
Gitea: 检查版本兼容性
避免打扰:同一「等级 + 版本」只提示一次(记录在 globalState);
服务端升到新版本后会重新提示。状态栏 tooltip 与「显示当前登录用户」对话框都会展示
服务端版本与已核对版本,便于随时比对。
校验只比较主次版本:同一 major.minor 下的补丁差异视为兼容(Gitea 补丁版本不做破坏性变更)。
二、日常交互:怎么回复、怎么关闭
所有写操作都在「详情面板」里完成——侧边栏只负责定位,点开即进入一个可读可写的详情页
(编辑器标签页形式,可左右分屏对照代码)。
打开详情面板
| 入口 |
操作 |
| 侧边栏 |
在 我的 Issue / 我的 Pull Request / 仓库下的条目上单击(或点悬浮的预览图标) |
| 右键菜单 |
「Gitea: 打开 Issue 详情面板」/「打开 Pull Request 详情面板」 |
| 快捷回复 |
悬浮的 💬 图标,或右键「Gitea: 在详情面板中回复」→ 直接聚焦输入框 |
| 命令面板 |
Gitea: 在详情面板中回复(需先在树中选中节点) |
面板里能做什么
┌────────────────────────────────────────────────────────────────┐
│ [#42](https://github.com/Echo-Note/gitea-toolkit/issues/42) 修复登录接口超时问题 [刷新] [浏览器] │
│ [进行中] @zhangsan 创建于 2 天前 · 更新于 3 小时前 │
│ fix/login-timeout → main +120 -30 3 个文件 │
│ [bug] [优先级/高] @lisi 里程碑: v1.2 │
├────────────────────────────────────────────────────────────────┤
│ 正文(Gitea 服务端渲染,Markdown / 任务列表 / @提及 与网页一致) │
│ 变更文件 / 评审记录(PR) │
│ 评论时间线 │
├────────────────────────────────────────────────────────────────┤
│ 写下回复…(支持 Markdown,Ctrl/Cmd + Enter 快速发表) │
│ [发表评论] [评论并关闭] Ctrl/Cmd + Enter 快速发表 │
│ [merge ▾] [批准] [请求修改] [合并] [检出分支] [关闭] │
└────────────────────────────────────────────────────────────────┘
| 我想做的事 |
怎么做 |
| 回复 |
在底部输入框写 Markdown → Ctrl/Cmd + Enter 或点「发表评论」 |
| 回复并顺手关闭 |
写好内容后点「评论并关闭」,一次请求完成两件事 |
| 关闭 Issue / PR |
点「关闭」;已关闭的条目会变成「重新打开」 |
| 批准 / 请求修改 |
先在输入框写理由(请求修改必填),再点「批准」或「请求修改」 |
| 合并 PR |
选好 merge / squash / rebase / rebase-merge → 点「合并」→ 扩展会再弹一次确认 |
| 检出 PR 分支 |
点「检出分支」,自动执行 git fetch origin <分支> + git checkout <分支> |
| 跳转到网页 |
点右上角「浏览器」,或点正文/评论里的任意链接(会在系统浏览器打开) |
草稿不会丢:输入内容会随面板状态持久化,切走标签页再回来仍在;面板隐藏期间关闭编辑器也不影响。
引用代码:需要贴大段代码或引用当前文件时,可先用编辑器的「复制」再粘贴;
Markdown 代码块、表格、任务列表都由 Gitea 服务端渲染,与网页端表现一致。
不需要打开面板的快捷操作
| 场景 |
入口 |
| 关闭 / 重新打开 |
树节点悬浮的 $(issue-reopened) 图标,或右键菜单 |
| 看 PR 代码差异 |
树节点悬浮的 $(diff) 图标 → 以 diff 语法高亮的编辑器标签打开 |
| 合并 PR |
树节点右键「Gitea: 合并 Pull Request」(选策略 → 确认) |
| 检出 PR 分支 |
树节点右键「Gitea: 检出 Pull Request 分支」 |
| 新建 Issue / PR / 分支 / 仓库 |
视图标题栏的 + 图标,或仓库节点右键菜单 |
| 全部通知标为已读 |
「通知」视图标题栏的 $(check-all) 图标 |
三、命令一览
| 分类 |
命令 |
| 认证 |
Gitea: 设置访问令牌、Gitea: 清除访问令牌、Gitea: 显示当前登录用户、Gitea: 检查版本兼容性 |
| 通用 |
Gitea: 刷新所有视图、Gitea: 在浏览器打开、Gitea: 显示日志、Gitea: 检查更新、Gitea: 搜索仓库、Gitea: 清除仓库搜索、Gitea: 加载更多 |
| 仓库 |
Gitea: 克隆仓库到工作区、Gitea: 新建仓库、Gitea: 新建分支 |
| Issue / PR |
Gitea: 新建 Issue、Gitea: 打开 Issue 详情面板、Gitea: 回复 Issue / Pull Request、Gitea: 在详情面板中回复、Gitea: 关闭 / 重新打开 |
| Pull Request |
Gitea: 新建 Pull Request、Gitea: 打开 Pull Request 详情面板、Gitea: 查看 Pull Request 差异、Gitea: 合并 Pull Request、Gitea: 检出 Pull Request 分支 |
| 工作流 |
Gitea: 查看工作流定义、Gitea: 查看作业日志、Gitea: 触发工作流、Gitea: 重跑工作流 |
| 通知 |
Gitea: 标记通知为已读、Gitea: 全部标记通知为已读 |
| AI 接入 |
Gitea: 复制 MCP 配置到剪贴板、Gitea: 写入 CodeBuddy MCP 配置、Gitea: 写入 MCP 配置文件(工作区) |
点击 Gitea 状态栏条目可打开快捷菜单。
四、接入 AI 助手
扩展提供两条互相独立的接入路径,可同时启用:
| 路径 |
实现方式 |
适用客户端 |
| MCP Server |
独立 stdio 子进程,暴露 35 个工具 |
CodeBuddy、VS Code 及任意 MCP 客户端 |
| 语言模型工具 |
vscode.lm.registerTool,常驻扩展宿主 |
VS Code 系 |
MCP Server:按客户端选接入方式
前提:先在「一、快速开始」里配好实例地址与访问令牌,否则服务能出现但调不通(会提示未设置令牌)。
| 客户端 |
怎么让它出现 |
| CodeBuddy |
不消费 VS Code 的 MCP 贡献点,必须落盘 → 执行 Gitea: 写入 CodeBuddy MCP 配置 |
| VS Code |
支持 MCP Definition Provider → 扩展激活后自动出现在 MCP 面板,无需落盘 |
| 其它 MCP 客户端 |
不必装扩展:用独立包 @echo-note/gitea-toolkit-mcp(发布在公共 npm,直接 npx,见下) |
这一点实测确认过:CodeBuddy 的 MCP 面板完全由 ~/.codebuddy/mcp.json 驱动。
即使扩展已经在 package.json 声明 contributes.mcpServerDefinitionProviders
并成功调用了 vscode.lm.registerMcpServerDefinitionProvider,面板里依然不会出现该服务。
CodeBuddy
命令 Gitea: 写入 CodeBuddy MCP 配置 会把 mcpServers.gitea 合并写入用户级配置
(优先写已存在的 ~/.codebuddy/mcp.json,不动其它 server)。随后在 CodeBuddy 的
MCP 面板刷新(或重启编辑器),即可看到名为 gitea 的服务。
升级后不用手工改路径:扩展安装目录名含版本号,升级后旧路径会失效、表现为该服务启动失败。
扩展每次激活都会检查用户级配置里指向本扩展的脚本路径,发现过期就静默改写
(仅在该文件已存在、且其中确有本扩展的条目时才动,不会凭空创建配置)。
VS Code
VS Code 支持 MCP Definition Provider,扩展注册后无需落盘:
// package.json
"contributes": {
"mcpServerDefinitionProviders": [{ "id": "giteaToolkit_mcp", "label": "Gitea Toolkit (MCP)" }]
}
// 运行时注册,VS Code 会自动发现并加载工具
vscode.lm.registerMcpServerDefinitionProvider('giteaToolkit_mcp', { ... });
访问令牌在「服务器即将启动」回调中才注入,不会长期停留在配置对象里。
其它 MCP 客户端:用独立包(无需装扩展)
MCP Server 已作为独立包 @echo-note/gitea-toolkit-mcp 发布,不依赖 VS Code 扩展,
任何支持 stdio 的 MCP 客户端均可接入。
① 公共 npm registry(推荐,命令最短、无需任何认证)。
npx -y @echo-note/gitea-toolkit-mcp --url https://gitea.example.com --token <令牌>
{
"mcpServers": {
"gitea": {
"command": "npx",
"args": ["-y", "@echo-note/gitea-toolkit-mcp", "--url", "https://gitea.example.com"],
"env": { "GITEA_TOKEN": "你的令牌" }
}
}
}
② 备选:Release 里的 tarball(适用于访问不了 npm registry 的环境)。
npm/npx 支持直接执行远程 tarball,而 GitHub Release 的附件下载是公开、免认证的:
npx -y https://github.com/Echo-Note/gitea-toolkit/releases/latest/download/gitea-toolkit-mcp.tgz \
--url https://gitea.example.com --token <令牌>
想锁定版本就把 latest 换成具体 tag,例如 releases/download/v0.9.0/gitea-toolkit-mcp.tgz。
0.7.0–0.8.5 期间曾发在 GitHub Packages,但那个源匿名装不了 ——
连公开包也要求先配 PAT 与 ~/.npmrc;而且 package-lock.json 会硬编码 registry 地址,
一旦提交,协作者 npm install 就会因为没有令牌而失败。
0.9.0 起改回公共 registry,上面这一行即可。
--help 可查看全部参数(--no-verify-tls、--timeout、--max-output),命令行参数优先于环境变量。
包目录里的 README 有 Claude Desktop / Cursor 的完整示例。
如果连 npx 都不想用:改用「装扩展」的方案 —— 扩展本身已经把 MCP Server 装好了,
配置命令是 Gitea: 写入 CodeBuddy MCP 配置 或 Gitea: 复制 MCP 配置到剪贴板。
独立包的价值仅在于完全不想装 VS Code 系编辑器的场景。
与扩展同源同构建:共用 src/core 与工具定义;esbuild.js 一次构建后把同一份产物
复制给扩展与 npm 包两侧(内容字节一致,只是落点不同),因此两边行为不会漂移。
版本号由 scripts/bump-version.mjs 强制同步,CI 发布前还会校验两者一致,不一致直接失败。
其它客户端与工作区级配置
命令 Gitea: 写入 MCP 配置文件(工作区) 会把配置写进当前工作区,可选:
.codebuddy/mcp.json ← CodeBuddy 项目级配置
.vscode/mcp.json
.mcp.json
适合「这个仓库用这套 Gitea 配置」的场景;只想全局生效就用上面的用户级命令。
写入采用合并语义,只覆盖 mcpServers.gitea,不会破坏你已有的其他 MCP 配置。
也可以 Gitea: 复制 MCP 配置到剪贴板 后粘贴到 CodeBuddy 的「MCP → Add MCP」面板。
生成的配置形如:
{
"mcpServers": {
"gitea": {
"type": "stdio",
"command": "/Applications/Visual Studio Code.app/Contents/MacOS/Electron",
"args": ["/path/to/extension/dist/mcpServer.js"],
"env": {
"ELECTRON_RUN_AS_NODE": "1",
"GITEA_SERVER_URL": "https://gitea.example.com",
"GITEA_TOKEN": "***",
"GITEA_VERIFY_TLS": "true"
}
}
}
}
服务介绍
⚠️ 本节与下一节的标题名是平台要求的字段名,不要改。把本项目提交到 MCP 广场
(魔搭 ModelScope 等)时,「从 GitHub 仓库快速创建」会从仓库根 README 里按名字提取
「服务介绍」与「服务配置」,解析不到会直接中断快速创建。npm run check:mcp-manifest
会守住这两段(已接进 npm run ci)。
Gitea Toolkit MCP 把自建 Gitea 的仓库、Issue、Pull Request、通知与 Gitea Actions 能力
暴露给任意 MCP 客户端(CodeBuddy / Claude Desktop / Cursor 等):列出与检索仓库、读写文件、
查看提交历史、创建与更新 Issue、创建 / 评审 / 合并 PR、查 CI 运行记录与作业日志、管理通知,
共 35 个工具。
它必须连到你自己的 Gitea 实例(地址 + 访问令牌)才能工作 ——
因此适合本地运行,不适合由平台托管部署。
服务配置
{
"mcpServers": {
"gitea": {
"command": "npx",
"args": ["-y", "@echo-note/gitea-toolkit-mcp@latest", "--url", "https://gitea.example.com"],
"env": { "GITEA_TOKEN": "你的访问令牌" }
}
}
}
把 --url 换成你的实例地址、GITEA_TOKEN 换成访问令牌即可。包名带 @latest,
这样每次接入都拿到最新版;想用 Python 实现就把 command 改成 uvx、
包名改成 gitea-toolkit-mcp@latest(详见 python/README.md)。
语言模型工具
扩展注册 27 个语言模型工具(giteaToolkit_gitea_*),
在 Craft / Agent 模式下输入任务即可被自动调用,例如:
帮我在 team/demo 建一个 Issue:登录接口在弱网下超时,标签 bug,指派给 lisi
看一下 #42 这个 PR 的 diff,如果有问题就发一条 review 请求修改,否则批准
帮我基于 main 建一个 feature/login-timeout 分支,提交 README 变更,然后开一个 PR 指派 lisi 评审
关闭 #7,并在下面回一条「已在新版本修复」
写操作会强制确认:gitea_create_*、gitea_update_issue、gitea_merge_pull、
gitea_review_pull、gitea_commit_file 等在 VS Code 语言模型工具路径下会先弹出确认卡片,
展示完整入参,避免 AI 误改线上数据。
校验连通性
Gitea: 显示当前登录用户 可用于校验令牌与实例连通性(也会写入日志)。
五、AI 工具清单(35 个)
未显式传 owner / repo 时,会从当前工作区的 git origin 远端 自动推断仓库,
所以在 Gitea 仓库里直接提问即可,不必每次重复仓库名。
| 域 |
工具 |
| 仓库(10) |
gitea_list_repos、gitea_get_repo、gitea_create_repo、gitea_list_branches、gitea_create_branch、gitea_list_commits、gitea_list_files、gitea_get_file、gitea_commit_file、gitea_get_commit_status |
| Issue(6) |
gitea_list_issues、gitea_get_issue、gitea_create_issue、gitea_update_issue、gitea_comment_issue、gitea_list_issue_comments |
| Pull Request(7) |
gitea_list_pulls、gitea_get_pull、gitea_get_pull_diff、gitea_list_pull_files、gitea_create_pull、gitea_merge_pull、gitea_review_pull |
| 账号 / 通知(4) |
gitea_get_current_user、gitea_list_orgs、gitea_list_notifications、gitea_mark_notifications_read |
| 工作流(8) |
gitea_list_workflows、gitea_list_action_runs、gitea_get_action_run、gitea_get_job_logs、gitea_list_artifacts、gitea_dispatch_workflow、gitea_rerun_action、gitea_set_workflow_enabled |
gitea_create_issue / gitea_update_issue 的 labels 接受标签名称(大小写不敏感),
扩展会自动解析为 Gitea 需要的标签 ID,无法识别的标签会被忽略并在结果中说明。
六、配置项
| 配置 |
默认值 |
说明 |
gitea.serverUrl |
空 |
实例地址。刻意留空,首次执行「设置访问令牌」时会引导填写 |
gitea.defaultOwner |
空 |
新建仓库时的默认组织 |
gitea.verifyTls |
true |
是否校验 HTTPS 证书 |
gitea.requestTimeoutMs |
20000 |
请求超时 |
gitea.pageSize |
50 |
列表类视图总共展示多少条(1–200,超出会自动翻页补齐) |
gitea.enableMcpServer |
true |
是否启用内置 MCP Server |
gitea.enableLanguageModelTools |
true |
是否注册语言模型工具 |
gitea.writeCodeBuddyConfigOnActivate |
false |
激活时自动写入工作区 .codebuddy/mcp.json(仅当文件不存在时) |
gitea.checkUpdates |
true |
每天检查一次扩展自身的新版本(给手动装 .vsix 的用户兜底,见「更新扩展」) |
另有 gitea.ignoreCertificates,是 gitea.verifyTls 的反向兼容别名(已标记废弃),
仅为兼容旧配置保留,新配置请一律使用 gitea.verifyTls。
七、开发
npm install
npm run build # 构建 dist/extension.js 与 dist/mcpServer.js(不递增版本号)
npm run watch # 监听模式
npm run compile # tsc --noEmit 类型检查
npm run lint # ESLint
npm run check:tools # CI:校验 package.json 工具清单与代码定义是否一致
npm run check:icons # CI:校验所有 codicon 图标名真实存在
npm run check:changelog # CI:校验 CHANGELOG 无未填占位、版本号与 package.json 一致
# 图标(VS Code 对拼错的图标名是静默忽略的,必须能离线核对,见下文「图标」一节)
npm run preview:icon # 生成插件图标预览页(不同尺寸 / 明暗主题)
npm run preview:tree # 生成侧边栏图标预览页(真实 codicon 字体)
# 打包(会先把版本号 +1,再产出 .vsix)
npm run package # patch +1:0.1.2 → 0.1.3
npm run version:minor # 手动递增:minor +1
npm run version:major # 手动递增:major +1
npm run version:bump -- --set=0.2.0 --dry-run # 指定版本 / 预演
SKIP_VERSION_BUMP=1 npm run package # 本次打包不递增
为什么只有 package 递增版本号:反复用同一个版本号打包,本地安装时 VS Code 会认为
版本未变而不更新;但开发构建(build / watch)触发频繁,若每次都递增会让版本号迅速失真。
package 除递增 package.json 外,还会在 CHANGELOG.md 顶部幂等地插入新版本区块
(已存在该版本标题则跳过),避免漏记版本。递增后的 package.json 与 CHANGELOG.md
属于源码改动,需要一并提交。
打包脚本本身不调用 esbuild —— 由 vsce 触发的 vscode:prepublish 统一负责,避免重复构建。
CI 与发版流程
.github/workflows/ci.yml 是唯一的流水线(分为 verify / package / release / 失败处理四组 job):
| 时机 |
行为 |
push 到 main、面向 main 的 PR |
校验(tsc / ESLint / 工具清单 / 图标名)→ 构建打包 → 上传 .vsix artifact |
package.json 的版本还没有对应 tag |
自动创建 v{版本}(指向本次构建的提交)并发布 Release,附件为 .vsix |
| 版本号未变更 |
只产出 artifact,不发布 |
main 上任何 job 失败 |
自动创建 issue(含失败步骤、提交、运行链接、本地复现命令);同一问题追加评论而非重复建 |
main 恢复通过 |
自动关闭遗留的失败 issue |
所以「发版」就等于「升级版本号并推送」,tag 与 Release 都由流水线依据 package.json
的版本创建,不需要手工 git tag:
npm run version:patch # 0.1.4 → 0.1.5,并在 CHANGELOG 顶部插入新区块
# 填写 CHANGELOG 后:
git add -A && git commit -m "chore: 发布 v0.1.5" && git push
未采用「每次 push 都自动发版」是有意的:那会让版本号随每次提交增长,Release 也变成噪音。
若确实需要,去掉 release job 的 if 条件并补一步 npm run version:patch + 提交即可,
提交信息需带 [skip ci] 以免流水线自我触发。
workflow 本身也有校验:npm run check:workflows 会检查 YAML 结构、内嵌 shell 与
github-script 的语法,以及 permissions 是否覆盖了代码里调用的 REST API
(显式声明 permissions 后,未列出的 scope 会被置为 none,漏掉就是 403)。
按 F5 启动「运行扩展」调试配置即可加载扩展。
关于 repository 字段:0.2.0 之前 package.json 刻意不声明它,理由是「避免把发布信息
绑定到某个私有仓库」,代价是打包必须加 --allow-missing-repository。
0.2.0 起改为显式声明(指向公开仓库),原因有二:更新检查需要从它推导 GitHub 坐标;
上架扩展市场时 vsce / ovsx 也会用它来修正 README 中的相对链接。
该参数已从 npm run package 移除。扩展 ID 与版本号仍一律从
ExtensionContext 读取(context.extension.id / context.extension.packageJSON),
源码中不存在硬编码的扩展标识。
调试详情面板时,命令面板执行 Developer: Open Webview Developer Tools 可查看面板日志。
发布到扩展市场
两个市场都要发,因为它们服务完全不同的客户端:
| 市场 |
谁在用 |
发布工具 |
| Open VSX |
CodeBuddy(CN 与国际版)、VSCodium、Gitpod、code-server 等 |
ovsx |
| VS Code Marketplace |
微软官方 VS Code |
vsce |
实测确认:CodeBuddy 的 product.json 中 extensionsGallery.serviceUrl 指向
https://open-vsx.org/vscode/gallery。只发 MS Marketplace,CodeBuddy 用户看不到这个扩展;
反过来只发 Open VSX,VS Code 用户也搜不到(VS Code 默认只查微软官方市场)。
两个市场的清单要求当前都已满足:publisher、icon(256×256 PNG,市场禁 SVG)、
repository、license、README.md、CHANGELOG.md,keywords 6 个(上限 30),
文档中无图片引用(市场要求 README / CHANGELOG 里的图片必须是 https 且非 SVG)。
发到 Open VSX(CodeBuddy 用户走这条)
# 1. 注册 Eclipse 账号 https://accounts.eclipse.org/user/register
# 其中的 GitHub Username 必须与登录 open-vsx.org 用的 GitHub 账号一致
# 2. 登录 https://open-vsx.org,在 Profile 页签署 Publisher Agreement
# 3. 生成访问令牌 https://open-vsx.org/user-settings/tokens(只显示一次,注意保存)
npx ovsx create-namespace echo-note -p <TOKEN> # 命名空间必须等于 package.json 的 publisher
npx ovsx publish gitea-toolkit-<版本>.vsix -p <TOKEN>
发到 VS Code Marketplace
# 1. https://marketplace.visualstudio.com/manage 创建 publisher,ID 必须为 echo-note
# 2. Azure DevOps 建 PAT:Organization 选 All accessible organizations,
# 作用域 Custom defined → Marketplace → Manage
npx vsce login echo-note # 粘贴 PAT
npx vsce publish --packagePath gitea-toolkit-<版本>.vsix
已接入 CI:发版时自动上架两个市场(外加独立 MCP 包)
发布由四个 job 完成,无需手工执行上面的命令:
resolve 解析版本号 + 判断该版本是否已发(看 Release 是否存在)
├─ publish-ovsx 发布到 Open VSX ┐
├─ publish-vsce 发布到 VS Code Marketplace ├ 三个并行执行
└─ publish-npm 发布到 npm(公共 registry) ┘
release 三个都成功后才创建 tag 与 Release
为什么拆成独立 job:放在同一个 job 里当连续三步的话,不只是串行,而且
任一步失败会让后面的步骤被整体跳过 —— 例如 Open VSX 报错会导致 Marketplace
压根不去尝试发布。拆开后每个市场独立成败、互不牵连,也更快(三个网络请求并行)。
启用方式(仓库 Settings → Secrets and variables → Actions):
| secret |
值 |
对应的目标 |
OVSX_PAT |
open-vsx.org 生成的访问令牌 |
Open VSX |
VSCE_PAT |
Azure DevOps PAT(Organization 须选 All accessible organizations,作用域须含 Marketplace → Manage) |
VS Code Marketplace |
NPM_TOKEN |
不要配(首次发布已完成,包已存在于 npm)。它只是"包还不存在时"的过渡手段,权限也过宽,npm 在令牌页会警告其安全风险(它是对的);正式做法是用下面的 OIDC 可信发布、完全不配此项 |
npm(公共 registry) |
设计上的三点(三个目标一致):
- 未配置 secret 时静默跳过、不阻断发版(只打一条 notice,并在运行摘要里说明)。
所以这段逻辑可以先合入,等你拿到 token 再补 secret,不需要再改 workflow。
反过来,只要 secret 存在,任何发布失败都会让那个 job 失败 —— 不会让你误以为已经上架。
- 刻意排在「创建 Release」之前。本流程用「
v<版本> 的 Release 是否存在」判断该版本
是否已发;若先建 Release 再发市场,一旦市场发布失败,重跑时该判断会变成「已发」,
三个市场被整体跳过,这个版本就永远上不了架。反过来则能自愈:
市场发成功 → 建 Release 失败 → 重跑时市场步骤幂等跳过,只补 Release。
- 幂等:发布前查询该版本是否已存在,已存在就跳过(失败重跑的常见场景)。
各家的查询接口脾气不同,都不能只看发布命令的退出码:
- Open VSX:
/api/<ns>/<name>/<版本> 在「扩展不存在」时返回 503 而非 404,且偶发抖动
- Marketplace:
extensionquery 接口查询失败时保守放行,真正的重复由 --skip-duplicate 兜住
npm 发布也支持 OIDC 可信发布(推荐,无需长期令牌):在 npmjs.com 的包设置 →
Trusted publishing 里添加 GitHub Actions,仓库填 Echo-Note/gitea-toolkit、
工作流文件名填 ci.yml(要与实际文件名完全一致),然后删掉 NPM_TOKEN secret 即可。
注意四点:① 可信发布要求包已存在 —— 首次必须用令牌或本地手动发一次;
② 本地手动发的 2FA 交互取决于账号的 2FA 方式:
安全密钥 / 通行密钥 → npm 会自动打开浏览器完成认证(实测:直接 npm publish --access public
即可,不需要任何额外参数,终端会打印 Authenticate your account at: …);
TOTP 应用 → 带 --otp=<6 位码>。
若既没弹提示、又直接 403,说明当前凭据无法完成 2FA 交互 —— 重新 npm login
(走浏览器 / 安全密钥流程)后重试,别急着去建长期令牌;
③ npm 正在收紧 bypass-2FA 令牌的自动化发布能力(目标 2027 年 1 月,届时它只能"暂存发布"
再由维护者 2FA 批准),所以 CI 应尽快迁到 OIDC —— 迁完就没有长期密钥,"过期"这回事也就不存在了。
另外它会自动生成溯源证明,此时 repository.url 必须与仓库地址完全匹配;
④ 绑定 GitHub 账号不是 2FA —— npm 官方文档明确:GitHub 只是「账号恢复」的关联身份
(万一丢了 2FA 设备,靠它加速找回),发布时它不顶替验证码/TOTP;
自己账号的 2FA 类型与状态可在 npmjs.com → 头像 → Account → Two-Factor Authentication 查看。
组织成员还可能被强制启用 2FA(自己关不掉)。
⚠️ 排查 2FA 时别用恢复码登录:会触发账号 72 小时临时安全冻结,
期间无法发布包、创建令牌或改账号设置。
⚠️ 若确实要建「绕过 2FA」的令牌 —— npm 会在令牌页警告它的安全风险,那是对的:
能拿到 6 位码就优先用 --otp,根本不用建令牌。非要建时:权限只给 @echo-note 的
read + write(包尚不存在时无法选单个包,只能按 scope 授权)、有效期选最短、
发布成功后立刻撤销,正式通道仍应迁到 OIDC。
任一发布渠道失败 = 整体失败(别改回"降级跳过"):发布 job 一失败,release 就会跳过、
不建 tag 与 Release,CI 也按失败上报。这是刻意的 —— 实测过"降级为跳过"的坏处:job 显示
success 却根本没发布,反而分不清成败。失败即失败还有个好处:Release 没建出 ⇒ 该版本仍未
"发过" ⇒ 下次推送会自动重试,天然自愈。
⚠️ 另有一条硬性前置条件:可信发布(OIDC)需要 npm CLI ≥ 11.5.1
(官方原文:Trusted publishing requires npm CLI version 11.5.1 or later and Node version
22.14.0 or higher)。而 node-version: '22' 自带的是 npm 10.x —— npm 10 根本不尝试
OIDC,会发出无身份请求,npm 按"查无此包"返回 404,与"没配可信发布"的报错一模一样。
本项目就在这上面栽了很久,所以工作流在发布前会 npm install -g npm@latest。
Release 说明直接取自 CHANGELOG(不再用 gh release create --generate-notes 的自动摘要):
后者按 commit / PR 罗列,与 CHANGELOG 里那份有分类、有原因、有实测数据的说明完全是两回事。
现在「扩展市场的 Changelog 标签页」「.vsix 里的 CHANGELOG」「Release 说明」三者同源,
不会再出现改了一处忘了一处。
⚠️ 已发布的历史 Release 不会自动更新。需要回填某个版本时:
gh release edit v0.8.1 --notes-file <(node scripts/release-notes.mjs --version 0.8.1)
首次上架前必须先做(否则对应步骤会失败):
# Open VSX:命名空间必须等于 package.json 里的 publisher,且需先签署 Publisher Agreement
npx ovsx create-namespace echo-note -p <TOKEN>
# Marketplace:在 https://marketplace.visualstudio.com/manage 创建 publisher,ID 必须为 echo-note
⚠️ Marketplace 的认证方式有硬时限:Azure DevOps 全局 PAT 将于 2026-12-01 完全停用,
而 Marketplace 要求的正是这一类(Organization 必须选 All accessible organizations,
选单一组织会 403/401)。届时需迁移到 Entra ID:
vsce publish --azure-credential(需 vsce ≥ 2.26.1),但需要 Azure 订阅 + 托管标识 + 服务连接,
是独立的一块工作。Open VSX 的令牌没有这个问题。
注意:这些 job 要到下一次版本递增才会真正发布。仅提交 workflow 改动不会触发发版
(resolve 会看到 v<当前版本> 的 Release 已存在,各发布步骤随之跳过),
这是有意设计,不是故障。
三个坑
publisher 上架后不可更改。它同时是 Marketplace 的 publisher ID 和 Open VSX 的命名空间;
一旦改动,扩展 ID 就从 <publisher>.gitea-toolkit 变成了别的,已装用户不会被自动迁移,
必须卸载重装。所以先确认能拿到 echo-note 这个 ID / 命名空间,再动手。
不要在本地直接跑不带 --packagePath 的 vsce publish。它会经由 npm version
自己创建 commit 与 tag,和本项目的 CI 发版流程(gh release create 建 tag)打架。
一律用 --packagePath 复用 CI 已构建的 vsix,做到「构建一次、多通道发布」,版本号严格一致。
Marketplace 的 PAT 认证有硬时限:Azure DevOps 全局 PAT 于 2026-12-01 完全停用,
而 Marketplace 要求的就是这一类。详见上一节的警告 —— 这不是「以后有空再说」,
到期当天 Marketplace 发布就会中断。
三个通道的分工
| 通道 |
受众 |
是否自动更新 |
| Open VSX |
CodeBuddy(CN / 国际版)、VSCodium 等 |
✅ |
| VS Code Marketplace |
微软官方 VS Code |
✅ |
| GitHub Releases |
离线安装、固定版本 |
❌ 需手动(见「更新扩展」) |
CI 会依次发布到前两个市场,最后创建 GitHub Release 作为「本次发版完成」的标记。
顺序不可颠倒 —— 详见 workflow 中的注释。
继续发 GitHub Releases 仍有价值(离线安装、SHA256SUMS 校验、变更记录),
只要保证各通道版本号一致即可。
目录结构
src/
├── core/ # 与 VS Code 完全解耦,扩展与 MCP Server 共用
│ ├── http.ts # 基于 node:http(s) 的 HTTP 客户端(支持关闭 TLS 校验)
│ ├── giteaClient.ts # 认证、URL 拼装、错误归一化、分页解析
│ ├── repoRef.ts # git 远端 ↔ Gitea 仓库坐标解析
│ ├── format.ts # 时间 / 截断 / HTML 转义与 URL 补全
│ ├── urls.ts # 网页地址构造(分支等实体 API 不返回 html_url)
│ ├── version.ts # Gitea 版本解析与兼容性判定(唯一基准常量)
│ ├── types.ts # Gitea 1.26.x 实体类型
│ └── operations/ # 按域拆分的业务操作(repos / issues / pulls / misc)
├── ai/
│ ├── tools/ # ★ AI 工具目录(单一事实来源)
│ ├── mcpProvider.ts # 动态注册 MCP Server
│ ├── mcpConfig.ts # MCP 启动参数与配置文件生成
│ └── lmTools.ts # vscode.lm.registerTool 适配
├── mcpServer/main.ts # MCP stdio 服务进程(含命令行参数解析)
└── vscode/
├── config.ts # 配置 + SecretStorage 令牌
├── service.ts # 客户端缓存、当前用户、默认仓库推断
├── mcpConfigWriter.ts # MCP 配置文件读写(合并语义)
├── git.ts # git 命令封装
├── statusBar.ts # 状态栏
├── commands/ # 命令处理器(按域拆分)
└── views/
├── detail/ # ★ 详情交互面板(Webview)
├── icons.ts # 节点图标与配色(纯数据,可离线预览 / 校验)
├── nodes.ts # 树节点结构与交互绑定
└── *Provider.ts # 4 个树视图
packages/mcp-server/ # 独立发布的 npm 包(不依赖 VS Code)
├── package.json # 版本号由 scripts/bump-version.mjs 与扩展强制同步
├── README.md # npm 页面正文
└── dist/index.js # 构建产物:esbuild 用同一份 bundle 投递过来(含 shebang)
packages/mcp-server/dist/index.js 与扩展内的 dist/mcpServer.js 由同一次 esbuild 构建产出
(构建后复制,不重复打包),保证两条分发通道的代码字节一致。
设计要点
1. 详情面板只上报意图,不接触令牌
Webview 通过 postMessage 上报 reply / setState / review / merge / checkout 等意图,
真正的 Gitea API 调用、确认弹窗、错误处理全部在扩展宿主完成。
Webview 通过 Content-Security-Policy + nonce 锁定脚本来源,图片只允许扩展资源与目标实例。
2. Markdown 交给 Gitea 服务端渲染
调用 POST /markdown 而非本地渲染库,保证任务列表、@提及、Issue 引用、代码高亮
与网页端完全一致;接口不可用时降级为纯文本 <pre>。
3. 单一事实来源的 AI 工具目录
src/ai/tools/ 下每个工具只用 zod 定义一份入参形状与处理器,随后自动派生出三处产物:
contributes.languageModelTools 的 JSON Schema(npm run sync:tools 生成,CI 用 --check 防漂移)
vscode.lm.registerTool 的运行时处理器
- MCP Server 的工具列表
新增一个能力只需在对应域文件中追加一条 defineTool({...}),无需三处同步。
图标体系
插件图标(media/gitea.svg):单色 currentColor 茶壶(Gitea = Git + Tea),
在活动栏 / 视图标题中以 24px 与 16px 两档显示,跟随主题前景色自动着色。
树节点图标:集中在 src/vscode/views/icons.ts,用内置 codicon + charts.* 主题色表达状态:
| 节点 |
图标 |
配色 |
| 仓库(普通 / 私有 / Fork / 归档) |
repo / lock / repo-forked / archive |
归档用警示黄 |
| 分支(普通 / 受保护) |
git-branch / lock |
受保护用警示黄 |
| Issue(进行中 / 已关闭) |
issue-opened / issue-closed |
绿 / 紫 |
| PR(进行中 / 草稿 / 已合并 / 已关闭) |
git-pull-request / -draft / git-merge / -closed |
绿 / 灰 / 紫 / 红 |
| 通知(按类型,未读加色) |
issue-opened git-pull-request git-commit repo |
未读用信息蓝 |
| 分组(分支 / Issue / PR / 分配给我 …) |
各自的 git-branch issue-opened git-pull-request account … |
同语义色 |
icons.ts 不依赖 vscode 模块,只产出 { id, color? } 纯数据,由 nodes.ts 的
toThemeIcon() 转换为 vscode.ThemeIcon。这样做是为了能离线验证:
- VS Code 对拼错的图标名是静默忽略的(不报错、不显示),只看代码发现不了
npm run check:icons 会把 icons.ts 用到的每个名字与 codicon.css 里的真实列表比对,不存在则退出码非 0
npm run preview:tree 用真实 codicon 字体渲染一棵与运行时一致的模拟树(明暗主题各一份),
可直观核对图标语义与配色
新增节点类型或调整图标时,改 icons.ts 后跑这两个命令即可,无需启动 VS Code。
八、已知限制
- 私有仓库的附件图片(
/attachments/...)在 Webview 中可能无法加载:
Webview 请求不携带访问令牌。头像与公开资源正常。
- 详情面板不支持行级代码评论(Gitea 的
pulls/{index}/reviews 行内评论接口需指定
diff 位置),目前只支持整体评审与对话评论。
- 「检出 PR 分支」不支持来自 fork 的 PR,需先手工添加对应远端。
- 合并策略仅支持
merge / squash / rebase / rebase-merge;强制合并与
「检查通过后自动合并」需在 Gitea 网页端操作。
- MCP Server 以子进程运行,无法直接打开系统浏览器(相关工具只返回链接)。
- AI 工具没出现在对话里时,对照「四、接入 AI 助手」先确认你的客户端走哪条路:
CodeBuddy 必须先用
Gitea: 写入 CodeBuddy MCP 配置 落盘,VS Code 则开箱即用。
也别漏了前提——未设置访问令牌时服务能出现,但调用会失败。
九、许可证
MIT