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

@jhh96/aiopsdk

v0.1.0

Published

面向 AI 指令下发 / 新手引导 / 远程配置的能力(capability)总线层:registry + dispatch 调度模型 + manifest 能力清单树

Downloads

43

Readme

aiopsdk

面向 AI 指令下发 / 新手引导 / 远程配置 的「能力(capability)总线」层。 把页面里跨页公用的能力(滚动、高亮、筛选……)抽象成统一的

registry + dispatch(name, payload) 调度模型

并附带一棵结构化的 manifest 能力清单树,把"有哪些能力、在哪、参数怎么填" 暴露给 AI / 后端 / 业务调用方。


目录


能力一览

| 能力 | dispatch 命名空间 | 命令式入口 | 内部实现 | 是否需要 controller | |---|---|---|---|---| | scroll | scroll.to / scroll.toTop | useScroll() | scrollToTarget | 否(DOM 全局可用) | | highlight | highlight.show / highlight.hide | useHighlight() | showHighlight | 否 | | filter | filter.apply / .reset / .get / .list / .options | useFilter() | filterCore | (业务状态) | | manifest | (自身不调度,提供描述) | useManifestRegistry() / exportManifest() | registry.getTree | 否 |


三层模型

┌─────────────────────────────────────────────────────────────┐
│  调用方                                                       │
│   ① dispatch('scroll.to', {...})   ← AI / 引导流程(runtime)│
│   ② useScroll().scrollTo({...})    ← 开发者写代码(静态)    │
└────────────┬───────────────────────────────┬────────────────┘
             │                                │
   ┌─────────▼─────────┐            ┌────────▼─────────┐
   │  core registry     │            │  composable      │
   │  (Pinia store)     │            │  (useFoo 薄封装) │
   └─────────┬──────────┘            └────────┬─────────┘
             │ dispatch                       │ 直接调用
             ▼                                ▼
        ┌──────────────────────────────────────┐
        │  能力实现(纯函数 / controller 适配) │
        │   scrollToTarget / showHighlight     │
        │   applyFilterOp / FilterController   │
        └──────────────────────────────────────┘

核心设计:两条入口共用一份纯逻辑,行为可证明一致。


装配步骤

main.ts 只需 4 行:

import { createApp } from 'vue'
import { CapabilitiesPlugin, setFilterController, useManifestRegistry } from 'aiopsdk'
import App from './App.vue'

import { useFilterController } from './composables/useFilterController'
import { PAGES } from './manifest'   // import.meta.glob 收集的 *.manifest.ts

const app = createApp(App)

// 1) 仅 filter 能力需要业务状态:注入 controller(DI 接口)
setFilterController(useFilterController())

// 2) 注入 manifest 描述树(build-time 收集,运行时一次性灌入)
useManifestRegistry().setPages(PAGES)

// 3) 装所有能力 plugin(聚合器:scroll + highlight + filter 一次到位)
app.use(CapabilitiesPlugin)
app.mount('#app')

前置约束setFilterController 必须在 app.use(CapabilitiesPlugin) 之前; filter plugin 在 install 时只注册 dispatch handler,真正 dispatch 时才读 holder。


调度式与命令式双入口

每个能力都提供两条路径,共享同一份纯逻辑

调度式(runtime 解耦)—— 给 AI / 引导流程 / 远程下发用

import { useCapabilityRegistry } from 'aiopsdk'
const reg = useCapabilityRegistry()

const res = await reg.dispatch('scroll.to', { selector: '#module-x', offset: -56 })
// => { ok: true, data: { found: true, scrolled: true } }
// 未注册的动作稳定返回:{ ok: false, error: 'action not active: scroll.to' }

特性:

  • 永不抛异常,永远返回 CapabilityResult —— 上游可直接根据 ok/error 判定重试或跳过。
  • 未注册的 action 返回稳定的错误码 'action not active: ${name}',便于 AI 决策。
  • 故意不给 dispatch 加泛型:handler 存进 store 后类型已擦除,<T> 是单方面的 cast。 需要类型保护时走命令式入口。

命令式(静态调用)—— 给开发者用,带完整类型推断

import { useScroll, useHighlight, useFilter } from 'aiopsdk'

const { scrollTo } = useScroll()
await scrollTo({ selector: '#module-x', offset: -56 })

const { show, hide } = useHighlight()
const { id } = await show({ selector: '#btn-go', timeout: 3000 })

const { apply } = useFilter()
await apply({ field: '岗位类别', values: ['技术'], mode: 'add' })

FilterController 接口契约

filter 的本质是改一份业务状态,存哪(Pinia store / 组件 state)由 FilterController 决定, shared 只定义语义与算子。这是 Hexagonal Architecture 的端口与适配器模式:

// shared 定义端口
export interface FilterController {
  get(): Promise<FilterState>                      // 读
  listFields(): Promise<FilterFieldMeta[]>         // 读
  options(field: string): Promise<FilterValue[]>   // 读
  set(state: FilterState): Promise<void>           // 写
  apply(op: FilterOperation): Promise<FilterState> // 写
}

// src 实现适配器
export const useFilterController = (): FilterController => ({ ... })
setFilterController(useFilterController())   // main.ts 注入一次

全异步(前瞻设计)

本地阶段 controller 内部其实是同步的(直接改 store),但接口全部签 Promise

为什么:接口化(远程组合筛选)那天,controller 实现内部从"同步过滤"改成 "await fetch + 落 store",对外契约 0 改动,AI 链路 / 命令式调用零改动。

holder 未注入时的退化

setFilterController 未调用时:

  • 调度式入口 dispatch('filter.xxx') 统一返回 { ok: false, error: 'filter controller not registered' }
  • 命令式入口 useFilter().apply(...) 返回 null

两种路径都优雅退化,shared 包单独 build / 单独被引用时不会崩。


Manifest 能力清单树

把"整页布局 + 每个节点挂哪些可被 dispatch 的动作"用一棵纯数据树暴露出去。

描述与运行时分离

一份能力由两半组成,性质不同,必须分开存放:

| 半边 | 写在哪 | 收集时机 | 例 | |---|---|---|---| | 描述(action/params/说明) | *.manifest.ts 同伴文件 | build-time(import.meta.glob eager) | { action: 'drilldown', params: [...] } | | handler(怎么执行) | 组件 useManifestNode(...) | runtime(组件 mount 时) | drilldown: ({team}) => showTalentList(...) |

关键收益:即便某页组件没 mount,它的 .manifest.ts 默认导出仍在树里 —— 满足"AI 可调用其他页面能力"的诉求。

描述形态 vs wire 形态

作者写的是 action(局部名),但 exportManifest 时自动拼成 dispatch(调度全名):

// 作者这样写(*.manifest.ts)
{
  id: 'supplyAge',
  capabilities: [{ action: 'exportImage', description: '...' }]
}

// getTree() 拼成 wire 形态(AI / 后端实际看到)
{
  id: 'supplyAge',
  capabilities: [{ dispatch: 'supplyAge.exportImage', description: '...' }]
}

输出格式(拆分后的文件族)

vite-plugin-manifest 在 build / dev 启动期生成一族 JSON,按路由 name 拆分 (便于单页维护、git diff 精准定位、AI 按需加载):

public/
  manifest.json            # 索引清单:列文件路径,不含能力本体
  manifest/
    globals.json           # 全局能力(scroll.* / highlight.*)
    <RouteName>.json       # 各页面能力树(含 scopes + components)

索引清单(极小,AI 拉这一份即可发现所有子文件):

{
  "version": "1.0",
  "generatedAt": 1783494150000,
  "files": {
    "globals": "manifest/globals.json",
    "pages":  ["manifest/Dashboard.json"]
  }
}

AI 加载流程:

  1. fetch('/manifest.json') 拿索引 → 得到 globals + 各 page JSON 的相对路径。
  2. 按需 fetch('/manifest/globals.json')fetch('/manifest/<RouteName>.json')
  3. 单页能力变化时,MVP 文件中只有 <RouteName>.json 重写——content-level 幂等 保证不变的字节不被改写(generatedAt 只在结构真变化时更新,不会再驱动 IO)。

schema 版本契约MANIFEST_SCHEMA_VERSION = '1.0' as const。若结构破坏性变更, bump 版本,让上游 AI / 后端做兼容校验。


命名约定与生命周期

dispatch 命名空间

| 类型 | 作者写法 | dispatch 全名 | |---|---|---| | 全局能力(globals) | 直接写完整字面量 | scroll.to / highlight.show / filter.apply | | 节点能力 | 写 action | ${nodeId}.${action},如 supplyAge.drilldown |

handler 生命周期

| 来源 | 注册时机 | 注销时机 | |---|---|---| | 全局能力(plugin install) | app 启动 | app 销毁(一般无需手动清理) | | 节点能力(useManifestNode) | 组件 onMounted | 组件 onBeforeUnmount(自动) |

组件卸载后,AI 调用 dispatch('supplyAge.exportImage') 会拿到 'action not active: supplyAge.exportImage' 而非命中已销毁的 dead handler。


能力清单(注册项)

scroll

| dispatch | 入参 | 说明 | |---|---|---| | scroll.to | { selector, offset?, behavior?, wait? } | 滚动到元素;找不到元素返回 ok:false,AI 可据此重试 | | scroll.toTop | — | 整页回顶 |

highlight

| dispatch | 入参 | 说明 | |---|---|---| | highlight.show | { selector, timeout?, wait? } | 聚光灯高亮;timeout=0 常驻、>0 自动隐藏 | | highlight.hide | { id? } | 隐藏某实例;不传 id 隐藏全部 |

返回值带 id,可后续精确隐藏。

filter

| dispatch | 入参 | 说明 | |---|---|---| | filter.apply | { field, values, mode? } | 应用一次筛选;非法值过滤,data.invalid 返回明细 | | filter.reset | { field? } | 清空全部 / 单字段 | | filter.get | — | 当前状态快照(字段名 → 选中队列) | | filter.list | — | 所有字段元信息(label/field/type),让 AI 知道有哪些维度 | | filter.options | { field } | 某字段合法值词表(基于已加载数据动态产出) |

mode: set(覆盖)、add(追加去重)、remove(移除)。

空值约定(避免 AI 误传清空状态):

  • set 空 values → 清空字段
  • add 空 values → no-op
  • remove 空 values → no-op(整字段清空走 reset({field})

性能与副作用声明

性能优化

  • waitForSelector rAF 合批:MutationObserver 每 mutation 不立即 querySelector, 合并到下一帧。大列表懒加载场景下避免观测到上千次 mutation、每次同步扫 DOM。
  • highlight 位置同步 rAF 合批:scroll / resize 期间合并到单帧对齐一次。

sideEffects 声明

package.json 已精确声明带副作用的文件(不是粗暴的 false):

"sideEffects": [
  "./src/highlight/styles.ts",  // 首次 import 时 inject <style>
  "./src/**/plugin.ts",         // install 时 register handler
  "./src/plugins.ts"
]

目的:让 bundler 在 false 时不会误把这些 install-time 注册逻辑砍掉。


维护约定

新增一个能力(最简)

  1. src/<能力名>/ 下建子目录,按现有 scroll/highlight/filter 的分层实现:
    • types.ts:对外契约
    • xxxCore.tsxxx.ts:纯函数实现(不依赖 Vue / Pinia)
    • composable.tsuseFoo 命令式入口
    • plugin.ts:调度式入口(install 时 register 到 registry)
    • index.ts:barrel 导出
  2. src/plugins.tscapabilityPlugins 数组里加一行。
  3. src/manifest/globals.ts 列出对外 dispatch 动作(与 plugin.ts 的 register 1:1)。
  4. src/index.ts 顶层 barrel re-export。

仅此 4 步,main.ts 不需要改动。

维护 check

  • 每个 plugin.ts 注册的 action,要和 globals.ts 的描述 1:1 对应(漂移会让 AI 调到「描述有、运行时没有」的死项)。
  • *.manifest.ts 里写的 action,要和 vue 组件 useManifestNode 注册的 handler 同 key。
  • 改 schema 结构时同步 bump MANIFEST_SCHEMA_VERSION