news 2026/9/8 23:04:51

基于 career-ops 插件模板开发并发布社区插件:从脚手架的 `{{NAME}}` 占位符到 registry 审核上架

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
基于 career-ops 插件模板开发并发布社区插件:从脚手架的 `{{NAME}}` 占位符到 registry 审核上架

基于 career-ops 插件模板开发并发布社区插件:从脚手架的{{NAME}}占位符到 registry 审核上架

【免费下载链接】career-opsOpen-source AI job search: scan job portals, evaluate listings into a structured A-H report with a global 1-5 score, tailor your CV, track applications — runs locally in your AI coding CLI (Claude Code, Codex, OpenCode, Antigravity…)项目地址: https://gitcode.com/GitHub_Trending/ca/career-ops

career-ops 是一套"零密钥、本地优先(local-first)、可在 AI 编码 CLI(Claude Code、Codex、OpenCode 等)中运行"的开源求职流水线。本文围绕 plugins/_template 这一官方插件脚手架目录展开:它既是新插件仓库的起点,也是一份声明式"最小可发布单元"的活样板。读完本文,你将掌握manifest.json+index.mjs的插件契约、plugins.mjs add/enable的双门禁安装与授权流程、密钥与非敏感配置的存放规范,以及把插件送入plugins-registry/获得"approved"审核的全过程,最终写出一个可被node plugins.mjs add <name>一键安装的合规社区插件。

模板目录是什么:一次看清脚手架的六个文件

plugins/_template/是一个不直接运行的模板目录(与已上架的apifygmailnotionh1b-sponsor等真实插件平级)。它的结构即一个插件仓库的"最小文件集":

plugins/_template/ README.md # 用户侧 README——即本文围绕的主体文档,含模板化的安装/配置/上架说明 manifest.json # 插件声明:解析而非执行,先于任何代码导入被校验 index.mjs # 默认导出按 hook 类型组织的对象(引擎调用的入口) skill.md # 教 AI Agent "如何驱动本插件"的领域说明 LICENSE # MIT License,版权行同样带 {{NAME}} 占位符 test/smoke.mjs # 零网络 smoke test,由 plugins.mjs add 与 registry CI 执行

从 plugins/README.md 的说明可以确认,插件的通用形态即"manifest.json(先校验后执行)+index.mjs(默认导出 hooks)+_xxx.mjs私有助手(_前缀表示永不被当作插件发现)"。若你是本地私人插件,则应放在plugins.local/(gitignored、不会被自动更新覆盖);模板目录则是"将要发布为独立 GitHub 仓库的社区插件"的种子。

全文除特别说明外,{{NAME}}均指你将要替换的插件名(manifest.json注释要求 id 只能由小写字母、数字与连字符组成,即[a-z0-9-],且必须等于目录名)。

manifest.json:先声明、后被校验的插件身份文件

模板自带的 plugins/_template/manifest.json 是理解插件的钥匙:

{ "id": "{{NAME}}", "name": "{{NAME}}", "version": "0.1.0", "apiVersion": 1, "description": "TODO: one mission-framed line describing what this plugin does.", "hooks": ["ingest"], "requiredEnv": [], "allowedHosts": [], "skill": "skill.md", "humanInTheLoop": true, "homepage": "https://github.com/your-user/career-ops-plugin-{{NAME}}" }

字段语义在 plugins/README.md 的 "manifest.json" 一节有完整注释,与模板一一对应:

字段含义约束/取值
id插件唯一标识必须等于目录名,字符集[a-z0-9-];同 id 的 bundled 插件在 id 冲突时永远优先
apiVersion契约版本模板为1
description一句话使命描述待你替换 TODO
hooks声明的 hook 类型可选provider / ingest / search / notify / export之一或多个,不存在 auto-submit hook
requiredEnv需要的环境变量值一律放在用户自己的.env,引擎在加载时按此清单做作用域冻结(见下文ctx.env
allowedHosts允许访问的主机白名单requiredEnv非空时必填;引擎据此做 SSRF 防护
skillAgent 指南文件名模板指向同目录skill.md
humanInTheLoop是否强制人工介入模板为true,且必须为true
homepage插件仓库主页发布后改为你的真实仓库

需要说明的是,plugins.mjs 的实现还承认两个模板里未出现的可选扩展字段:allowsLocalhost(允许插件额外访问 localhost,见plugins.mjs对网络声明的输出逻辑)与supersedesBundled(声明自己是某 bundled 插件"受维护的继任者")。此外 smoke test 支持manifest.entry自定义入口文件名,缺省回落到index.mjs。你可以在模板基础上按需声明,但不要删除上面这份"身份最小集"。

index.mjs:hook 出口与 ctx 运行时契约

模板入口 plugins/_template/index.mjs 顶部注释直接给出了引擎会为社区插件强制执行的三条铁律,写插件前务必逐条对照:

  1. 出站流量只能走ctx.fetch/ctx.fetchJson/ctx.fetchText:引擎会应用你allowedHosts白名单并做 SSRF 防护。禁止import node:http/net或调用全局fetch——社区插件一旦出现会被拒绝。
  2. Producer(provider/ingest/search)返回Job[],形如{ title, url, company, location };由引擎(而非插件)经由规范的 writer 写入data/pipeline.md,插件因此不可能破坏 Web 端读取的数据格式。Consumer(export/notify)只向用户自己的外部存储推送。没有任何自动提交(auto-submit)hook。
  3. 密钥来自ctx.env(在manifest.requiredEnv中声明);非敏感配置来自ctx.settings(用户config/plugins.ymlplugins.{{NAME}}块)。

模板只给出了一个空的ingest示例:

export default { // Replace/add hooks to match manifest.hooks. Example ingest: async ingest(ctx) { // const data = await ctx.fetchJson('https://api.example.com/jobs'); // return data.results.map(j => ({ title: j.title, url: j.url, company: j.company, location: j.location || '' })); return []; }, };

五种 hook 的完整签名与用途,见 plugins/README.md 的表格:provider是带密钥/鉴权门禁的职位源,通过scanportals.ymlprovider: <id>条目上触发;ingest从服务(邮件、看板)拉取职位;search按查询串返回职位;export只读的 tracker 快照推送到你自己的外部存储;notify发送外发通知。运行时命令为:

node plugins.mjs list node plugins.mjs run gmail # ingest node plugins.mjs run notion search "platform" # search node plugins.mjs run notion export [--dry-run] # export

ctx对象还提供env(冻结、只包含你声明的密钥)、settings(你在config/plugins.yml中写的非敏感配置块)、log(会对你声明的密钥做脱敏)、dryRun四个成员。其中ctx.fetch是 HTTPS-only、锚定到allowedHostsredirect:'manual'且每一跳都重新校验、跨主机名跳转会剥离凭据的"受守卫原语"——把 HTTP 都走它,出口守卫才真正生效(仓库内apify插件是唯一刻意例外:其客户端自行硬编码到单一主机,并在其代码中明确注释说明)。

skill.md:给 Agent 的"插件驾驶手册"

plugins/_template/skill.md 以 YAML front-matter 开头(name: career-ops-plugin-{{NAME}}descriptionlicense: MIT),正文刻意保持范围收敛:只教 Agent 如何运行本插件(node plugins.mjs run {{NAME}})、产出什么数据结构(producer 的Job[]四字段,或 export 推到哪、推到什么格式)、以及有哪些config/plugins.yml下的plugins.{{NAME}}设置项。文件头注释给出关键边界——它不得指示 Agent 修改核心文件、改动评分逻辑或越过插件已声明的 hooks 行事。插件可选用node plugins.mjs skill <id>打印这份指南。

test/smoke.mjs:零网络的可安装性自检

plugins/_template/test/smoke.mjs 是一个不发起任何网络请求的冒烟测试:读取manifest.json,以默认导出对象为入口,断言"默认导出必须是 hooks 对象、至少声明一个 hook、每个 hook 名都属于五种合法类型、且 manifest 声明的每个 hook 都确实被index.mjs导出"。它在plugins.mjs add时与 registry CI 中都会被运行——也就是说,只要你保持manifest.hooksindex.mjs导出一致,就能通过这道最基础的"文件级体检"。从docs/PLUGINS.md可知,可被上架列表收录的插件最少需包含manifest.jsonindex.mjsREADME.mdLICENSE,若要进入 "listable" 状态还需附上skill.mdtest/smoke.mjs

安装与启用:模板 README 的用户侧操作全继承

模板 plugins/_template/README.md 的 Install 一节给出了两种安装路径与授权流程,这是文章读者最该直接复用的部分:

# Once it's in the career-ops registry: node plugins.mjs add {{NAME}} # Before listing (install directly from your repo at a pinned commit): node plugins.mjs add <your-github-user>/career-ops-plugin-{{NAME}} --sha <40-hex-commit>

Then enable + consent:

node plugins.mjs enable {{NAME}} # shows the capability card node plugins.mjs enable {{NAME}} --confirm # grants it

结合 plugins.mjs 的命令分派(list / available / run / skill / new / add / enable)与 docs/PLUGINS.md,上述流程的底层行为可以展开为以下几点,写插件时应在 README 里向用户讲清:

  • 两扇门都打开插件才会运行:插件默认全关(Default: off),一是要在config/plugins.yml中启用(把 config/plugins.example.yml 复制为config/plugins.yml并设enabled: true);二是把密钥放进用户自己的.envnode doctor.mjsnode plugins.mjs list会列出每个插件及其密钥是否齐备——模板 README 的 Configure 一节正是让插件作者把"缺什么、放哪里"写明白。
  • enable是一个显式同意动作:第一次enable只展示"能力卡片"(声明网络访问主机与所需密钥,见plugins.mjs中对requiredEnvallowedHosts的输出),加上--confirm才真正写入同意记录并启用。社区插件从 git 仓库安装时还有信任徽标(📦 bundled / ✓ approved / ❓ community-unverified / ⚠️ off-registry)与篡改检测:文件在未升版本号的情况下变更会被阻断,需node plugins.mjs trust <id>重新钉住。
  • 本地脚手架node plugins.mjs new my-plugin会直接在plugins.local/my-plugin/生成一套同样式样的模板(plugins.mjscmdNew的实现即此行为),供你在本地迭代后再发布。

密钥与配置分离:.env放 Secret,plugins.yml放选项

模板 README 的 Configure 一节只有两条,但背后是整个插件的配置哲学,务必原样遵守并在你自己的 README 里展开:

  • Secrets go in your.env(the names are inmanifest.jsonrequiredEnv).
  • Non-secret options go inconfig/plugins.ymlunderplugins.{{NAME}}.

即:requiredEnv只写变量名,具体值属于用户侧.env;非敏感开关写进config/plugins.ymlplugins.{{NAME}}块,运行时经ctx.settings抵达插件。config/plugins.example.yml 里的gmail段是可参照的范本——enabled: false表示默认关闭,注释里的labeldays_back正是"非敏感选项放 settings"的示例,而APIFY_TOKENNOTION_ACCESS_TOKENGMAIL_CLIENT_ID等一律指向.envconfig/plugins.yml属于用户:gitignored、永不被自动更新改写;引擎只在两扇门都打开时才会读它,未启用插件的核心运行与无插件时代完全一致。

上架 registry:模板 README "Get it listed as approved" 的完整流水线

模板 README 把上架指引收敛为一句话——"Open a registry PR against career-ops (see docs/PLUGINS.md)",而完整步骤在 docs/PLUGINS.md 的 "Publishing + getting approved" 一节,作为插件作者必须把这条链路写进你发布后的 README:

  1. 独立发布:把模板填充后发布为独立的公共 GitHub 仓库,命名必须精确为career-ops-plugin-<name>(模板仓库即给出正确形状与 release 工作流)。
  2. 登记:提交一个 "Plugin registration" issue,作为插件的 home/changelog。
  3. registry PR:使用?template=plugin-registry.md模板(模板仓库的 release 工作流可在打 tag 时替你从自己的 fork 发起)新增plugins-registry/<id>.json钉住一个精确 commit。CI(plugin-registry-validate)会先做命名、manifest、最小文件集、license、出口网络与静态审计,再由维护者人工复核。合并后,用户即可node plugins.mjs add <name>安装,插件随正常更新机制分发。
  4. 更新:就是再一次 registry PR,把条目的shaversion一起抬升——用户只会拿到你被批准的那个 commit。仓库内plugins-registry/下现存的google-calendar.jsonlinkedin-alerts.jsontavily.json等条目即此格式的真实样例。

如果你想把某个 bundled 插件(如gmailnotionapify——它们是"参考种子",刻意保持精简稳定,不做日常功能开发)扩展成自己的维护版,官方路径是发布同 idcareer-ops-plugin-<id>,在 registry 条目里设"supersedesBundled": true,批准后引擎会让你的维护继任者优先于 bundled 参考实现,node plugins.mjs available会显示 "🔁 maintained version" 提示。优先级只授予"registry 批准且钉住精确 commit"的继任者,未审核的社区插件永远无法顶掉 bundled 插件——这保护的是供应链,而不是阻止你在本机运行自己的修改。

信任边界与安全前提:必须写进 README 的诚实说明

career-ops 是纯 ESM、无构建步骤,引擎无法真正沙箱化插件的 import。allowedHosts、作用域化的ctx.env以及"无 auto-submit 的 hook 分类学"约束的是诚实的插件,并让每个被加载的插件都可见(doctor/plugins.mjs list),但它们不是对抗恶意代码的硬边界——恶意代码仍可直接触达process.env或网络。这是社区插件 README 中应如实交代的部分:

  • Bundled 插件plugins/下)与providers/一样经过代码评审;CI 会检查它们未声明核心拥有的密钥、不引入浏览器自动化或进程派生模块、永不自动提交。
  • plugins.local/运行在你自己的信任之下——是你亲手安装的,把第三方插件当作任何在你机器上运行的代码一样对待。

因此当你把模板 README 中的占位内容替换为真实说明时,"What it does / Install / Configure / Get it listed / License" 五节骨架不应删减,建议把上面的密钥来源、hook 行为、信任边界分别映射进 Configure 与上架两节,让用户在不看主仓库文档的情况下也能安全地安装与判断你的插件。

License:随仓库走 MIT

模板 plugins/_template/LICENSE 是标准 MIT License,版权行模板为 "Copyright (c) the career-ops-plugin-{{NAME}} authors"。发布前记得把作者名占位替换为真实的插件作者集合;模板 README 的 License 一节只写MIT,若你的插件引用了某个 bundled 插件的代码起步(官方鼓励如此,且它本就是 MIT 并注明出处),保持 MIT 即可与主仓库的许可证相容。

总而言之,plugins/_template/的价值在于:把"career-ops 社区插件到底长什么样"压缩到了一个目录、六个文件、一套占位符里。对照 plugins/README.md 的完整契约与 plugins.mjs 的命令实现逐文件替换{{NAME}}、补全 TODO、跑通test/smoke.mjs,你就已经站在了"提交 registry PR、进入 approved 列表"的门槛上。

【免费下载链接】career-opsOpen-source AI job search: scan job portals, evaluate listings into a structured A-H report with a global 1-5 score, tailor your CV, track applications — runs locally in your AI coding CLI (Claude Code, Codex, OpenCode, Antigravity…)项目地址: https://gitcode.com/GitHub_Trending/ca/career-ops

创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考

版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/9/8 23:00:42

Spring Boot Banner定制实战:从release包下载到项目接入全指南

简介&#xff1a;面向 Android 开发者的 Banner 组件库发布包&#xff0c;版本 1.4.10&#xff0c;核心解决广告位、推荐位等内容的自动轮播与循环展示需求&#xff0c;适用于新闻客户端、电商首页、运营活动页等常见场景。库内已封装自动播放间隔配置、多页面切换动画、无限循…

作者头像 李华
网站建设 2026/9/8 22:57:00

浏览器会话跨机迁移:从Cookie到Playwright的共享登录态方案

简介&#xff1a;一套面向Web开发与网络安全学习者的共享浏览器工程方案&#xff0c;重点解决异地设备间Session克隆与会话无缝迁移问题。方案深入讲解HTTP会话机制&#xff0c;围绕Session ID捕获、加密传输、请求头注入与实时同步等核心步骤&#xff0c;覆盖Cookie管理、HTTP…

作者头像 李华