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

@syllm/brickly-sdk

v0.1.1

Published

Brickly Brick Node runtime 官方 SDK

Readme

@syllm/brickly-sdk

Brickly Brick Node runtime 官方 SDK。用 1 行代码替代 ~150 行手写的 BPP(Brickly Plugin Protocol)样板。

零运行时依赖,只用 Node 内置 readline / process / events


快速上手

const { BricklyRuntime } = require('@syllm/brickly-sdk')

const brick = new BricklyRuntime({ brickId: 'com.example.foo' })

brick.onCommand('spawn-pet', async (ctx, input) => {
  const win = await ctx.ui.createBrowserWindow('pet.html', {
    width: 200,
    height: 200,
    frame: false,
    transparent: true,
    alwaysOnTop: true
  })
  win.on('closed', () => ctx.events.publish('pet.closed', { id: win.id }))
  return { windowId: win.id }
})

brick.start()

SDK 自动完成:

  • host.helloruntime.ready 握手
  • runtime.pingruntime.pong 心跳
  • host.* 请求 id 分配与 host.result / host.error 路由
  • command.invoke 分发与 command.result / command.error 序列化
  • runtime.shutdownonShutdown 钩子 → runtime.bye → 退出

核心 API

BricklyRuntime

| 方法 | 作用 | | -------------------------------------- | ----------------------------------------------------------- | | onCommand(id, handler) | 注册命令处理器 | | onReady(fn) | runtime.ready 之后立即触发(适合 host-start service 或实例初始化) | | onShutdown(fn) | runtime.shutdown 时触发,返回后 SDK 自动发 runtime.bye 退出 | | ui.createBrowserWindow(url, options) | 创建子窗口,返回 WindowHandle | | ui.listWindows() | 列出本 Brick 持有的窗口 | | events.on(event, fn) | 订阅事件总线(含 window.* 系列) | | events.publish(event, payload) | 发布事件 | | invoke(brickId, commandId, input?, options?) | 在 command 作用域内发起 child 跨 Brick 调用 | | invokeRoot(brickId, commandId, input?, options?) | command 外发起 root 跨 Brick 调用 | | openSession(brickId, options?) | 打开跨 Brick 会话;后续 session.invoke 需在 command 作用域内调用 | | log(...parts) | 写 stderr 日志;宿主日志中心自动关联 Brick 身份 | | start() | 启动 stdin 循环 |

CommandContext(handler 第一个参数)

| 字段 | 作用 | | --------------------------- | ------------------------------------- | | requestId / commandId | 当前请求与命令 id | | invocation | 宿主注入的调用来源;热键触发时 source === 'hotkey',可携带热键 Profile 选择 | | progress(value, message?) | 进度(0~1) | | chunk(chunk, name?) | 向具名输出追加片段 | | output(name, value) | 一次性覆盖具名输出 | | onCancel(fn) | 注册取消回调 | | isCancelled() | 协作式取消轮询 | | invoke(brickId, commandId, input?, options?) | 跨 Brick 调用命令,自动携带当前 parentRequestId,支持 options.profileId | | invokeStream(brickId, commandId, input?, options?) | 流式跨 Brick 调用命令,自动携带当前 parentRequestId | | openSession(brickId, options?) | 打开跨 Brick 会话,支持 options.profileIdsession.invoke 自动携带当前 parentRequestId | | ui / events / platform | 与 brick.ui / brick.events / brick.platform 同源 |


日志约定

brick.log(...) / transport.log(...) 只写入 stderr,stdout 永远只写 BPP 协议消息。SDK 不会把 brickId 拼进日志正文;宿主日志中心会在采集 stderr 时用结构化字段记录来源、Brick id、stream 与作用域名称。插件代码不要手动输出 [brickId] 前缀,避免日志中心和 SQLite 中出现重复信息。


跨 Brick 调用

普通 Brick 在 command handler 内调用其它 Brick 命令时只需要 ctx.invoke。SDK 会自动携带当前请求的 parentRequestId,宿主会把子调用挂到同一 invocation graph 下,并自动启动、复用和回收目标 Brick 实例。目标 Brick 需要配置时,可传入目标 Brick 的 Profile ID;不传则使用目标 Brick 默认 Profile。

const result = await ctx.invoke(
  'com.brickly.openai',
  'chat',
  { prompt: 'hello' },
  { profileId: 'work' }
)

调用方 manifest 必须在 dependencies 中声明目标 Brick 和允许调用的命令:

"dependencies": {
  "com.brickly.openai": {
    "commands": ["chat"]
  }
}

commands: ["*"] 表示允许调用目标 Brick 的全部可见命令;隐藏命令必须显式写命令 id。

热键触发 command 时,用户可以在热键管理页为依赖 Brick 选择 Profile。SDK 会在 ctx.invoke(targetBrickId, ...) 未显式传 options.profileId 时自动使用这份选择;代码里显式传入 profileId 时仍以代码为准。

如果需要在 command 外主动创建顶级调用,使用显式 root API:

const result = await brick.invokeRoot(
  'com.brickly.openai',
  'chat',
  { prompt: 'hello' },
  { profileId: 'work' }
)

平台 System API

brick.platform.system.* 与 handler 内的 ctx.platform.system.* 通过 BPP host.platform.system.* 调用宿主系统能力:

brick.onCommand('show-app-info', async (ctx) => {
  return {
    appName: await ctx.platform.system.getAppName(),
    appVersion: await ctx.platform.system.getAppVersion(),
    userData: await ctx.platform.system.getPath('userData'),
    isMacOS: await ctx.platform.system.isMacOS()
  }
})

当前方法包括 showNotificationshellOpenPathshellTrashItemshellShowItemInFoldershellOpenExternalshellBeepgetNativeIdgetAppNamegetAppVersiongetPathgetFileIconreadCurrentFolderPathreadCurrentBrowserUrlisDevisMacOSisWindowsisLinux

runtime 侧仍按 manifest 权限校验:通知需要 os.notification;Shell 类能力需要 os.exec;应用信息、路径、设备 ID、平台判断和当前文件夹路径读取需要 os.env;文件图标需要 fs.readreadCurrentFolderPath() 在 macOS Finder 与 Windows Explorer 前台窗口可用;当前没有可读取的前台文件管理器文件夹时会抛 CURRENT_FOLDER_UNAVAILABLEreadCurrentBrowserUrl() 当前预留,会返回 UNSUPPORTED_PLATFORM

跨 Brick 会话

当目标 Brick 实例内部有状态,需要在多次命令调用之间保留上下文时,在 command handler 内使用 ctx.openSession。同一个 session 的后续 invoke 会落到同一个目标 Brick 实例;每次 session.invoke 都会自动携带当前 parentRequestId。调用 close()、调用方 Brick 实例退出或宿主回收调用方时,会话会结束。

const session = await ctx.openSession('com.brickly.openai', { profileId: 'work' })

try {
  await session.invoke('start-thread', { title: 'Draft' })
  const reply = await session.invoke('chat', { prompt: '继续刚才的话题' })
  return reply
} finally {
  await session.close()
}

profileId 仍然是目标 Brick 的 Profile ID;不传则使用目标 Brick 默认 Profile,或使用热键调用上下文中的依赖 Profile 选择。session.invoke 也会按调用方 manifest 的 dependencies[target].commands 重新校验命令。

依赖调用类型生成

如果当前 Brick 在 manifest.dependencies 声明了依赖 Brick,可以生成一组开发期 JS 包装函数和 .d.ts,让调用依赖命令时获得命令名、入参、返回值和说明提示。

{
  "dependencies": {
    "com.brickly.text-toolkit": {
      "commands": ["case", "stats"]
    }
  }
}

在当前 Brick 根目录执行:

brickly-typegen

默认输出到 runtime/node/_generated/deps/。生成物包含:

  • index.js / index.d.ts:统一导出所有依赖命名空间,并扩展 SDK CommandMap
  • <brick-id>.js / <brick-id>.d.ts:每个依赖 Brick 一个文件,函数注释来自目标 manifest 的 commands[].description 与 socket 描述。

普通 JS runtime 可以直接使用生成的 wrapper:

const { BricklyRuntime } = require('@syllm/brickly-sdk')
const { textToolkit } = require('./_generated/deps')

const brick = new BricklyRuntime({ brickId: 'com.example.composite' })

brick.onCommand('run', async (ctx, input) => {
  return textToolkit.caseCommand(ctx, {
    text: String(input?.text || ''),
    mode: 'upper'
  })
})

也可以绑定一次上下文,减少重复传参:

const text = textToolkit.bind(ctx)
const changed = await text.caseCommand({ text: 'hello', mode: 'upper' })

TypeScript 项目只要把生成的 index.d.ts 纳入 tsconfig.include,裸写 ctx.invoke('com.brickly.text-toolkit', 'case', ...) 也会按生成的 CommandMap 自动推导输入和返回类型。


WindowHandle(107 个方法)

ui.createBrowserWindow 返回的 WindowHandle 完整封装了宿主白名单的 107 个方法。详细签名/参数/返回值见 @d:\ai-bricks\specs\window-api.md

1. 几何 / 位置(17)

win.setBounds({ x, y, width, height })       // 全部字段可选
win.getBounds()                              //=> { x, y, width, height }
win.setContentBounds({ ... })  win.getContentBounds()
win.getNormalBounds()                        // 非最大化/最小化时的"常态"
win.setPosition(x, y)          win.getPosition()      //=> [x, y]
win.setSize(w, h)              win.getSize()
win.setContentSize(w, h)       win.getContentSize()
win.setMinimumSize(w, h)       win.getMinimumSize()
win.setMaximumSize(w, h)       win.getMaximumSize()
win.setAspectRatio(16/9)       win.setAspectRatio(16/9, { width: 40, height: 50 })
win.center()

2. 状态切换(11)

win.minimize()  win.maximize()  win.unmaximize()  win.restore()
win.hide()      win.show()      win.showInactive()
win.focus()     win.blur()
win.setFullScreen(true|false)
win.destroy()    // 强制销毁,不触发 close 事件

3. 状态查询(18,全部返回 boolean)

// 可见性 / 焦点 / 状态
win.isVisible() isFocused() isMinimized() isMaximized()
   isFullScreen() isNormal() isModal() isDestroyed()
// 能力开关
win.isResizable() isMovable() isFocusable()
   isMinimizable() isMaximizable() isClosable() isFullScreenable()
   isEnabled() isKiosk() hasShadow()

4. 视觉属性(10)

win.setOpacity(0.85)           win.getOpacity()
win.setBackgroundColor('#1e293b')
win.setTitle('My Window')      win.getTitle()
win.setHasShadow(true)         win.invalidateShadow()        // macOS
win.flashFrame(true)
win.setProgressBar(0.4)        win.setProgressBar(0.8, { mode: 'indeterminate' })
win.moveTop()

5. 层叠 / 鼠标 / 任务栏(7)

win.setAlwaysOnTop(true)       win.isAlwaysOnTop()
win.setAlwaysOnTop(true, 'screen-saver')   // 带 level
win.setIgnoreMouseEvents(true) win.setIgnoreMouseEvents(true, { forward: true })
win.setSkipTaskbar(true)
win.setVisibleOnAllWorkspaces(true)        win.isVisibleOnAllWorkspaces()
win.moveAbove(mediaSourceId)

6. 能力开关 setter(9)

win.setResizable(false)        win.setMovable(false)        win.setFocusable(false)
win.setMinimizable(false)      win.setMaximizable(false)    win.setClosable(false)
win.setFullScreenable(false)   win.setEnabled(false)        win.setKiosk(true)

7. 菜单栏(5)

win.setMenuBarVisibility(false)  win.isMenuBarVisible()
win.setAutoHideMenuBar(true)     win.isMenuBarAutoHide()
win.removeMenu()

8. macOS 文档窗口(4)

win.setRepresentedFilename('/path/to/doc.txt')   win.getRepresentedFilename()
win.setDocumentEdited(true)                       win.isDocumentEdited()

9. 内容加载(3)

win.loadURL('https://example.com')   win.loadURL(url, { httpReferrer, userAgent })
win.loadFile('relative/path.html')   win.loadFile(p, { query, hash })
win.reload()

10. WebContents 子对象(22)

仿 Electron 原生写法:

// DevTools
win.webContents.openDevTools({ mode: 'detach' })
win.webContents.closeDevTools()
win.webContents.toggleDevTools()
win.webContents.isDevToolsOpened()

// 跨进程消息(宿主 → 子窗口 ipcRenderer 'channel')
win.webContents.send('lab:result', { ok: true, value: 42 })

// 远程执行 JS
const title = await win.webContents.executeJavaScript('document.title')

// 导航
win.webContents.goBack()    goForward()   canGoBack()   canGoForward()
win.webContents.getURL()    getTitle()

// 缩放
win.webContents.setZoomFactor(1.25)   getZoomFactor()
win.webContents.setZoomLevel(1)       getZoomLevel()

// 编辑命令(作用于聚焦元素)
win.webContents.copy()  paste()  cut()  selectAll()  undo()  redo()

关闭与事件

win.close()                       // 走 host.ui.closeWindow,可被 beforeunload 拦截
win.id                            // BrowserWindow.webContents id
win.on('closed', () => ...)
win.on('message', ({ channel, args }) => ...)   // 子窗口 brickly.sendToParent 推上来
win.on('focus' | 'blur' | 'minimize' | 'maximize' | 'resize' | 'move', ...)

网络检查

createBrowserWindow 可通过 network 选项启用平台侧 CDP Network 监听。runtime 仍是普通 Node 进程,不需要直接依赖 Electron:

const win = await ctx.ui.createBrowserWindow('ui/browser.html', {
  webPreferences: { webviewTag: true },
  network: {
    enabled: true,
    captureBody: true,
    maxBodyChars: 200000,
    targets: 'all'
  }
})

win.onNetwork((event) => {
  if (event.stage === 'response-body') {
    ctx.chunk(event, 'requests')
  }
})

targets 可选 windowwebviewallcaptureBody 会尝试读取响应体;下载流、特殊协议、浏览器内部拦截或 CDP 未缓存的大资源可能产生 response-body-error


协议与版本对齐

  • 白名单真相源@d:\ai-bricks\specs\bpp.schema.jsonBrickWindowMethod enum。
  • 跨语言协议规范@d:\ai-bricks\specs\window-api.md(写 Go / Python SDK 时照此抄)。
  • 当前协议版本:0.1.0,窗口 API 子集版本 v0.2(41 → 107 个方法,向后兼容)。
  • Go SDK 对照实现@d:\ai-bricks\Brickly\packages\brickly-sdk-go,API 表面与本包一一对应。
  • src/protocol.ts 是 schema 的 TS 镜像,由 @d:\ai-bricks\Brickly\scripts\check-bpp-schema.mjs 强制保持同步。

构建与同步到 Brick

# 在 Brickly/ 目录下
npm run sdk:build

Brick runtime 通过 require('@syllm/brickly-sdk') 加载,并在 runtime/node/package.json 声明依赖。SDK 协议变更后发布新版 npm 包,Brick 升级依赖版本即可。


测试

npm run sdk:test    # tsx --test tests/runtime.test.ts tests/api.test.ts

参考实现:

  • 窗口 API 实验室(覆盖 80+ 个方法的可视化测试):@d:\ai-bricks\bricks\com.brickly.demo-window-lab\