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

cola-plugin-sdk

v0.4.0

Published

Plugin SDK for Cola channel plugins

Readme

cola-plugin-sdk

Cola 的 channel 插件 SDK。

Channel 插件让 Cola 能够在桌面端之外(微信、Slack、Telegram、Discord、自定义 HTTP 网关等)和用户对话:传输层交给插件,agent loop 留给 Cola server。

English: README.md


安装

pnpm add cola-plugin-sdk

仓库内通过 pnpm workspace 解析 (workspace:*);独立仓库请依赖已发布版本。

快速开始

插件就是一个从 package.json#cola.channel.entry 指向的入口文件 default export 出来的对象。

// src/index.ts
import { defineChannel, createPollLoop, withBackoff } from 'cola-plugin-sdk'

type State = {
  lastCursor: string | null
}

export default defineChannel<State>({
  id: 'my-channel',

  meta: {
    label: 'My Channel',
    description: '把 Cola 桥接到 my-platform.com',
  },

  capabilities: {
    receive: { text: true, image: true },
    send: { text: true, image: true, markdown: true, typing: true },
    limits: { maxTextLength: 4096 },
  },

  // 'isolated'(默认):每个外部用户独立 session
  // 'shared':所有消息都进桌面端 session
  sessionMode: 'isolated',

  gateway: {
    async start(ctx) {
      const state = ctx.state   // 插件生命周期内共享可变状态
      state.lastCursor = null

      createPollLoop({
        signal: ctx.abortSignal,
        intervalMs: 1000,
        fetch: async (signal) =>
          withBackoff(() => fetchMessagesSince(state.lastCursor, signal)),
        onMessages: async (items) => {
          for (const msg of items) {
            state.lastCursor = msg.id
            await ctx.deliver({
              channelUserId: msg.from,
              message: msg.text,
              attachments: msg.files,
            })
          }
        },
        onError: (err) => ctx.logger.warn('轮询失败', err),
      })
    },

    async stop(ctx) {
      ctx.logger.info('gateway 停止中')
    },

    getStatus(ctx) {
      return { connected: true, configured: Boolean(ctx.config.get().apiKey) }
    },
  },

  outbound: {
    async sendText(ctx) {
      await postMessage(ctx.channelUserId, ctx.text, ctx.config.apiKey as string)
    },
    async sendMedia(ctx) {
      await postMedia(ctx.channelUserId, ctx.filePath, ctx.mediaType)
    },
    async sendTyping(ctx) {
      await setTyping(ctx.channelUserId, ctx.active)
    },
    textChunkLimit: 4000,
  },

  auth: {
    async login(ctx) {
      const qr = await requestQrCode()
      ctx.onQrCode?.(qr.dataUrl)
      const session = await pollForLogin(qr.token)
      ctx.runtime.logger.info(`登录成功: ${session.user}`)
      // 凭证自行持久化,插件通常写到 `${colaDir}/plugins/<id>/accounts/`
    },
  },

  commands: [
    {
      name: 'status',
      description: '显示连接状态',
      async execute(ctx) {
        return { reply: `已连接: ${ctx.config.user ?? '未知'}` }
      },
    },
  ],
})

目录结构

my-plugin/
├── package.json          # 必须包含 "cola.channel" 字段(见下)
├── dist/                 # 构建产物,对应 cola.channel.entry
│   └── index.js
└── src/
    └── index.ts

package.json 必须声明 manifest:

{
  "name": "cola-channel-my-platform",
  "version": "0.1.0",
  "type": "module",
  "cola": {
    "channel": {
      "id": "my-channel",
      "label": "My Channel",
      "entry": "dist/index.js",
      "minSdkVersion": "0.1.0"
    }
  },
  "dependencies": {
    "cola-plugin-sdk": "^0.1.0"
  }
}

Loader 读取 cola.channel.id / entry / minSdkVersionminSdkVersion 高于当前 SDK 时会被跳过并 warn。

SDK API

工厂函数

| 导出 | 用途 | | --- | --- | | definePlugin(plugin) | 带完整 Plugin<State> 类型推断的恒等函数 | | defineChannel(def) | 纯 channel 插件的简写 — metachannel.meta 推断 |

Channel Adapters

ChannelDefinition 可以挑选下列 adapter 的任意子集:

| Adapter | 触发时机 | 主要方法 | | --- | --- | --- | | gateway | 插件启动 / 停止 / 重载 | start(ctx)stop(ctx)reload(ctx)getStatus(ctx) | | outbound | Agent 产生回复 | sendText(ctx)sendMedia(ctx)sendTyping(ctx) | | auth | 用户执行 cola channel login <id> | login(ctx) | | config | Server 读取配置 schema | schema |

gateway.start 收到的 GatewayContext<State> 包含:

  • config — 插件配置文件的冻结快照
  • runtime — host 注入的 API(见下)
  • stateState 类型对象,插件可自由修改;跨 start / stop 存活
  • abortSignal — server 关闭插件时触发
  • deliver(payload) — 把入站消息交给 agent loop
  • logger — 带命名空间的 logger

PluginRuntime

Host 会把这个对象注入到每个 adapter 的 context:

type PluginRuntime = {
  identity: {
    resolve(channelUserId): Promise<string | null>  // channel 用户 → Cola 用户 id
    bind(channelUserId): Promise<void>              // 创建 / 刷新绑定
  }
  config: { get(): Readonly<Record<string, unknown>> }
  logger: PluginLogger
  offload<T>(fn: () => T): Promise<T>               // 把 CPU 重活丢到 worker pool
  version: string
  reloadGateway?(): Promise<void>
}

Commands

plugin.commands[] 暴露 slash 命令,CLI 和对话中均可触发:

commands: [{
  name: 'bind',
  aliases: ['link'],
  description: '把当前 channel 用户绑定到 Cola 账户',
  args: [{ name: 'token', description: '配对 token', required: true }],
  async execute(ctx) {
    await ctx.runtime!.identity.bind(ctx.args.trim())
    return { reply: '已绑定。' }
  },
}]

Helpers

| Helper | 作用 | | --- | --- | | createPollLoop({ fetch, onMessages, intervalMs, signal, onError }) | 长轮询脚手架;signal.abort 后干净退出 | | withBackoff(fn, { maxRetries, baseMs, maxMs }) | 指数退避 + ±25% jitter |

版本门槛

SDK 导出 SDK_VERSION。在 package.json#cola.channel.minSdkVersion 声明最低要求,loader 通过 satisfiesMinVersion(标准 semver)比对;需要更高 SDK 的插件会被跳过。

开发流程

1. 建项目

在 Cola 仓库之外(或内部贡献)新建一个包,按照上文的目录结构。

2. 构建

任何 ESM bundler 都行。仓库内约定使用 tsup

pnpm tsup src/index.ts --format esm --dts --sourcemap

产物路径需与 cola.channel.entry 一致(通常是 dist/index.js)。

3. 本地安装

快速调试最简方式:软链接到 Cola 插件目录:

ln -s "$(pwd)" ~/.cola/plugins/<your-plugin-id>

重启 server:

cola server stop && cola server start

cola plugin list 里会看到插件,sourcelocal

4. 配置

把配置写入 ~/.cola/plugins/<id>/config.json — 就是 runtime.config.get() 的返回值。

5. 迭代

  • 改代码 → rebuild → cola server stop && cola server start
  • cola channel list — runtime 状态(connected / disconnected)
  • cola channel login <id> — 触发 auth.login adapter
  • Server 日志:~/.cola/logs/server.log

6. 发布到 Plugin Store(可选)

CLI 从 marswaveai/cola-pluginsregistry.json 解析插件。提交 PR 新增:

{
  "id": "my-channel",
  "label": "My Channel",
  "description": "…",
  "version": "0.1.0",
  "minSdkVersion": "0.1.0",
  "downloadUrl": "https://…/my-channel-0.1.0.tar.gz"
}

终端用户即可:

cola plugin search
cola plugin install my-channel
cola plugin update my-channel

运行时模型

  • Isolated(默认): 每个 channelUserId 独立 Cola session scope;用 runtime.identity.bind 把外部用户绑定到 Cola 账户
  • Shared: 所有入站消息都进桌面端 session。适合通知桥 / 需要和人类坐在电脑前共享上下文的助手场景
  • 状态归属: 插件拥有 state;host 管身份、配置、agent loop。跨重启的持久化数据写到 ${getColaDir()}/plugins/<id>/
  • 生命周期: start 每次插件启动执行一次;stop 关闭或换配置时执行(换配置后紧跟一次新 start);reload 可选,热更新配置时优于 stop+start

声明 Channel 动作(message 工具)

Channel 可以通过共享的 message 工具向 Agent 暴露平台原生动作(react / edit / delete / send)。在 channel 上实现 messaging 适配器:

import { definePlugin, type ChannelMessageActionAdapter } from 'cola-plugin-sdk'

const messaging: ChannelMessageActionAdapter = {
  describeMessageTool: (ctx) => ({
    actions: ['send', 'react', 'delete'],
    schema: {
      properties: {
        myCustomParam: { type: 'string' },
      },
    },
  }),
  handleAction: async (ctx) => {
    // ctx.action, ctx.params, ctx.toolContext.currentMessageId
    return { ok: true, message: 'done' }
  },
}

export default definePlugin({
  id: 'my-channel',
  meta: { label: 'My Channel', description: '...' },
  channel: {
    meta: { label: 'My Channel', description: '...' },
    capabilities: { receive: { text: true }, send: { text: true } },
    messaging,
  },
})

动作名必须来自 CHANNEL_MESSAGE_ACTION_NAMES 闭集;自定义参数通过 schema contribution 带入,不需要占用新的动作名。ctx.toolContext.currentMessageId 由 host 从入站事件注入,因此 react/edit/delete 即使不带参数也能作用于当前消息。

内部(非稳定)API

channel.agentPromptchannel.agentTools 标记了 @internal — 可以让插件贡献 system prompt 片段和 agent 工具。不遵循 semver,随时可能变,使用需自担风险。