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.9.0

Published

Brickly Brick Node runtime 官方 SDK 0.9.0(gRPC Runtime)

Readme

@syllm/brickly-sdk

Brickly Brick Node runtime 官方 SDK。用 1 行代码接入 Host gRPC Runtime(invoke / interact),不要手写旧 BPP 或 session API。

体验窗 / 页面 TypeScript 请安装 @syllm/brickly-ui,不要用本包给 window.brickly 做类型。

生产协议是 loopback gRPC;缺少 Host endpoint 时会拒绝 fallback 到 stdin/stdout。


快速上手

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

const brick = new BricklyRuntime()

brick.onCommand('hello', async (_ctx, input) => {
  const name = String((input && input.name) || 'Brickly')
  brick.log.info('hello', { name })
  return { message: 'Hello, ' + name }
})

brick.start()

SDK 自动完成:

  • 连接 BRICKLY_HOST_ENDPOINT 并注册 gRPC Runtime
  • invoke / interact 命令分发
  • Host 平台 / UI / Resource 客户端路由
  • 取消信号与 shutdown 钩子

进阶:双向 live

ctx.send 只在 interact 里有意义,不要写进 hello。页面用 interact,不要用 callcall 会立刻关掉输入)。

brick.onCommand('live', async (ctx) => {
  let n = 0
  const timer = setInterval(() => {
    n += 1
    ctx.send({ type: 'tick', n }).catch(() => {})
  }, 1000)
  ctx.onEvent(async (event) => {
    await ctx.send({ type: 'reply', text: '收到' + String(event?.text || '') })
  })
  await ctx.closed
  clearInterval(timer)
  return { n }
})

进阶:两种子窗

attached(默认)随这次调用 / runtime 消失。standalone 窗在则 runtime 在:须声明 command.window: standalone,并在命令执行期间创建。后台定时弹窗请 invoke 一条 window: standalone 的命令,不要直接 createWindow。建窗写 lifetime: 'standalone'。不要把「附加」理解成常驻。


核心 API

长期占用请使用 ToolSdk.start() / ToolHandle。Runtime 里占用依赖用 require(alias).start(),必须先进入自己的命令(brick.invoke 中转);占用跟这次 Call,return 自动放手。一次性 invoke / interact 不会 pin 进程。 dispose() 关闭 attached 窗口并释放 caller retainer;stop() 取消该 Lifetime 的调用并关闭全部窗口。 子窗口默认 attachedstandalone 须声明 command.window: standalone,并在当前命令执行期间创建。后台定时弹窗请 invoke。建窗标 lifetime: 'standalone'per-call 不能 start(),因此不能开 standalone。有当前命令且 command.window === none 时禁止 createWindow

BricklyRuntime

| 方法 | 作用 | | ------------------------------------------------------ | ---------------------------------------------------------------------- | | onCommand(id, handler) | 注册命令处理器 | | invoke(commandId, input?) | 再跑自己的一条命令;已有占用则不 dispose。没有占用则拒绝 | | interact(commandId, input?, { onEvent }) | 已有占用上再开会话,不 dispose;必须传入 onEvent | | call(commandId, input, { onEvent }) | interact + 半关闭的糖;必须与命令 mode=call 对齐 | | onReady(fn) | gRPC Runtime 就绪后立即触发(适合 service 预热后初始化 / 实例启动逻辑) | | onShutdown(fn) | Host 关闭 Runtime 时触发 | | ui.createBrowserWindow(url, options) | 创建子窗口,返回 WindowHandle | | ui.listWindows() | 列出本 Brick 持有的窗口 | | events.on(event, fn) | 订阅公共事件(命名空间:主题);窗口寿命用 win.on | | events.publish(event, payload) | 发布事件 | | resources.open(ref) | 惰性绑定已有 ResourceRef,不立即访问 Host | | dependencies.require(alias) | 获取 Host 握手绑定到精确 BrickRef 的依赖客户端 | | log.debug/info/warn/error(...) | 经 Host diagnostics.log 进日志中心;带着这次 command 的 invocationId 则挂该节点(含返回后的异步/定时器),否则顶级 | | start() | 连接 Host gRPC endpoint 并阻塞运行;同时读取 BRICKLY_PROFILE_CONFIGctx.config |

CommandContext(handler 第一个参数)

| 字段 | 作用 | | -------------------------------------------------- | --------------------------------------------------------------------------------------------------------------------- | | requestId / commandId | 当前请求与命令 id | | invocation | 宿主注入的调用来源;热键触发时 source === 'hotkey',可携带热键 Profile 选择 | | send(event) | 推给调用方(仅 interact) | | onEvent(handler) | 收调用方 send(仅 interact) | | closed | 等到调用方 end / 断开 | | onCancel(fn) | 注册取消回调 | | isCancelled() | 协作式取消轮询 | | dependencies.require(alias) | 获取当前 command 的依赖客户端,自动携带 parent、trace 与依赖 Profile | | ui / events / platform | 与 brick.ui / brick.events / brick.platform 同源 | | config | Host 在 spawn 时固化的当前 Profile 字段快照(inject=env 的字段不在这里) | | storage | 本机持久 KV / collection / secrets;与体验窗 window.brickly.storage 共库。看不见路径或 _rev |


日志约定

业务日志只允许带 level 的 API:

brick.log.info('ready')
brick.log.warn('retry', { n: 2 })
brick.log.error('failed', err)
// command 内自动挂当前 Trace:
// (若使用 CommandContext 扩展)ctx 侧同样应走 structured log

对应宿主结构化日志。禁止 console.* / 裸 stderr / 无 level 的 log(...)(已移除)。 不要往 stdout 打业务日志;应改用 plugin.log / brick.log


跨 Brick 调用

业务代码只使用 manifest alias。精确来源和版本由 Host 注入依赖绑定,SDK 不做回退或猜测。

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

const poem = await openai.call('complete', { prompt: '写一首诗' }, {
  onEvent(event) {
    void event
  }
})

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

"dependencies": {
  "openai": {
    "target": {
      "brickId": "com.brickly.openai",
      "origin": "installed",
      "version": "2.1.0"
    },
    "commands": ["chat"]
  }
}

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

热键触发 command 时,SDK 按 alias 绑定的精确 BrickKey 自动使用依赖 Profile;显式 profileId 优先。

没有当前命令时,同一套 invoke / call / interact 就是 root,不必另写 invokeRoot

const result = await brick.dependencies
  .require('openai')
  .invoke('chat', { prompt: 'hello' }, { profileId: 'work' })

大载荷与资源

普通 invoke 始终返回直接值,输入和结果的逻辑 JSON 上限为 10 MiB,一次传完;超过上限会抛 PAYLOAD_TOO_LARGE,不会自动改成联合返回类型,也不会自动重试可能有副作用的命令。 大结果由作者 resources.create 后直接 return Handle。invoke 原样交回 ResourceRef 或普通 JSON,调用方要读时再 open

const ref = await ctx.dependencies.require('report').invoke('export', input)
const resource = brick.resources.open(ref)

if (resource.ref.sizeBytes <= 200 * 1024 * 1024) {
  const report = await resource.json()
} else {
  await resource.saveTo(outputPath)
}

await ctx.dependencies.require('consumer').invoke('analyse', { source: resource })

Brick 也可以主动创建资源后再传给下游:

const inputResource = await brick.resources.create(bytes, {
  mimeType: 'application/octet-stream',
  name: 'input.bin'
})

string 默认 text/plain; charset=utf-8Uint8Array 默认 application/octet-stream,通常无需填写 mimeType。资源创建仍受 Host 配额与生命周期治理。小内容使用一次性快速路径;超过内部阈值时 SDK 自动切换到 Writer,调用方式 和返回类型不变。

大内容或未知长度数据使用 store-and-forward 流式创建:

const resource = await brick.resources.createFrom(createReadStream('large.bin'), {
  name: 'large.bin'
})

SDK 自动聚合来源小块并按最大 1 MiB 的 wire 分块逐块等待 Host 落盘确认,最后调用 finish 返回 ResourceHandle。资源在 finish 前不可读取;finish 后是不可变快照,下游读取速度不会影响上传。 流式资源总大小不受普通 invoke 的 10 MiB 上限约束。Host 限制并发上传并在生产环境保留 1 GiB 磁盘安全余量;部署还可配置全局和 Brick 维度的 pending bytes 配额。

ResourceHandle 提供 stream()bytes()text()json()saveTo()close()revoke();再次作为 input 时 SDK 只发送 ResourceRef,不会复制资源字节。 bytes() / text() / json() 仅允许整体物化不超过 200 MiB 的资源。

普通 invoke、interact、命令输入和资源 JSON 中的嵌套引用保持 ResourceRef。发送 invoke、 command 结果或事件时,SDK 会自动把嵌套 ResourceHandle 转为完整 Ref;接收方读取 嵌套资源时显式打开,open() 本身不会发送 Host 请求:

const handle = brick.resources.open(payload.attachment)
try {
  await handle.saveTo(outputPath)
} finally {
  await handle.close()
}

events.on() 回调收到的就是发布时的业务对象,不会再包一层资源,也不会水合成 ResourceHandle。若业务对象里本身带 ResourceRef,需要读内容时再 resources.open

平台 System API

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

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

readCurrentFolderPath() 在 macOS Finder 与 Windows Explorer 前台窗口可用;当前没有可读取的前台文件管理器文件夹时会抛 CURRENT_FOLDER_UNAVAILABLEreadCurrentBrowserUrl() 当前预留,会返回 UNSUPPORTED_PLATFORMshellOpenExternal 仅允许 http / https / mailto

跨 Brick 会话

目标 Brick 有状态时,在 command handler 里 interact,不要另做 open()

const session = await ctx.dependencies.require('openai').interact(
  'chat',
  { prompt: '继续刚才的话题' },
  {
    onEvent(event) {
      void ctx.send(event)
    }
  }
)
return await session.end()

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

依赖调用类型生成

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

{
  "dependencies": {
    "text": {
      "target": {
        "brickId": "com.brickly.text-toolkit",
        "origin": "installed",
        "version": "1.0.0"
      },
      "commands": ["case", "stats"]
    }
  }
}

在当前 Brick 根目录执行:

brickly-typegen

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

  • index.js / index.d.ts:统一导出所有依赖命名空间,并扩展 SDK CommandMap
  • <alias>.js / <alias>.d.ts:每个依赖 alias 一个文件,函数注释来自目标 manifest。

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

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

const brick = new BricklyRuntime()

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,wrapper 就会按 alias 扩展 CommandMap;业务代码不传 BrickRef


WindowHandle(105 个反射方法)

ui.createBrowserWindow 返回的 WindowHandle 封装宿主的 105 个反射方法,并提供独立的普通关闭和强制终止操作。详细契约见 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. 状态切换(10)

win.minimize()  win.maximize()  win.unmaximize()  win.restore()
win.hide()      win.show()      win.showInactive()
win.focus()     win.blur()
win.setFullScreen(true|false)

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()

关闭与事件

const result = await win.close()  // closed | prevented | pending | not-found
await win.forceClose()            // 跳过页面关闭协商
win.id                            // BrowserWindow id
win.windowKey                     // 生命周期主键
win.webContentsId                 // Electron webContents id
win.isClosed                      // 仅 closed/not-found/终态事件后为 true
win.expose({
  pause: () => timer.pause(),
  import: async (payload, { emit, signal }) => {
    emit({ progress: 1 })
    return { ok: true, payload, aborted: signal.aborted }
  }
})
await win.send('tick', { remaining: 60 })
win.on('closed', ({ eventId, cause, forced }) => ...)
win.on('focus' | 'blur' | 'show' | 'hide' | 'resize' | 'move', ...)

pending/prevented 后句柄保持可用。终态会从 Runtime 的窗口 Map 删除句柄并清空它的全部 listener;重复 window.closed.eventId 只处理一次,transport 结束也会释放所有句柄。


协议与版本对齐

  • 白名单真相源specs/window-protocol.schema.jsonBrickWindowMethod enum。
  • 跨语言协议规范specs/window-api.md(写 Go / Python SDK 时以此为准)。
  • 当前 SDK 包版本为 0.9.0SDK_VERSION);生产协议是 brickly.runtime.v1
  • 实验性的窗口网络抓包 API 已于 2026-07-15 删除,不保留 network 选项或 onNetwork()
  • Go SDK 对照实现brickly-sdk-go,API 表面与本包一一对应。
  • 窗口 DTO 与方法类型由 Schema 生成到 src/generated/window-protocol.ts

构建与同步到 Brick

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

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


测试

npm test            # 包目录:tsc + 官方 tsx --test 清单
npm run sdk:test    # 工作区等价:npm -w @syllm/brickly-sdk test

源码结构

源码在 src/,按协议、invoke 类型、window/platform 能力层与 runtime 编排层拆分;api.ts / index.ts 为公共入口。

| 目录 | 职责摘要 | | --------------------------- | --------------------------------- | | internal/grpc/ | Host gRPC 客户端与 Runtime 接入 | | scope/ | command / event ALS | | invoke/ | CommandMap 与 invoke 类型 | | window/ | WindowHandleUiApi、结果校验 | | platform/ | PlatformApi 工厂 | | command/ | CommandContext 等类型 | | storage/ | StorageApi、Host 存储客户端 | | runtime/ | BricklyRuntime 编排 | | protocol.ts / errors.ts | 协议类型与错误 | | generated/ | schema 生成物(禁止手改) |

模块职责表、允许依赖与改动规则见 src/README.md(给后续改代码的 AI / 维护者用)。