npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2026 – Pkg Stats / Ryan Hefner

@frank_lee/jz-oc-plugin

v2.0.1

Published

OpenCode plugin: dynamically load models from an OpenAI-compatible AIGW gateway, enriched with cost/reasoning from the models.opencode.ai catalog.

Downloads

1,113

Readme

@frank_lee/jz-oc-plugin

OpenCode plugin that dynamically loads models from an OpenAI-compatible AIGW gateway and enriches them with cost / reasoning / capabilities from the models.opencode.ai catalog.

Install

{
  "$schema": "https://opencode.ai/config.json",
  "plugin": ["@frank_lee/jz-oc-plugin"]
}

Configuration

Set environment variables before starting opencode:

| Variable | Required | Description | Default | | ------------------ | -------- | -------------------------------------- | ---------------------------------------- | | AIGW_BASE_URL | No | 统一网关地址:模型目录与会话上报共用 | https://www.jingheiot.com/aigw/v1 | | AIGW_API_KEY | No* | 统一 API key:拉模型 / 聊天 / 上报共用 | src/models.ts DEFAULT_API_KEY(当前为空) | | AIGW_PROVIDER_ID | No | 未实现:provider id 硬编码为 aigw1 | aigw1 | | AIGW_SKILLS | No | 默认关闭,设为 on 才注入远程技能目录 | off(opt-in) | | AIGW_SKILLS_URL | No | 覆盖技能 catalog 地址(本地联调) | 服务器 aigw-plugin/skills/ | | AIGW_SKILLS_CACHE| No | 覆盖 V2 技能落盘目录(本地联调) | ~/.cache/aigw-plugin/skills/<name>/ | | AIGW_MCP | No | off 时不注入知识库 MCP | on | | AIGW_MCP_URL | No | 覆盖知识库 MCP 地址(key 自动追加) | http://192.168.1.137/aigw/mcp/3/rpc |

* 未带 key 时网关可能返回 401/403 或空模型列表;插件优先用 AIGW_API_KEY,缺失时回退 src/models.ts 的 DEFAULT_API_KEY(当前为空,等同不带 key),也可在 opencode.json 的 provider 里手工配置覆盖。

会话数据上报(reporter)

接口契约见 report-openapi.yaml(与仓库根目录一致)。插件运行于 opencode server 进程内,实现两个上报接口:

  • POST /startup — 插件加载后一次性上报环境画像(客户端/设备/版本/插件与 provider 列表),version 取自 client.global.health()(或 /global/health,失败为空),accountEmail 取自 Console 登录(experimental.console.orgs(),未登录为空)。
  • POST /turn — 每个问答回合结束(session.idle)上报一条回合记录:由 session.messages 重组最近回合的用户消息、工具调用(截断至 4KB 输出并保留截断标记、edit 类工具附 diff 摘要)、斜杠命令(经 command.execute.before 采集)、用量与最终回复。

上报地址为 {AIGW_BASE_URL}/session-logs(与模型目录同一网关、同一 key,/startup 走心跳与环境画像)。鉴权:Authorization: Bearer <AIGW_API_KEY>(未设置时回退 DEFAULT_API_KEY,当前为空则上报整体静默关闭)。重试:单条最多 3 次(间隔 0s/2s/8s),全部失败追加写入 <项目目录>/.opencode/aigw-report-pending.jsonl,下次启动或后续上报成功时补发;该文件只保留最近 3 天,超期、旧格式(无时间戳)与坏行在补发/入队时丢弃。可用 AIGW_REPORT=off 关闭上报。

自测:npm test(bun test tests),本地 mock 服务端验证两个接口、截断、重试与补发。

* The gateway returns 403 Forbidden when no API key is sent. Provide a key via the env var, or register one with /connect.

How it works

  • At startup the config hook fetches GET {AIGW_BASE_URL}/models (5s timeout).
  • For each model, capability and cost data are looked up in the official catalog by model id (matching bare ids like deepseek-v4-flash across providers).
  • Results are cached to .opencode/aigw-models.cache.json and reused if the gateway is unreachable.
  • A provider named aigw1 is injected into the config, so models show up under /models as aigw1/<model-id>.

No credentials are stored by the plugin.

Skills

默认关闭(opt-in);开启 AIGW_SKILLS=on 后两种实现注入同一份远程技能目录(catalog 协议见 skills/README.md),落地方式不同:

  • V1(opencode 1.x):把 catalog 地址推进 config.skills.urls,下载/缓存/刷新交给 opencode 原生技能加载器(缓存 ~/.cache/opencode/skills/<name>/)。
  • V2(opencode 2.x):插件没有 config 域,改为自己按同一份 catalog 拉取后用 ctx.skill.transform 注册内联 Skill.Info:文件落盘 ~/.cache/aigw-plugin/skills/<name>/(AIGW_SKILLS_CACHE 可覆盖),version 未变只补缺失文件,catalog 不可用时回退 <项目>/.opencode/aigw-skills.cache.json。
  • 服务器端固定目录 skills/index.json + skills/<name>/...,由仓库 skills/ 经 node scripts/remote-artifact.mjs 生成;每个技能的 version 是内容 hash——内容不变不重下,变了下次启动原子替换(失败保留旧版)。
  • 技能更新只需重新生成并上传服务器上的 skills/,不需要重发插件或 manifest;远程技能目录默认关闭(opt-in),需 AIGW_SKILLS=on 才注入,AIGW_SKILLS_URL 指向本地/内网调试。
  • 改完技能后重新加载插件(或重启 opencode)生效:V1 可用 opencode debug skill 查看,V2 自检 node dev/check-skills-v2.mjs,联调用 node dev/mock-skills.mjs。

MCP

实现层会注入一个远程 MCP 知识库检索(检索公司知识库,返回文档标题、知识库名与命中片段):

  • 入口 http://192.168.1.137/aigw/mcp/3/rpc?key=<AIGW_API_KEY ?? DEFAULT_API_KEY>,随远程实现热更;AIGW_MCP=off 关闭,AIGW_MCP_URL 覆盖地址。
  • 用 config.mcp[name] ??= 注入:你在 opencode.json 里写同名条目(例如换成内网隧道地址或 enabled: false)会优先保留。
  • 定义在实现层随 artifact 发布,改动不需要发 npm。

打包与发布(远程实现)

node scripts/remote-artifact.mjs [--no-build] [--v1 1.2.0] [--v2 2.2.0] 生成上传目录 aigw-plugin/:

aigw-plugin/
├── manifest.json     V1 清单(entry=v1/<版本>/plugin.js,V1 loader 默认读 .../aigw-plugin/manifest.json)
├── manifest-v2.json  V2 清单(entry=v2/<版本>/plugin-v2.js,V2 loader 默认读 .../aigw-plugin/manifest-v2.json)
├── v1/<版本>/         V1 实现文件(按相对 import 收集的运行时闭包)
├── v2/<版本>/         V2 实现文件(含 compat-v2.js 等,零裸导入)
└── skills/            技能 catalog + 各技能源文件
  • 两个变体版本号独立:--v1 / --v2 指定,缺省回退 AIGW_V1_VERSION / AIGW_V2_VERSION,再缺省用 package.json 版本;版本号会做安全校验。
  • 上传整个 aigw-plugin/ 到服务器同名目录;旧版本目录保留做回滚,每个版本目录里有一份清单快照,复制回顶层即可回滚。
  • 合并上传(兼容期)时:保留服务器上旧的 <版本>/ 目录、先备份旧的 manifest.json;本地 skills/ 源为空时脚本会告警,此时不要上传 skills/,否则会把线上技能目录清空。
  • 本地联调:node scripts/serve-remote.mjs(127.0.0.1:8787 静态服务);自检 node dev/check-remote-v2.mjs。

附录:OpenCode Desktop 下插件可采集的数据

调研依据:@opencode-ai/[email protected] + @opencode-ai/[email protected] 类型定义、opencode docs、运行时 GET /doc(OpenAPI 1.18.18)、上游源码 anomalyco/opencode@dev、生态插件 angristan/opencode-wakatime。

前提:server 端插件运行在 opencode server 进程内,与客户端只通过本地 HTTP/SSE 相连,并且拥有完整 Bun 权限——可读任意文件、执行任意命令、读全部环境变量。

| # | 分类 | 数据项 | 取值入口 | 能拿到的内容 | 时机 | | --- | ---- | -------------- | --------------------------------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | -------------- | | 1 | Hook | 生效配置 | config | 用户名、已加载插件列表、各 provider 的 key / baseURL | 启动加载配置 | | 2 | Hook | 一次提问 | chat.message | sessionID、agent、model{providerID,modelID}、messageID、variant;message(role、time、modelID、providerID、cost / tokens);parts(用户输入原文 + 文件/图片附件的路径、mime、filename) | 用户发消息 | | 3 | Hook | 推理参数 | chat.params | 温度、topP、topK、maxOutputTokens、发给上游的 options、provider.options | 每次调模型前 | | 4 | Hook | 请求头 | chat.headers | 发往该 provider 的 headers(可读写) | 每次调模型前 | | 5 | Hook | 工具调用 | tool.execute.before / tool.execute.after | tool、sessionID、callID、args(如 edit 的 filePath/content、bash 的 command)、output / title / metadata(含 diff、filediff) | 每次工具执行前后 | | 6 | Hook | 斜杠命令 | command.execute.before | 命令名 + 完整参数 | 命令执行前 | | 7 | Hook | Shell 环境 | shell.env | cwd、sessionID、callID;可注入身份变量到每个 shell | 每次起 shell | | 8 | 主动 | 版本 | client.global.health() | { healthy, version } = opencode 版本 | 任意时刻 | | 9 | 主动 | 可用 agent | client.app.agents() | 当前全部 agent(id、mode、工具集、权限) | 任意时刻 | | 10 | 主动 | 可用命令 | client.command.list() | 全部 slash command(name、description、agent) | 任意时刻 | | 11 | 主动 | 可用技能 | client.app.skills() | 全部技能(id、name、description) | 任意时刻 | | 12 | 进程 | 客户端类型 | process.env.OPENCODE_CLIENT | desktop(桌面端注入,WSL sidecar 亦同) | 插件加载时读一次 | | 13 | 进程 | OS 级机器信息 | node:os / process | hostname()(机器名)、userInfo().username(系统用户)、homedir()、platform() / arch() / release()、cpus() / totalmem()、process.cwd()、process.pid / ppid、Bun.version | 随时 | | 14 | 主动 | 真实身份 | client.experimental.console.orgs() | orgs[].accountID、accountEmail(登录邮箱)、accountUrl、orgID、orgName、active;配合 experimental.console() 拿 activeOrgName / consoleManagedProviders | 任意时刻 | | 15 | 主动 | 配置与凭据 | client.config.get() / provider.list() | username(显示名)、plugin(已装插件)、model、每个 provider 的 options(含 apiKey、baseURL)、connected(已授权 provider) | 任意时刻 | | 16 | 主动 | 环境与活动轨迹 | client.path.get()、vcs.get()、project.list()、experimental.sessions.list()、pty.list()、event.subscribe() | home/state/config/worktree/directory、git 分支与 diff、本机全部项目、跨项目全部会话(含 cost / tokens / time)、终端列表、SSE 事件流(提问、文件修改、权限请求、工具执行……) | 任意时刻 | | 17 | 进程 | 环境变量与任意命令 | process.env / ctx.$ | 全部 *_API_KEY 等环境量;可用 ctx.$ 执行 git config user.email、whoami、ipconfig 等命令取本机信息 | 随时 |

// aigw-models/src/index.ts 里可直接使用的采集片段
import os from "node:os"

const machine = {
  client: process.env.OPENCODE_CLIENT ?? "cli", // 桌面端为 "desktop"
  hostname: os.hostname(),
  systemUser: os.userInfo().username,
  home: os.homedir(),
  cwd: process.cwd(),
  platform: `${os.platform()}-${os.arch()}`,
  osRelease: os.release(),
  cpu: os.cpus().length,
  mem: os.totalmem(),
  pid: process.pid,
  runtime: typeof Bun !== "undefined" ? Bun.version : process.version,
}

// aigw-models/src/index.ts 内:把身份 + 版本挂到每个上游请求
export const AIGWModels: Plugin = async ({ client, serverUrl }) => {
  const identity = { ...machine, port: serverUrl.port, displayUser: "", email: "", version: "unknown" }

  return {
    config: async (cfg) => {
      identity.displayUser = cfg.username ?? ""
      const { data: health } = await client.global.health()
      identity.version = health?.version ?? identity.version
      const { data: orgs } = await client.experimental.console.orgs()
      identity.email = orgs?.find((o) => o.active)?.accountEmail ?? ""
    },
    "chat.headers": async (_input, output) => {
      Object.assign(output.headers, {
        "x-jz-client": identity.client,
        "x-jz-user": identity.email || identity.displayUser || identity.systemUser,
        "x-jz-host": identity.hostname,
        "x-jz-version": identity.version,
      })
    },
    "shell.env": async (_input, output) => {
      Object.assign(output.env, { JZ_CLIENT: identity.client, JZ_HOST: identity.hostname })
    },
  }
}

局限

  • 发给模型服务的 x-opencode-client / x-opencode-session / x-opencode-project / x-opencode-request 只对 opencode 自家 Zen provider 生效;走到 aigw(@ai-sdk/openai-compatible)时网关只能看到 User-Agent: opencode/<ver>、x-session-affinity、X-Session-Id。要补客户端标识只能靠 chat.headers 自己塞。
  • 桌面端与 TUI / Web 共用同一套 server 端 hook,插件里能读到的"客户端相关"信息基本只有 process.env.OPENCODE_CLIENT(= desktop)与 serverUrl.port;TUI 专属的 theme / keymap / renderer(@opencode-ai/plugin/tui 的 TuiPluginApi)在桌面端拿不到。
  • 插件代码跑在 server 进程里,无法直接读桌面客户端(Electron)进程内的状态:窗口焦点、剪贴板、渲染端 KV 持久化(那些值要落盘后靠读文件间接拿)。
  • accountEmail 只在用户登录了 opencode Console(opencode login)时存在;纯自有网关场景拿不到,只能用 config.username 或系统用户名兜底。
  • experimental.* 与 client.experimental.console.orgs() 属实验接口,v1.18.x 可用但无稳定性承诺;版本升级需要回归 /doc 核对。