在 Twenty 上开发"应用":Hello World 示例工程与 twenty-sdk 开发全流程实战指南
导读:本文以 packages/twenty-apps/examples/hello-world 为骨架,系统讲解如何在 Twenty(面向 AI 的开源 Salesforce 替代品)之上,通过
twenty-sdk构建一个可安装、可同步、可卸载的"Twenty 应用"。你将掌握 workspace 认证(remote)、yarn twenty dev开发模式、用dev:add脚手架声明对象/字段/逻辑函数/前端组件/角色/视图等实体,以及用集成测试验证"应用安装成功"的完整闭环。
Twenty 的"应用(Application)"并非运行在 Twenty 内部的插件进程,而是一份用 TypeScript 声明业务实体、并通过 CLI 同步到目标 workspace 的代码包。Hello World 示例正是这样一个最小但五脏俱全的应用:它由 create-twenty-app 脚手架生成,既展示了 twenty-sdk 的实体声明语法,也内置了一套可执行的安装集成测试。本文会先带你完成"认证 → 开发 → 查看结果"的起步流程,再逐个拆解工程结构与底层实现。
一、起步:认证、开发模式与查看结果
1.1 认证你的 workspace
Hello World 工程通过 twenty CLI 与一个正在运行的 Twenty workspace 通信。第一步是把它"绑定"到本地开发环境:
yarn twenty remote:add --api-url http://localhost:2020 --as local
该命令将 http://localhost:2020 这个 API 地址注册为一个名为 local 的 remote(远端),twenty CLI 会把认证凭据写入本机配置目录(默认为 ~/.twenty)。从测试配置可以印证这一结构:setup-test.ts 会向 ~/.twenty/config.test.json 写入形如 { "remotes": { "local": { "apiUrl, apiKey } }, "defaultRemote": "local" } 的配置,说明 CLI 的认证体系以"命名 remote + 默认 remote"为核心。
1.2 启动开发模式
yarn twenty dev
该命令进入开发模式,它会一次性完成四件事:监听文件变更、执行构建、把应用声明同步到 workspace、自动生成类型化客户端(Typed Client,即 twenty-client-sdk)。也就是说,你在 src/ 中每新增或修改一个实体声明,CLI 都会自动把它推送到远端的 Twenty 实例。
1.3 查看结果
打开你的 Twenty 实例,进入 /settings/applications(应用管理)页面,即可看到当前 workspace 中已安装的应用。若 yarn twenty dev 同步成功,Hello World 应用会出现在应用列表中;其附带的视图、导航菜单项、逻辑函数等也会随之出现在对应位置。
二、命令速查:Remotes 与应用生命周期
运行 yarn twenty help 可随时查看全部命令。以下是本示例 README 中梳理的常用命令分类:
# Remotes & Authentication(远端与认证)
yarn twenty remote:add --api-url http://localhost:2020 --as local # 认证并添加 Twenty 远端
yarn twenty remote:status # 查看认证状态
yarn twenty remote:use # 设置默认 remote
yarn twenty remote:list # 列出所有已配置的 remote
yarn twenty remote:remove <name> # 移除某个 remote
# Application(应用)
yarn twenty dev # 启动开发模式(watch、build、sync、自动生成类型化客户端)
yarn twenty dev:add # 脚手架:生成新实体(object、field、function、front-component、
# role、view、navigation-menu-item)
yarn twenty dev:function:logs # 流式查看逻辑函数运行日志
yarn twenty dev:function:exec # 携带 JSON payload 执行某个逻辑函数
yarn twenty app:uninstall # 从 workspace 卸载应用
几个值得注意的语义:
- remote 多环境管理:通过
--as local你可以命名多个 remote(如staging、prod),再用remote:use切换默认目标;remote:list/remote:status分别用于查看全部远端与当前认证状态。 dev:add面向实体而非文件:它交互式地为你生成某一类实体的骨架文件,覆盖了 Twenty 应用支持的全部实体类型,无需手写样板。app:uninstall与dev相对:负责把应用从目标 workspace 彻底移除,是发布/回滚流程中的关键操作。
上述命令的完整可用列表以 yarn twenty help 的输出为准。工程对运行时环境有明确要求,见 package.json:node 需 ^24.5.0、yarn 需 >=4.0.2(声明 packageManager: yarn@4.13.0),且推荐使用 yarn 而非 npm。
三、工程解剖:Hello World 应用由哪些"零件"组成
Hello World 应用的价值在于它展示了 Twenty 应用支持的全部实体类型。以 src/ 为根,目录即类型:
| 实体类型 | 目录 | 对应文件 | 作用 |
|---|---|---|---|
| 应用本身 | src/ |
application-config.ts | 声明应用的 universalIdentifier、显示名与默认角色 |
| 对象 | src/objects |
example-object.ts | 定义一个名为 exampleItem 的自定义对象 |
| 字段 | src/fields |
example-field.ts | 为对象补充额外字段 |
| 逻辑函数 | src/logic-functions |
见下 | 可被 HTTP 触发的服务端逻辑 |
| 前端组件 | src/front-components |
hello-world.tsx | React 组件,可嵌入记录页 |
| 角色 | src/roles |
default-role.ts | 定义默认权限角色 |
| 视图 | src/views |
example-view.ts | 面向对象的列表/看板视图 |
| 导航菜单项 | src/navigation-menu-items |
example-navigation-menu-item.ts | 把视图挂到左侧边栏 |
| 页面布局 | src/page-layouts |
example-record-page-layout.ts | 自定义记录详情页 tab 与 widget |
| Agent | src/agents |
example-agent.ts | 声明一个 AI Agent |
| Skill | src/skills |
example-skill.ts | 为 Agent 提供能力指令 |
所有实体统一由 twenty-sdk/define 暴露的 defineXxx 工厂函数声明,并以 ESM 默认导出。这套"声明式 + 统一标识符"的设计,是 CLI 能够把应用同步到 workspace 的基础。下面深入几类核心实体。
3.1 应用入口:universalIdentifier 是全局契约
application-config.ts 是应用的身份证:
import { defineApplication } from 'twenty-sdk/define';
import { DEFAULT_ROLE_UNIVERSAL_IDENTIFIER } from 'src/roles/default-role';
export const APPLICATION_UNIVERSAL_IDENTIFIER =
'bb1decf6-dee5-43ef-b881-9799f97b02a8';
export default defineApplication({
universalIdentifier: APPLICATION_UNIVERSAL_IDENTIFIER,
displayName: 'Hello world',
description: '',
defaultRoleUniversalIdentifier: DEFAULT_ROLE_UNIVERSAL_IDENTIFIER,
});
关键点:universalIdentifier 是一个 UUID v4(LLMS.md 特别强调所有生成的 UUID 必须是合法 v4,见下文第四节),它在应用的所有安装目标间保持稳定,也是集成测试定位"这个应用是否已安装"的匹配键。defaultRoleUniversalIdentifier 指向应用安装后需要默认存在的角色。
3.2 对象与字段:数据模型的声明
example-object.ts 用 defineObject 声明对象,并通过 fields 内联声明主字段:
export default defineObject({
universalIdentifier: '47fd9bd9-392b-4d9f-9091-9a91b1edf519',
nameSingular: 'exampleItem',
namePlural: 'exampleItems',
labelSingular: 'Example item',
labelPlural: 'Example items',
description: 'A sample custom object',
icon: 'IconBox',
labelIdentifierFieldMetadataUniversalIdentifier: NAME_FIELD_UNIVERSAL_IDENTIFIER,
fields: [ /* { type: FieldType.TEXT, name: 'name', ... } */ ],
});
而 example-field.ts 展示了"独立字段"的写法——通过 objectUniversalIdentifier 把新字段(FieldType.NUMBER 的 priority)挂到已有对象上。这种拆分意味着:对象与字段可以分别声明、分别演进,多个应用甚至可以共享同一个对象的字段增量。
3.3 逻辑函数:可被 HTTP 触发的服务端逻辑
src/logic-functions 下有四类函数,恰好覆盖四种用法:
- hello-world.ts:无参纯函数,返回
{ message: 'Hello, World!' };httpRouteTriggerSettings将其暴露为GET /hello-world-logic-function,且isAuthRequired: false(公开接口)。 - create-hello-world-company.ts:在 handler 内实例化
CoreApiClient,通过类型化 mutationcreateCompany真正向 workspace 写入一条名为 "Hello World" 的公司记录;对应 HTTP 路由为POST /create-hello-world-company,isAuthRequired: true。这是"逻辑函数 = 内嵌 GraphQL 数据访问"的直接范例。 - pre-install.ts:用
definePreInstallLogicFunction声明,在应用安装前执行,用于准备工作(如环境校验),handler 可读取InstallPayload.previousVersion获知升级前版本。 - post-install.ts:用
definePostInstallLogicFunction声明,在应用安装后执行初始化/置数据。
三者共同定义了 Twenty 应用"安装 → 生效"的生命周期钩子。每个函数都显式声明 timeoutSeconds(普通函数 5、安装类钩子可高达 300),超出将被终止。开发时可借助 yarn twenty dev:function:logs 与 yarn twenty dev:function:exec 分别查看日志与手动触发。
3.4 前端组件与页面布局:把 UI 嵌入 Twenty
hello-world.tsx 是一个普通 React 组件:进入页面时用 CoreApiClient 查询公司表第一行并渲染"公司名 + ID"。它通过 defineFrontComponent 包装成 Twenty 可识别的前端组件,随后 example-record-page-layout.ts 将其作为 FRONT_COMPONENT 类型的 widget,挂到某对象的记录详情页(PageLayoutTabLayoutMode.CANVAS 画布布局)上。
值得注意的约束(同样来自 LLMS.md):前端组件必须自适应 Twenty 给定的固定 widget 宽高,不要自带滚动条,除非它专门用于 Canvas 标签页场景。
3.5 角色、视图、导航菜单与 AI 实体
- default-role.ts 声明默认角色:可读/可更新/可软删除全部对象记录,但禁止物理删除(
canDestroyAllObjectRecords: false),为函数执行提供最小权限模型。 - example-view.ts 声明视图
All example items,通过objectUniversalIdentifier绑定到自定义对象并配置列宽与可见性。 - example-navigation-menu-item.ts 把该视图挂载为左侧边栏菜单项。注意:如果创建视图却不创建关联的 navigationMenuItem,视图会出现在左侧边栏上(这是 LLMS.md 明确列出、易被忽视的坑)。
- example-agent.ts 与 example-skill.ts 展示了如何随应用一起分发 AI Agent 与其配套 Skill——这正是 Twenty "designed for AI" 的应用层体现。
四、随工程分发的"LLM 须知"(LLMS.md)
工程根目录还带有一份 LLMS.md,专门面向使用该工程的开发者或 AI 编程助手,其中浓缩了三条关键约束:
- UUID v4 要求:所有生成的
universalIdentifier必须是合法 UUID v4。 - 视图必须与导航菜单项配对:只建 view、不建关联 navigationMenuItem,会导致该视图直接出现在左侧边栏。
- 前端组件不要自带滚动:组件应自适应固定 widget 的宽高;Canvas tab 场景除外。
此外它把 fixtures/rich-app 指认为"更丰富的应用示例"——当 Hello World 无法覆盖你的需求时,可进入该 fixture 查看更大规模的真实组织方式。完整的 Twenty 应用开发文档与更复杂示例位于仓库 twenty-apps 目录体系内。
五、集成测试:验证"应用确实装上了"
Hello World 自带一个端到端的安装集成测试,脚本入口是 app-install.integration-test.ts:
# 确保一个 Twenty server 正在运行
yarn test
README 提示需先启动 Twenty server;实际端口以 vitest.config.ts 中
TWENTY_API_URL的默认值为准(默认http://localhost:2020),可用同名环境变量覆盖。若使用远端 workspace,请把目标地址与该 workspace 的 API Key 一并注入。
5.1 测试做了什么
测试在 beforeAll 中顺序执行三段流水线(均来自 twenty-sdk/cli 编程接口,而非命令行):
appBuild:把process.cwd()(即应用目录)构建成 tarball(tarball: true),产出可分发的安装包;appDeploy:把 tarball 上传部署到 workspace;appInstall:执行安装(此刻 pre-install / post-install 逻辑函数被触发)。
随后主断言通过 MetadataApiClient(来自 twenty-client-sdk/metadata)查询元数据:
const result = await metadataClient.query({
findManyApplications: {
id: true,
name: true,
universalIdentifier: true,
},
});
const installedApp = result.findManyApplications.find(
(application) => application.universalIdentifier === APPLICATION_UNIVERSAL_IDENTIFIER,
);
expect(installedApp).toBeDefined();
其核心逻辑是:用 APPLICATION_UNIVERSAL_IDENTIFIER 在已安装应用列表中精确匹配,命中即证明应用真实安装成功。afterAll 阶段则调用 appUninstall 做清理,保证测试可重复执行、不留残留。
5.2 测试配置的注入方式
setup-test.ts 是 vitest 的 setup 文件,它负责三件事:
- 从环境变量读取
TWENTY_API_URL/TWENTY_API_KEY,缺失时直接抛错并提示先启动本地 server 或在 vitest env 中配置; - 请求
{apiUrl}/healthz做可达性探测,server 未启动则给出明确报错; - 把凭据写为独立测试配置
~/.twenty/config.test.json(与开发配置隔离),并把 token 注入TWENTY_APP_ACCESS_TOKEN。
vitest.config.ts 还设置了两个关键参数:testTimeout / hookTimeout 均为 120_000(构建 + 部署 + 安装的完整流程耗时较长,普通超时会误杀);include 只匹配 src/**/*.integration-test.ts,确保单元与集成测试互不干扰;其 env 段内置了一个开发用默认 API Key 作为兜底(生产环境务必用环境变量覆盖)。
六、总结与下一步
通过 Hello World 示例,一条清晰的 Twenty 应用开发路径已经成型:
- 认证绑定:
yarn twenty remote:add --api-url <workspace> --as <name>; - 实体声明:用
defineObject/defineLogicFunction/defineFrontComponent/defineView等描述业务; - 持续同步:
yarn twenty dev监听变更并自动推送,配合dev:add快速生成新实体骨架; - 验证交付:
yarn test跑通"构建 → 部署 → 安装 → 元数据断言 → 卸载"的闭环。
从工程内部看,这套体系的根基在于两点:其一是统一的 universalIdentifier(UUID v4)契约,让 CLI、元数据客户端与集成测试能用同一个键对齐声明与真实 workspace 状态;其二是 twenty-sdk 的声明式 API + twenty-client-sdk 的类型化 GraphQL 客户端,让对象模型与数据访问都获得编译期类型保障。若想继续深入,建议阅读仓库中的 rich-app fixture(更完整的实体组合)、twenty-sdk 源码(define 与 cli 的具体实现),以及 twenty-client-sdk 的 metadata/generate 目录(理解类型化客户端如何由 GraphQL schema 生成)。
atomcodeClaude Code 的开源替代方案。连接任意大模型,编辑代码,运行命令,自动验证 — 全自动执行。用 Rust 构建,极致性能。 | An open-source alternative to Claude Code. Connect any LLM, edit code, run commands, and verify changes — autonomously. Built in Rust for speed. Get StartedRust0629
MiniCPM5-2BMiniCPM5-2B 是一款面向端侧、本地部署和资源受限场景的 2B 稠密 Transformer,能够达到同尺寸开源模型 SOTA 水平。Markdown00
GLM-5.3GLM-5.3 与 GLM-5.2 使用相同的基座模型——所有提升均来自后训练。与 GLM-5.2 相比,它在复杂编程和长程任务上的表现显著提升。Jinja00
HivisionIDPhotos⚡️HivisionIDPhotos: a lightweight and efficient AI ID photos tools. 一个轻量级的AI证件照制作算法。Python07
DragonOSDragonOS is an operating system developed from scratch using Rust, with Linux compatibility. It is designed for **Serverless** scenarios. 使用Rust从0自研内核,具有Linux兼容性的操作系统,面向云计算Serverless场景而设计。Rust00
Spark-X2.5-1.7BSpark-X2.5-1.7B 旨在让强大的 AI 更加实用、高效且易于获取。这些模型在广泛的日常任务中表现出色,涵盖对话、写作、翻译、推理、编程、工具调用和智能体工作流,并在同等规模的开源模型中取得领先结果。Spark-X2.5 将面向效率的架构与最高 1M tokens 的原生上下文窗口相结合,并支持 200 多种语言。Python00