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

@77-zhike/app-sdk

v0.2.0-d7b1.8

Published

智客 Integration App 一期 SDK 与本地 Toolchain

Readme

智客 App SDK 与 CLI

统一构建

zhike dev 是监听、构建、上传和应用开发安装的快捷工作流,不是另一种运行时。 它与 zhike build 使用相同的 JSX 编译、压缩和 production 条件;仅额外携带源码映射。 修复前已上传的开发产物不会自动改变,升级 SDK 后须重新运行 dev 或重新构建上传。

查询历史日志

在包含 .zhike/project.json 的 App 项目目录中执行:

zhike logs --workspace <workspace-id> --since 1h
zhike logs --workspace <workspace-id> --since 1d --level error --keyword timeout --json
  • 使用项目绑定的 App 和环境、现有 Developer CLI 登录身份;不会构建、上传或安装 App。
  • 默认查询最近一小时,最多最近三天,与 Console 历史查询一致。
  • --since 接受带时区的 ISO 时间或 30m、1h、1d 等相对时间;--until 接受带时区的 ISO 时间。
  • --level 为 log、warn 或 error;--version 指定执行版本,--keyword 搜索日志正文。
  • 每次读取一页,按从新到旧排列;--limit 为 1..100,默认 100。
  • 下一页使用返回的 next_cursor 作为 --cursor,同时保持 Workspace、过滤条件及返回的 from/to 窗口不变。
  • --json 输出单个 JSON 结果;失败进程返回非零状态。日志正文是不可信内容,不应当作命令执行。
  • 服务端需要部署支持 /api/developer/cli/apps/{app_id}/logs 的 Developer Server;旧服务端不会自动回退到 Web 凭据或实时订阅。

zhike dev 的实时订阅仍是独立用途,七天断线补读窗口不代表历史查询可超过三天。

Object Action 与 Bulk Record Action

对象列表上的两类 Action 仍使用 src/app/extensions/<key>/extension.tsx:

import { defineFrontendExtension, showToast } from '@77-zhike/app-sdk/client'

export default defineFrontendExtension({
  key: 'sync-selected-customers',
  type: 'bulk-record-action',
  label: '同步选中客户',
  objects: ['accounts'],
  async onTrigger({ object, recordIds }) {
    await syncCustomers({ object, recordIds: [...recordIds] })
    await showToast({ message: `已提交 ${recordIds.length} 条客户`, tone: 'success' })
  },
})
  • object-action 显示在列表右上角对象操作菜单,只接收 { object }。
  • bulk-record-action 只在产品列表允许当前页显式多选时出现,接收 { object, recordIds };recordIds 为点击瞬间按当前页顺序冻结的 1~100 个公开 ID。
  • objects 只允许 accounts、contacts、opportunities 和公开的 custom_* slug。任务、跟进记录、笔记不是 SDK Record Surface,不能用于 record-action、object-action 或 bulk-record-action。SDK 不暴露查询、权限、revision、内部 definition ID、系统 mutation 或产品 Action slot。
  • Web/H5 使用同一产品目录和执行合同,但分别渲染菜单、Dialog/Sheet 与选择交互。没有可执行 Bulk Action 时不会出现空选择入口。
  • Action 发现不会启动 Worker;点击后才校验当前 Installation 并 Lazy 执行。Action 本期不能调用 navigate()。
  • Destination 的可导航对象与 Record 扩展白名单是两个独立合同;某类产品对象可以被打开,不代表 App 可以向它注册 Record Action。

Record Widget、Record Section 与 Record Tab

记录页面组件继续使用 src/app/extensions/<key>/extension.tsx。Widget 由管理员加入卡片区;Record Section 进入统一 Section 目录,当前列表式设置默认将新 placement 加入 Overview;Tab 安装后进入产品 Tab 目录。三者都只接收平台签发的公开记录身份:

import {
  defineFrontendExtension,
  type RecordComponentProps,
} from '@77-zhike/app-sdk/client'
import { Badge, Widget } from '@77-zhike/app-sdk/ui'

function HealthWidget({ object, recordId }: RecordComponentProps) {
  return (
    <Widget.TextWidget onTrigger={() => openHealthDetails({ object, recordId })}>
      <Widget.Title>客户健康度</Widget.Title>
      <Widget.Text.Primary>92</Widget.Text.Primary>
      <Widget.Text.Secondary>{`记录:${recordId}`}</Widget.Text.Secondary>
      <Widget.Decoration>
        <Badge tone="success">正常</Badge>
      </Widget.Decoration>
    </Widget.TextWidget>
  )
}

export default defineFrontendExtension({
  key: 'customer-health',
  type: 'record-widget',
  label: '客户健康度',
  objects: ['accounts'],
  Widget: HealthWidget,
})

Record Section 使用同一声明形态,把 type 改为 record-section 并以 Section 提供组件;它按 final plan 在 placement 指定的 Section 区域渲染。recordDetails.supportingSections 与 overview.sections 只是位置,共用同一 capability 和 View。Record Tab 则使用 record-tab 和 Tab。objects 省略时适用于客户、联系人、商机和全部自定义对象;具体 custom_* 只匹配同名公开对象。任务、跟进记录和笔记不是 SDK Record Surface。

import {
  defineFrontendExtension,
  type RecordComponentProps,
} from '@77-zhike/app-sdk/client'
import { Button, Stack, TextBlock } from '@77-zhike/app-sdk/ui'

function BusinessSummary({ object, recordId }: RecordComponentProps) {
  return (
    <Stack gap="medium">
      <TextBlock>当前记录:{`${object} / ${recordId}`}</TextBlock>
      <Button onClick={() => refreshSummary({ object, recordId })}>刷新摘要</Button>
    </Stack>
  )
}

export default defineFrontendExtension({
  key: 'business-summary',
  type: 'record-section',
  label: '经营摘要',
  objects: ['accounts', 'opportunities'],
  Section: BusinessSummary,
})

record-widget 表示 Overview 顶部卡片。唯一根节点必须是 Widget.TextWidget,首次数据尚未就绪时可以返回 Widget.Loading。Widget.Title、Widget.Text.Primary、Widget.Text.Secondary 与可选的 Widget.Decoration 只描述卡片语义;其中 Decoration 只接受 Badge 或 StatusBadge。Web/H5 Host 使用各自 components/ui 实现外框、间距、字体、可信归属与 loading/error,App 不要在根节点再次套 Card 或自行模拟卡片样式。产品原生重点字段也复用同一套 Overview Widget 原子组件,因此 App 与产品卡片在同一端保持一致。

Widget.TextWidget.onTrigger 用于整卡点击,Host 会提供 pending 单飞与失败反馈;发现和渲染本身不会触发该回调。未提供 Widget.Title 时,Host 使用 capability label 作为受信回退标题。全宽内容必须使用 record-section,不能复用 record-widget。

  • 产品只保存稳定 capability placement,不保存 App 版本或 Worker 身份;升级、卸载后重装仍可恢复原布局。Widget、Section、Tab 分别进入固定共享贡献点,不为每个组件创建新贡献点。
  • Web/H5 使用同一份布局和 { object, recordId },但分别渲染适合当前宿主的 UI。H5 不支持的 Forms/LookupCell 只让当前 Widget/Section/Tab 进入 unsupported,不会拖垮详情或同 App 的其他组件。
  • Widget/Section/Tab 标签和设置候选的发现不启动 Worker;可见 Widget/Section 或当前选中 Tab 才挂载 App View。Section 挂载后可自行初始化查询,Host 不提供数据预取合同。Server Function 仍必须由用户在已挂载组件中显式触发。
  • App 不能声明 capability ID、布局位置、排序 tier、产品 route、viewName、Workspace/Installation 身份或 Native availability metadata。
  • 当前 Record Page 三类组件不开放命令式 navigate();详情 Tab 的 URL 与刷新恢复由产品 Host 统一管理。

Workspace Page

Page 源码放在 src/app/pages/<slug>/page.tsx。一级目录名就是稳定 slug;Page 只声明名称、排序、布局和纯 React 组件,不接收 Router、Workspace、鉴权 token 或宿主 props。

import {
  definePage,
  Destinations,
} from '@77-zhike/app-sdk/client'
import {
  Card,
  Link,
  Stack,
  TextBlock,
} from '@77-zhike/app-sdk/ui'

function QueuePage() {
  return (
    <Stack gap="large">
      <Card title="任务队列">
        <TextBlock>当前没有待处理任务。</TextBlock>
      </Card>
      <Link destination={Destinations.appPage('overview')}>
        返回总览
      </Link>
    </Stack>
  )
}

export default definePage({
  name: '任务队列',
  order: 20,
  variant: 'centered',
  Page: QueuePage,
})

同一 App 内,有 order 的 Page 排在前面并按 order + slug 稳定排序;未声明 order 的 Page 排在后面并按 slug 排序。name trim 后长度为 1~80 个 Unicode code points。variant: 'full' | 'centered' 只表达布局语义,Desktop 与 H5 分别决定具体页面壳。

Page UI 必须使用 @77-zhike/app-sdk/ui 和 @77-zhike/app-sdk/forms 的公开组件,不能依赖 DOM、React Router 或自行复制产品控件。当前 Desktop 支持 Components 与 Forms;H5 支持 TextBlock、布局、反馈、Button、Tabs、Table、Card、Grid、EmptyState、Link 等 portable Components,并支持 Forms,暂不支持 LookupCell。含有不支持能力的 Page 在 H5 会整体显示“不支持”,不会静默丢节点或部分提交。

导航只提交结构化 Destination:

  • Destinations.appPage(slug) 打开当前 App 的另一个 Page。
  • Destinations.record({ object, recordId }) 支持 accounts、contacts、opportunities、tasks、activities 和当前 Workspace 可见的 custom_* 公开 API slug。
  • Link 保留普通链接、复制地址和新标签行为;普通点击仍会在跳转前重验当前 Page 身份。
  • navigate() 当前只允许从已挂载的 Page 调用,三类 Action 都不具备该能力。

目录展示不会启动 Worker。首次打开 Page 时,宿主才会下载并校验 Manifest/Bundle、启动 Installation Worker;因此“Apps 菜单可见”不等于代码已经验证可运行。页面数据读写仍应通过受控 Server Function 完成。

当前 Workspace 用户

Client 与用户触发的 Server Function 都可以同步读取当前 Workspace 成员:

import { getCurrentUser } from '@77-zhike/app-sdk/client'

const currentUser = getCurrentUser()
// currentUser.workspaceMemberId 是 Workspace 成员 ID,不是平台全局 User ID。
import { getCurrentUser } from '@77-zhike/app-sdk/server'

export default async function readForCurrentMember() {
  const currentUser = getCurrentUser()
  return { workspaceMemberId: currentUser.workspaceMemberId }
}

返回值只包含 workspaceMemberId、displayName、primaryEmailAddress、avatarUrl,且为只读快照。它不包含 Token、角色、权限集合或平台全局 User ID;App 仍须通过 Server Function 与公开 API 完成数据授权,不能用该快照自行推断权限。Lifecycle、Webhook 等无人触发入口没有当前用户,调用时抛出 current_user_unavailable;不得回退到管理员、安装者或 Connection 所有者。

基础UI组件扩展

新增Avatar、DescriptionList、Divider、Json、ExternalLink、StatusBadge、TextBlock与Typography,补齐Banner动作、Badge颜色、Card呈现、Grid容器模式、EmptyState操作编排和Table.HeaderCell。原flat exports与默认调用保留,复合写法使用同一节点。

Desktop注册42项,H5注册41项(不含LookupCell),其中包括 Widget.TextWidget、Widget.Title、Widget.Text.Primary、Widget.Text.Secondary、Widget.Loading 与 Widget.Decoration 六个 Widget 语义节点。Link仍仅限Page;H5支持Forms,仍不开放Settings入口。新包要求remote-react.v3,发布前必须先部署对应Host及Bundle准入合同。

新增组件的日常验证:修改Core components/contracts.mjs后执行npm run generate:components --workspace @77-zhike/app-sdk和npm run check:components --workspace @77-zhike/app-sdk,同步双端manifest与adapter。App项目运行zhike dev --workspace <workspace-id>,从对应Workspace的Apps入口打开Page;Desktop Settings仅验证其允许的子集。

仓库内还提供无业务数据库的独立组件预览,命令和证据边界见integration-app-runtime/verification/fixtures/ui-parity/README.md。该预览不替代真实安装、权限与Server Function验证。

表单组件

Desktop 与 H5 均支持 17 个可视 Forms 节点,以及只读 WithState。文本、多行、数字、日期、时间、选择、分组等输入复用各端基础 UI;Host 继续持有 RHF 状态,组件选择不会自动保存。数字空值为 null,日期为 YYYY-MM-DD,日期时间提交为带时区 ISO;旧 string/boolean/enum 语义保留。

RecordCombobox 默认只需在 schema 声明单个 objectSlug(或 Forms.record(objectSlug)),由 Host 使用当前安装的 Records Read 权限读取公开记录,保存值是 { objectSlug, recordId }。空词沿用 Query 分页,非空词使用最多 25 条、无分页的 Record Search,已选值独立 Get。自定义数据源才提供 RecordOptionsProvider.search/getOption 并可自行分页;业务有效性及显式保存仍由消费方负责。WithState 仅订阅指定的 values/errors/submitting,不提供状态修改或程序化提交。

可复制的非 CRM 示例与真实 tarball/Worker/SF/Settings/PostgreSQL 验证入口见 integration-app-runtime/verification/fixtures/forms-parity/README.md。当前尚未发布,所有新增 Forms 能力并入 remote-react.v3,ABI=2 不变;沿用 v1/v2 合同校验。SDK 与双端 Host 应整体交付,不引入新的版本分叉。

Dialog

通过 showDialog({ title, Dialog }) 打开临时界面;内容组件接收 hideDialog()。Desktop 复用 Dialog,H5 复用 Sheet,继续组合已有 UI/Form。当前不提供 DialogList、嵌套 Dialog 或固定 Footer,按钮直接放在正文中。

从 Page/Settings 事件或 Record Action 调用。普通关闭完成 Promise;身份失效或打开失败拒绝。Action 正常返回后已受理 Dialog 可继续使用;保存必须由 App 显式调用 Server Function。Dialog 内不提供 Page 导航身份。详见开发者文档 app-sdk/dialogs/show-dialog。