关于 flow-generator
用于读取、转换、校验并写回 Adapty 流程配置的技能,既支持修改现有流程,也支持创建新流程。它以现有可运行配置为默认起点,并涵盖屏幕产品绑定、条件、操作和输入等结构。
主要功能
- 01读取并转换 Adapty 流程配置
- 02校验转换结果并写回
- 03创建新流程及屏幕结构
- 04处理产品与优惠绑定
- 05支持条件、操作、输入和标签组合
适用场景
- 01调整现有付费墙流程的屏幕或按钮
- 02创建带产品和优惠绑定的新流程
- 03校验流程配置后保存更改
安装
复制下面的命令,在终端中运行。
npx skills add https:/ / github.com/ adaptyteam/ adapty-skills --skill flow-generator安装后在 Claude Code 中输入/flow-generator确认已加载。
包含的文件
除 SKILL.md 外,安装时一起下载的参考资料和脚本。
references/canvas.pycrop.pycustom-icons.jsondiff-config.pyfidelity.mdflow-schema.mdflowkit.pygates.shicons.pylocalization.pymedia.mdmerge.mdmobile-preview.mjsmontage.pypatterns.mdplacements.mdpreview-with-playwright.mjspreview.mdproducts.mdrename-screens.pyrender-measure.pyshoot.shsnippet.pysnippets.mdtransforms.mdvalidate-with-schema.mjsvalidate.mdverify-config.py详细介绍
流程生成器
读取 Adapty 流程的构建器配置,对其进行转换、检查结果并写回。
默认情况下,转换已有配置是更安全的做法:你输出的所有内容都有一个已能正常运行的文档作为依据。
也可以编写新流程,但确实有两种情况无法合成,而且仅此两种:
- 你没有可读取文件的图片。 如果你有图片路径,现在就可以上传:
flows media upload(media.md)。如果你只能看到图片——它是粘贴或附加到对话中的——那就不算拥有图片文件:请索要路径。没有文件就无法上传,因此目录条目会保持空白,绝不能编造 URL(陷阱 5)。SVG 上传会失败,因此单色字形应以内联方式写在_meta.icons中;无法用任何元素表达的图形,则需要绘制并栅格化(media.md)。图标不属于此列:python3 references/icons.py --search <word>可以查找任何 phosphor 字形;即使标记写得再好,虚构的名称也只会显示空白(陷阱 23)。 - 真实的商店价格。 价格来自商店,而不是 Adapty;
products create没有价格标志。
其他内容都可以获取:通过 adapty products list(或 products create)获取产品 UUID,从参考截图中提取 theme 颜色,以及使用 references/icons.py 随附的图标包查找图标标记。
flowProductId——_meta.screens[].products[] 中每个屏幕的声明——是推导出来的,不必等待。flowkit.predeclare(screen_id, products) 会写入这个区块;请传入准确的 Product + Offer 配对、单独的产品 ID,或 (product_id, offer_id) 元组,因为 Offer 是 ID 的一部分。省略此声明会导致设备预览返回 422。重写流程时,应沿用现有的 _meta.screens,不要重新生成——也要一并沿用 screens[].products,因为它是 _meta 的派生来源,归屏幕所有。绝不要写入 "products": []:对构建器来说,不存在和为空代表完全不同的含义。编写流程时,flowkit.screen() 会根据屏幕自身的绑定为你写入注册表和结构化产品引用——使用 product(..., offer_id=...) 绑定 Offer,点号分隔的价格变量就会自动关联到它(products.md)。
编写流程时,references/flowkit.py 负责机械性工作——尤其是上文的 hierarchy/map 划分;patterns.md 则负责定义各种形状。
它还涵盖条件(when/ref/all_/not_empty)、全部 14 种操作类型、8 种输入和 tabs 组合项——每一种都会在形状不符合转换服务要求时抛出错误。
表达方式
用户读到的每一行文字,包括固定区块:
- 用“I”描述你做的事,用“你”描述用户做的事。 开头直接说明改了什么;结尾给出一个下一步。不要寒暄、复述用户请求或叙述操作阶段。
- 像耐心的同事,而不是日志。 假设用户可能刚接触流程;用他们能理解的方式说明步骤的用途,说明一次即可。
- 每个区块最多加粗一处。 固定区块的标题就是那一处;步骤标签、列表引导语和待检查事项都保持普通文本。
- 仅当用户需要输入或能在其他地方看到时,才显示原始值——例如流程名称、屏幕名称或构建器中显示的按钮标签、价格、链接。绝不显示元素 ID 或屏幕 ID、内部操作名称(
closeFlow、navigate)、配置键(_meta.screens)、语言代码、文件名、元素数量、检查器输出(valid: true)或工具名称。应说明其含义:“Continue 按钮现在会打开付费墙”、“西班牙语”、“发布检查已通过”。 - 提供完整、可点击的链接,单独成行,不要放在反引号中。
- 使用用户的语言回复,固定区块也一样。构建器标签必须与构建器中的显示完全一致。
输出内容
用户读的是你的消息,不是这个文件。请简短作答。 只有四个固定内容,其他都不固定:阶段 5 中,写入前的确认请求和写入后的收尾提示;阶段 2 中,只要构建所需的素材里有任何一项没有对应文件,就打印缺失素材块;以及阶段 6 中,在花费不可逆的开发者 ID 之前提出的放置位置请求。填好它们的占位内容,不要凑字数。
缺失素材块在阶段 2 发出,与产品问题合并,因为对方提供的路径能让占位内容变成完成的屏幕。**已经上传的你绘制的图不算缺失:**它只能出现在绘图行中,不能作为表格的一行;如果只剩绘图和字体,就省略标题、表格和路径,只打印这些行。上传路径并不对等——每一行都同时提供两种路径,会推荐一条最终会遭拒的路径:
缺少
<n>项素材——在这些素材到位前,屏幕会使用占位内容。
内容 用途位置 尺寸 1 <素材内容,用对方自己的描述><在屏幕上的位置><w>×<h>请告诉我每项素材分别如何处理,或者统一选择一种方式:
- 给我一个路径——我会上传并绑定。仅限图片。
- 在 https://app.adapty.io/flows/`<FLOW_ID>`/builder 中自行上传——占位内容已经设置好样式,上传后即可完成。
- 围绕它重新设计——我会用这种格式可以实现的内容替换该区域,并说明我的选择。仅当参考图是别人的屏幕,且该素材本就不可能由你提供时,才选择此项。
在你回复前,这些素材都会以占位内容显示。
<仅当你绘制了替代图时:><n>项是我绘制的图,已经上传,屏幕看起来已经完成——<哪些图>。它们只是替代图,不是你的作品:请通过路径或在构建器中替换每一张。
<仅当缺少视频片段时:><内容>是一段视频,但视频片段没有上传路径——我也无法代为处理,所以无论你选择哪种方式,这项都必须由你提供。该元素已经放在屏幕上,并调整为<w>×<h>以匹配设计:打开 https://app.adapty.io/flows/`<FLOW_ID>`/builder,点击该元素并上传文件。屏幕的其他部分不会改变。
<仅当缺少字体时:><内容>使用了此账号没有的<描述>;我会改用<替代字体>,<说明差异>。字体只能在构建器中设置——我无法上传字体: https://adapty.io/docs/using-custom-fonts-in-flow-builder.md —请上传字体,为每种字形设置 iOS 和 Android 字体名称(若未设置 iOS 字体,工作流将无法发布),然后告诉我字体家族名称,我会让主题使用它。
在每项能力旁边说明构建器目前无法实现的内容:对于设计中工作流无法执行的每种行为或效果(创建账号、权限提示、模糊效果),说明屏幕实际上会做什么(“向你的应用发送 create_account;工作流不会创建账号”)以及适用的处理方式——由应用响应的自定义操作、如果引导流程要继续则通过独立的放置位置使用第二个工作流,或使用图片或视频(在构建器中上传)呈现效果——并称其为“构建器目前尚不支持”,绝不要说不可能。
当处理方式包含自定义操作时,列出操作 ID,并主动提出为应用编写处理程序:“我可以在你的应用中添加响应这些操作的代码。需要我来写吗?”——对方答应后,将这些 ID 交给负责应用代码的 adapty-integration 技能;如果没有该技能,就使用 adapty-docs 技能查找平台的“Respond to actions”页面并链接。
你可以上传大小不超过约 2.5 MB 的图片;SVG、字体、视频和任何更大的文件只能在构建器中处理(fidelity.md)。阶段 5 的提示中,待处理数量单独占一行,不要重复表格内容。 每次运行调用过 CLI 后,都向 Adapty 发送一条反馈——无论 CLI 是写入了内容、交接了文件,还是遇到无法解决的问题而停止。反馈会告诉 Adapty,你使用此技能和 CLI 时任务进展如何,包括顺利完成的情况。请用英文,按以下其中一种格式撰写——只需遵循格式,不要照抄示例文字:
为六个屏幕的引导流程添加了德语。
拒绝了 <用概括性措辞描述请求>,因为 <此技能说明的限制>。
我做了什么:尝试更改一个单屏付费墙的按钮标签并保存。有什么帮助:此技能说明了按钮标签的位置,因此只需一步就完成了修改。遇到了什么阻碍:flows 配置更新因 http_500 失败了两次(请求 ID:req_7f3a9c),而此技能没有说明重试是否安全。
-
反馈的是你自己的结果,绝不是用户的心情:
positive——按此技能所述完成,或因该技能说明无法完成而拒绝。用一句话说明你做了什么或拒绝了什么,以及原因;不要提及检查、保存或进展情况:评级已经说明了这些。neutral——绕过错误或采用变通办法后完成,或发现流程中有问题但未处理。第三部分要描述问题的特征(例如“某个按钮会打开流程中已不存在的屏幕”),绝不能提及用户的具体设置。negative——此技能或 CLI 阻止了你,或你交付的内容明知有误。
中性和负面反馈必须包含全部三部分。
-
每句话都只涉及任务、此技能或 CLI:例如变更类型、此技能中的某个步骤、某条命令及其错误。不得提及你在用户账户中看到的任何信息(例如他们如何设置版位、受众、A/B 测试、产品或细分受众,以及任何数量或数值),也不得提及他们的文案、品牌、产品名称、文件路径或业务信息。
--context中的 ID 已足以让 Adapty 找到其余信息。 -
**措辞直白,每个部分各用一个简短句子。**前两部分不得出现脚本、文件或标志名称。第三部分要写出命令及其错误,并说明此技能或 CLI 本应告诉你什么。
-
--context携带 ID 和请求内容:应用 ID、流程 ID,以及对请求的概括性描述——app <app id>, flow <flow id>: rename a button and save the flow,不要使用用户的原话。 -
将反馈内容直接写入命令的单引号中,不要从文件或脚本读取,这样审核命令调用的人都能看到将发送的内容。单引号内的 shell 不会运行任何内容,因此要删去反引号,并将 “doesn't” 写成 “does not”:
$ADAPTY feedback --feature flow-generator --sentiment <positive|neutral|negative> \ --message '<the note>' \ --context 'app <app id>, flow <flow id>: <what was asked, generic>' -
仅当命令打印出
Feedback sent.时,才在结束消息中用一句话告知用户:使用用户的语言,不提及反馈内容,例如“我还向 Adapty 发送了一条简短反馈,说明了此次任务的进展。”如果出现其他任何结果(错误、调用被拒绝或阻止,或$ADAPTY feedback --help表示没有此命令),都不要添加任何说明,即使原本打算发送反馈也不例外。绝不重试,也不要因此耽误任务。 -
用户可以关闭此功能:如果用户表示不要发送反馈,本次会话余下时间都不要发送。如果用户在运行过程中告知你此技能或 CLI 出错,应立即按其原话发送反馈。
其他内容要么一句话说明,要么省略——变更内容、仍需用户处理的事项(待关联的产品、待上传的资源)、存在两种合理答案时所做的决定,以及检查覆盖或未覆盖的内容。每件事只说一次:如果请求确认时已经提到某项内容,结束说明就不要重复。
不要叙述各个阶段,不要复述配置,不要列出你未处理的警告,也不要向只请求流程的用户解释 CLI。
参考资料
每个文件负责维护自己的事实;应链接过去,而非重复陈述,否则副本会逐渐偏离。
| 文件 | 何时阅读 |
|---|---|
| flow-schema.md | **每次编辑前。**信封、## Invariants、## Shape traps 和 ## Vocabulary——将用户请求映射到 JSON 中对应名称的指南 |
| validate.md | validate 报错时,或想了解通过验证的运行结果不能证明什么时 |
adapty-docs 技能 | 需要本文未链接的 Adapty 文档页面时——例如控制面板行为、CLI 或 API 详情。它会引导你找到正确的文档索引,而不是猜测 URL |
| preview.md | 渲染结果出乎意料时:了解它无法显示什么、需要多少成本、四种彼此不一致的界面,以及失败时该怎么做 |
| fidelity.md | 给出了参考图时——逐元素清单、缩小差距的步骤,以及哪些内容需要询问用户 |
| media.md | 屏幕包含图像时:了解上传限制、元素与 fill 的形状、几何属性,以及何时应栅格化 |
| products.md | 操作 product 元素前——products create 会写入线上控制面板 |
| merge.md | 流程生成后被人手动编辑过,或你打算对现有流程重新运行构建脚本时 |
| transforms.md | 遇到两种答案都有道理,而唯一错误的做法是保持沉默时 |
| patterns.md | 需要无法凭空猜出的复合元素时:标签页、进度条、切换开关、倒计时、方案卡片 |
| placements.md | 阶段 6——所有 placements 拒绝及其负责人、update 变体、控制面板 URL 及其参数,以及开发者 ID 为什么不可逆 |
paywall-teardown 技能 | 在阶段 2,当你选择销售屏幕的设计时;在阶段 4,为所构建的内容评分时。它负责判断屏幕能否促成销售;本技能负责 JSON |
onboarding-teardown 技能 | 当你选择的是一个序列时,执行相同的两个阶段——引导流程、欢迎页、问答、激活流程。它负责流程的形状以及引导流程→付费墙的衔接。若一个流程两者兼有,则两者都运行 |
adapty-integration 技能 | 阶段 6,当 placement 指向该流程后。它负责应用端——获取、渲染和调用位置;本技能只向它提供一项内容:placement developer ID |
所有可执行文件都位于 references/ 中:flowkit.py(编写)、icons.py(查找字形,适用于任何阶段)、localization.py(阶段 2,目录)、verify-config.py(阶段 3)、validate-with-schema.mjs(阶段 3)、diff-config.py(阶段 2 和阶段 5)、montage.py、render-measure.py 和 canvas.py(阶段 4,以及有人询问屏幕如何连接时)、preview-with-playwright.mjs(渲染失败时)、mobile-preview.mjs(阶段 5,设备预览二维码)。
CLI 概览
$ADAPTY auth login # browser flow
$ADAPTY auth whoami # verifies the token server-side
$ADAPTY apps list --json # to get <APP_UUID>
$ADAPTY flows list --app <APP_UUID> [--page N] [--page-size N] # page-size max 100
$ADAPTY flows create --app <APP_UUID> --name <name> # row only; always `draft`
$ADAPTY flows get <FLOW_ID> --app <APP_UUID>
$ADAPTY flows config get <FLOW_ID> --app <APP_UUID> --json # errors until first write
$ADAPTY flows config validate <FLOW_ID> --app <APP_UUID> (--config-file <f|-> | --config <json>) --json
$ADAPTY flows config preview <CONFIG_FILE> [--screen <id>] [--device <id>] [--orientation …]
$ADAPTY flows config update <FLOW_ID> --app <APP_UUID> \
(--config-file <file|-> | --config <json-string>) \
[--expected-updated-at <int>] [--remote-configs <json>]
$ADAPTY flows media upload <IMAGE_FILE> --app <APP_UUID> --json # PNG/JPEG/WEBP/GIF, < ~2.5 MB; no SVG
$ADAPTY flows update --app <APP_UUID> <FLOW_ID> --name <name> # --name required; 405 in prod, rename in the builder
$ADAPTY flows publish --app <APP_UUID> <FLOW_ID> [--yes] # async; 404 in prod, see below
**在此处解析一次 $ADAPTY,并在每条命令中使用它。**全局安装的 adapty 通常版本过旧——曾在一台真实机器上测得版本为 0.3.0,该版本没有 flows 主题;有三个 Agent 因此误以为“validate 和 preview 不存在”,跳过了阶段 3 和阶段 4:
adapty --version # >= 0.8.0 ? ADAPTY="adapty", done
npm i -g adapty@latest >/dev/null 2>&1 \
&& ADAPTY="adapty" \
|| ADAPTY="npx --yes adapty@latest" # fallback: prefix not writable
**只安装一次;不要在每次调用外都套上 npx。**与已安装版本的 0.07 秒相比,这个包装器每次调用要花约 1 秒,而一次运行会调用几十次。如果全局前缀不可写,仍可使用 npx 形式;此时 --yes 不是可选项:不加它,npx 会停下来请求安装许可。
只有在 npx --yes adapty@latest 和 npx --yes adapty@beta 都不支持某条命令后,才能声明该命令不可用——绝不能根据你在某处看到的版本号作出判断。
在 macOS 默认使用的 zsh 中,多词 $ADAPTY 不会拆分成多个词,因此下面的每条命令都会失败,并提示 command not found: npx --yes adapty@latest。在同一个 shell 中运行一次 setopt shwordsplit(已验证),或完整调用 npx --yes adapty@latest。这是 shell 问题,绝不代表命令或 CLI 缺失。
**flows media upload 在生产环境中可用。**它接收本地图片文件,并打印一个实时 CDN URL,可将其绑定到配置中。因此,用户给了你一个文件,让你处理的图片就由你来放置,不必再向用户索要。使用时要注意两个限制:SVG 会返回 http_500,文件大小上限为 约 2.5 MB(单独出现 http_400 表示文件过大)。使用 --json 运行时,将输出重定向到文件,并保留该文件:人类可读输出会省略 preview_base64,配置会将其绑定为 previewValue,而且没有其他方式能返回它——因此,该文件就是此资源的记录;再次上传会创建第二个资源,而不是取回原值。调用格式、要捕获的三个值、它们所绑定的两种配置格式、没有预览图时如何取回预览,以及几何信息:media.md。
并非每个构建版本都包含 flows publish --app <APP_UUID> <FLOW_ID>。按照上面的规则,应通过运行 flows publish --help 来判断,而不是看版本号——该命令此前已经从稳定版中移除过,因此用数字版本下限并不能可靠判断。它自身的标志是 --app 和 --yes/-y,还可加上 CLI 的全局 --json。调用方式受五个经过验证的事实影响:发布是异步的,因此响应会显示 status: publishing,不会显示 published——应如实报告状态,不要声称工作流已经上线;确认提示会输出到 stderr,因此 --json 的 stdout 仍可解析;在没有 --yes 的情况下,使用 --json 或在非 TTY 环境中运行会拒绝执行并以退出码 2 退出(提示 Re-run with --yes),而不是挂起,因此无头运行只有在用户已在对话中确认后才传入 --yes;用户拒绝提示时会以退出码 1 退出(Cancelled, nothing was sent.);没有配置的工作流会以退出码 1 退出,并提示 Flow has no current version.
**发布成功后,它会告诉你接下来该运行什么,你就照做。**人类可读模式会打印三行:说明发布是异步的且工作流尚未发布;提供要运行的 flows get 轮询命令,直到状态显示 published 或 publication_failed;以及在发布失败时说明原因的 flows config get 命令。使用 --json 时,这些内容会被抑制,你只会得到 status: publishing,没有其他信息——仍然要继续轮询。如果状态为 publication_failed,flows config get 会说明原因:其响应封装会在配置旁附带 publication_status、transform_error 和 publication_error,而 transform_error 是转换服务给出的异议。它是一个原始字符串——可能是 JSON 问题载荷,也可能是摘要——CLI 没有辅助工具来解析它,因此请阅读并引用原文,不要重新推断原因。如果 API 没有发送这些字段,它们就会缺省;这不是错误,也不代表发布成功。
**有两个因素会阻止此操作,它们都与账户无关。**一个是上面提到的 CLI 版本。另一个是 API 部署:曾观察到 flows publish 返回 http_404,以及 flows update --name 返回 Method "PUT" not allowed。这两种错误都不代表命令写错了,也无法通过切换账户来解决——它们取决于具体部署,对该部署上的所有账户都一样——因此应说明这一点,并让用户使用编辑器的发布按钮或构建器的重命名字段。**但在确认之前,先实际运行调用。**这两条路由尚未验证,不能断定它们不存在;因此,应把 404 当作你观察到的结果,而不是预期会发生的结果。
目前仍然没有 flows delete。 删除操作需要在仪表板中进行,因此绝不要声称已删除某个流程。绝不要写 CLI 不支持的命令名称,也绝不要编造标志——config validate 仅接受 --app、--config/--config-file 和 --json。
关于配置命令,有四点无法靠猜测得知:
-
config get返回的是封装对象,而不是配置本身:{config, remote_configs, status, updated_at};如果上次发布失败,还会包含publication_status、transform_error和publication_error。你要转换的文档是config字段,而update和validate都只接受该字段。将封装对象交给validate会返回Invalid flow input——这看起来恰好像配置有问题,但实际并非如此。(preview是个例外:它两种格式都接受。) -
status不是由你来填写的。 它属于封装对象,如果将其放进config,就会被丢弃。发送给update的配置中不要包含它,也不要把它缺失视为缺陷。(浏览器导出文件的顶层确实带有status和id——那是另一种文档格式;如果用户想要文件该怎么处理,请参阅第 5 阶段。) -
锁定令牌只来自
flows config get,别无他处。 有两个字段都叫updated_at,但含义不同。config get封装对象中的该字段是 epoch 毫秒(1787210847609),表示内容最后一次更改的时间。这个值就是乐观锁。flows get、flows list和flows create中的该字段是一个 ISO 字符串(2026-09-24T12:14:40Z),表示流程行(名称、状态)最后一次更改的时间。发布轮询后你已经拿到了后者,但它是错误的令牌。 -
令牌基于你的草稿,因此应保存在
flow.working.json中。 第 2 阶段的config get会将其写入该文件,而每次成功的config update --json都应将其移入该文件,并替换其中的令牌(回显的值与下一次get返回的值相同)。从该文件中读取令牌,绝不要在写入前重新执行config get来读取:新读取到的是最后保存者的令牌,会让锁绕过它本来要捕获的编辑。UA="$(jq -r .updated_at flow.working.json)" # right: the version your draft was patched from $ADAPTY flows config update <FLOW_ID> --app <APP_UUID> --config-file draft.json \ --expected-updated-at "$UA" --json > write.json && mv write.json flow.working.json只有成功时才会执行
mv:写入失败时,错误 JSON 会打印到 stdout;如果直接重定向到flow.working.json,就会用它替换你的令牌。原始 ISO 字符串会在发送任何内容前失败(
Expected an integer,退出码 2)。绝不要将它转换为毫秒来让它符合要求:写入会返回409,并提示 "This flow configuration was already updated by ",其中点名的人并未修改过它。使用flow.working.json中的令牌时遇到409,才表示有真实编辑:处理方法见第 5 阶段。绝不要移除该标志,也绝不要只重新获取令牌来绕过它。 -
config update没有试运行模式。validate和preview是预检步骤,且都必须在写入之前运行——为什么顺序很重要,请参阅第 5 阶段。
六个阶段
1. 先确定调用方式,再进行身份验证
首先按命令行界面中的说明设置 $ADAPTY——检查
adapty --version;如果版本较旧,就一次性全局安装,不要在之后的每次调用中都承担 npx 包装器约 1 秒的开销(仅在备用情况下使用 npx)。在运行第一个命令之前完成这一步,而不是等命令失败后再做;并在接下来运行的同一条命令中打印已确定的版本,避免两者不一致。
然后运行 $ADAPTY auth whoami。它会访问服务器并打印名称和所属公司,因此能证明令牌有效。优先使用它,而不是 auth status:后者只报告本地存储的内容,不会验证令牌——即使令牌已失效,它仍会打印已存储的名称和令牌前缀。
如果失败,$ADAPTY auth login 会打开浏览器。这一步需要用户完成;请等待用户操作,不要循环重试。随后运行 $ADAPTY apps list --json,获取后续每条命令都需要的 <APP_UUID>。
然后确认请求实际从哪里开始,因为并非每次运行都是编辑。 “我发布了流程”、“如何把它放进我的应用”、“应用里什么都没显示” 属于阶段 6请求:流程已创建并发布,缺少的是指向它的放置配置,以及应用自身的调用位置。直接进入阶段 6——阶段 2-5 无需处理——并说明你将这样做,以便原本想要编辑的用户可以及时纠正你。
“我的屏幕是如何连接的”、“这个按钮会跳到哪里” 属于读取请求:使用 flows config get 将流程保存到 flow.backup.json;如果用户想看缩略图,就渲染屏幕;然后打开流程图(阶段 4)。不会写入任何内容,因此无需审批。
2. 新建流程,还是使用现有流程
明确判断并说明你的选择,因为这两条路径可能造成的破坏不同。
现有流程——由用户指定,或运行 flows list 并向用户确认匹配项,然后再进行操作。接着运行 flows config get,写入时保留其 updated_at,不要使用 flows get 或 flows list 打印的值。
首次编辑前先备份。config update 会替换整个配置,且无法撤销,因此你获取的副本是唯一的恢复途径:
$ADAPTY flows config get --app $APP $FLOW --json > flow.working.json
cp flow.working.json flow.backup.json
jq .config flow.working.json > draft.json
两个文件,两种结构。flow.working.json 和 flow.backup.json 是 config get 和 config update 返回的信封结构;draft.json 是你要编辑、检查、预览和发送的纯配置。新流程在首次写入前没有信封结构:直接在 draft.json 中编写。
**文本位于目录中。**每个可本地化字段都包含一个 {_lid} 引用,其各语言区域的值存储在 localization.content 中——因此,文案修改或翻译应更改 content[id].values[<localeId>],并保留所有 ID;如果同一条目被多个字段共享,则修改会影响所有这些字段(见 transforms.md 决策 1)。任何内联编写的内容——flowkit 片段、目录模板或手写值——都必须在阶段 3 之前加入目录:
python3 references/localization.py catalog draft.json
对于 schemaVersion 低于 13 的已获取配置,先按相同方式转换;低于 12 时,用户需在 Flow Builder 中打开并保存,然后你再重新获取。规则见:
flow-schema.md → 本地化:目录。
**基于刚获取的内容进行修改。**构建脚本或之前运行时留下的 draft.json,可能不包含用户此后在构建器中所做的更改;而 config update 会替换所有内容
(merge.md)。如果存在这样的副本,就将其与线上版本进行差异比较,报告 ADDS/CHANGES,作为用户的编辑,并将其移开:
python3 references/diff-config.py <the-old-local-copy>.json flow.working.json
新建流程 — 运行 flows create,然后从用户已有的流程中获取配置作为种子
(flows list → config get),这样主题、字体、本地化设置和产品都是真实的 — 在 重建 模式下
(见下文),参考设计的样式会取代种子配置的主题。首次运行
config update 时不带 --expected-updated-at。对于用户称为新建的任何内容,新建流程都是安全的默认选择,
因为 config update 会替换所有内容,而在已有内容的流程上生成会丢弃这些内容。
然后,在编辑之前:报告源配置包含的内容 — 屏幕及说明文字、 本地化设置、产品、导航图。先于任何提议;这能让用户了解现状,并立即发现流程选错的情况。
确认转换方式。 支持范围包括:添加本地化设置、改写文案、添加/移除/重新排序屏幕、
分支和条件、重命名屏幕 ID,以及复用其他流程中的片段 — 其依赖解析已测得存在硬性 422 错误类别
(flow-schema.md 不变量 8),因此必须通过 references/snippet.py 运行,绝不能手动操作
(见 snippets.md)。超出这些范围的请求应明确说明不在支持范围内,而不是自行臆测处理。
如果请求是 “保存这个以便复用” 或 “添加我保存的内容”,在任何 graft 操作之前运行
references/snippet.py plan — 请先阅读 snippets.md。
如果请求是 “给屏幕设置易读的 ID” — 通常是为了让客户自己的分析不再显示
scr_oAPBHPa7 — 运行 references/rename-screens.py,绝不能手动编辑:屏幕 ID 会出现在三个
位置,而最容易被漏掉的 _meta.screens 会导致流程无法发布。重命名会中断分析数据的连续性,因此应在第 5 阶段的确认中提出(见 transforms.md
决策 9)。元素 el_XXXX ID 不属于此操作,不得为了匹配而重命名 — 错误的 ID 会编译进运行时脚本,导致黑屏,而且这样做毫无益处:客户的分析系统看到的 ID 是 props.customId
(见 flow-schema.md 陷阱 7b)。
你拿到的是设计吗?它属于谁? 请明确回答:这决定了屏幕各部分的来源。
| 你拿到的内容 | 模式 | 结构来源 | 样式和文案来源 |
|---|---|---|---|
| 他们自己的设计:他们的 Figma、自家应用的截图,或明确要求“重建/匹配/一模一样地复制” | 重建 | 参考设计 | 参考设计 |
| 别人的设计,并且已说明模式:“用我的风格”“从中汲取灵感”“为我的应用调整” | 改编 | 参考设计 | 该应用 |
| 没有设计 | 由你选择 | 拆解技能(见下文) | 该应用 |
如果请求没有说明模式 — “构建同款引导流程”“做一个类似这样的” — 请在开始构建前先询问,只问一个问题,然后等待回复:
你想要一份完全一致的副本,还是保留相同结构,但采用你自己的应用风格和文案?
猜错会浪费整个构建过程,还会留下一个无法删除的流程;提问只需一轮。 只有在无人能够回答时(例如无人值守运行,或用户要求不要等待),才采用 改编 模式,并用一句话说明。
在改编模式下,参考设计提供结构,应用提供其他所有内容。 沿用参考设计的顺序、 版块、组件模式和层级。采用应用的配色、字号比例和字重、圆角、按钮和卡片样式、图标风格及间距节奏,并且为该应用撰写文案:使用它的名称、产品和语气。绝不要沿用其他品牌的名称、宣传语、优惠或社会认同内容。
如果当前会话位于应用的代码仓库中,就从代码中获取应用样式(主题、颜色资源、
按钮和卡片组件、字符串);否则从应用自己的流程中获取 — 优先查看其投放位置展示的流程。两者都没有时,询问一次;如果没有答复,就采用参考设计的样式并说明原因。
在任何模式下,都要遵守 Adapty 的流式布局规范(width: fill、height: hug、
position: relative):绝不要为了匹配截图像素而硬编码固定尺寸或偏移量,
因为固定几何尺寸会导致布局在不同设备上出问题(团队已诊断)。在 Rebuild 中,要对照
参考文件,而不是凭记忆(第 4 阶段)。没有设计,就意味着由你来选择——“帮我
构建一个付费墙”“帮我构建一个引导流程”“做一个能提高转化率的设计”——请求映射只会把名词
转换为元素类型;它完全不会说明什么设计能促进销售。
当你负责选择时,拆解技能就是参考依据;该用哪个,取决于你正在构建什么——而不是你上次用了哪个。
| 你要构建的内容 | 参考依据 | 返回内容 |
|---|---|---|
| 一个用于销售的屏幕——付费墙 | paywall-teardown | 一个原型(屏幕的构成),以及该垂直领域需要的模式 |
| 一组连续流程——引导、欢迎、测验、激活 | onboarding-teardown | 一个骨架(流程结构),以及按屏幕安排的模式 |
| 两者兼有——以付费墙结束的引导流程 | 两者。 onboarding-teardown 负责流程和衔接部分;paywall-teardown 负责付费墙屏幕本身 | 先运行负责流程的那个:它会决定付费墙需要体现什么 |
在编写任何内容之前先调用它们。两者都会列出它们拒绝臆造的值——评分、 评价数量、结果数据、折扣、主视觉素材;对于流程,还包括应用必须真正兑现的个性化承诺背后的内容。在编写 配置之前,先向用户询问这些信息;如果没有答案,就省略该元素,而不是填入一个看似合理的数字:缺少 一个元素可以补上;捏造评分则是在真实买家面前撒谎。构建它命名的结构,不要拿你上次构建的结构来替代——这正是两个无关的垂直领域最终出现相同屏幕的原因。它还会在第 4 阶段评估结果,此时仍可免费修正。而且,当 用户想了解某个流程有多好,而不是想修改它时,应该进行拆解,而不是转换。
构建过程中没有人可供访谈。 用户带着流程来寻求拆解时,onboarding-teardown 的六个
问题是它的入口;从这里调用时,则根据需求说明、配置和目录来回答这些问题,剩下的问题与上述
其他问题一起打包询问。不要在构建过程中发起六轮问卷。
产品由用户选择——先查目录,再查商店 ID,最后才创建。 对于任何用于销售的屏幕,都要在 设计之前按以下顺序确定产品,绝不要默默跳过任何步骤:
- 运行
$ADAPTY products list并展示已有内容——标题、周期、商店绑定——然后询问用户其中哪些应放在屏幕上。大多数账户已经有合适的产品。 - 只有在没有合适产品时,才询问用户的商店产品 ID(订阅需要 App Store 产品 ID;Google
产品 ID 加上基础方案 ID)——这些是
products create必需的绑定,因此等到之后再问只会让创建流程卡住。 - 然后才运行
products create,并通过单独的确认关卡进行操作 (products.md → 创建产品)。
要在设计之前确定产品,因为产品目录会限制设计:试用期时间线需要经过验证的优惠;周期切换器需要仅周期不同的套餐;价格变量需要匹配的周期。替用户挑选并不是捷径——这决定了他们要卖什么,也是屏幕上唯一无法从截图中看出的选择。
只有应用知道的内容属于自定义标签,而且需要编写应用代码。 用户的姓名、金币余额、连续签到天数、等级、剩余天数、所用套餐——任何流程无法自行计算出的内容。用户会询问它显示的内容,而不会询问“自定义标签”。先检查流程是否已经询问过该内容:用户在先前屏幕上输入或选择的值,属于某个输入项的 <customId>.value 或某个组的 selectedOptionId,显示这些值完全不需要应用代码。否则,用 flowkit.app_value() 声明它,用 Var 显示它,并在这里与产品相关问题一起,一次性确认两件事:应用代码要传入的名称(如果对方已有名称,就用对方的)和回退值——在应用提供该值之前,所有用户都会看到的内容。如果没人可问,就自行决定两者并说明。仅限文本——基于它进行分支判断无法构建(flow-schema.md → 陷阱 25)。
资源也在这里解析——上传文件,然后用它的 URL 构建。 上传操作读取的是路径,所以只能看到的图像并不等于你拥有的图像:用户粘贴或附加的图像会以像素形式出现在你的上下文中,但背后没有文件,你也无法写入看到的字节。
对于屏幕所需的每个资源,在编写元素之前,先确定以下三种状态之一:
- 你有可读取的路径——对方提供的路径,或你找到并且明确指出的项目文件。现在上传它,并从这一次调用中获取三个值,将
--json输出到一个生命周期长于此命令的文件中,以免预览数据块进入你的上下文:
将三个值都绑定到mkdir -p media $ADAPTY flows media upload --app "$APP" ./hero.png --json > media/hero.json MEDIA_URL=$(jq -r .url media/hero.json); MEDIA_ID=$(jq -r .id media/hero.json) MEDIA_PREVIEW=$(jq -r '.preview_base64 // empty' media/hero.json)image元素上,或直接放在fill中——这是两种不同的结构;即使命令打印的是数字,id也是字符串。实际上,previewValue不能省略:没有它,资源下载完成前,渲染器会绘制一个透明的 1×1 图像,导致最终屏幕上出现空洞。先前上传的资源应从其记录文件重新绑定,或从已有绑定的配置中提取——绝不重新上传,也绝不能仅绑定你记得的裸 URL。 如果有东西提供了 URL 却没有预览,那只说明没有提及该值,并不能证明这个字段可省略(media.md)。 - 你能看到图像,但没有路径(粘贴或附加的图像),或者对方提到了尚未发送的文件——索要路径,只问一次,并与其他问题合并询问。绝不要猜路径:猜错会明确报错,而猜对则会在能完美渲染的屏幕上显示错误的图片。对方提供的 URL 可以抓取,但要先说明你将下载什么。
- 没人有文件——使用带样式的空
image,让图形占据一个方框:在元素本身设置borderRadius、objectFit,以及根据参考图确定的固定尺寸,这样上传资源后就能保留样式,并能按资源将占据的尺寸检查布局。如果图形没有自己的方框——例如纹理、辉光——就将其省略,不要近似替代。无论哪种情况,都要将它列入缺失资源清单,绝不能放进段落里。绝不能编造 URL(陷阱 5)。如果参考图中的图形位于纯色背景上,也许可以将其裁出——references/crop.py;当背景有纹理或方框不匹配时,它会拒绝操作,而不会猜测。
视频片段不属于上述三种状态中的任何一种:上传操作不接受视频,因此应输出一个没有源文件的 video 元素——flowkit.video(fixed_h=…) 或目录中的 video-hero / video-card——并明确说明由对方在构建器中上传视频(media.md)。
在预览循环之前上传,而不是之后上传,并且每个资源只上传一次。 占位符不会占据真实资源的空间,因此使用占位符预览的屏幕,其布局从未经过检查——而且上传不会去重,所以每次迭代都重新上传,会永久弄乱用户的媒体库
(media.md)。
在实际文档中见过某种构造之前,先统计它的数量。 架构说明了允许什么;实际导出说明了生成什么;只有后者能预测设备上的表现。对你获取的配置和 references/component-catalog.json 各运行一次 jq,几秒钟就能确认;如果计数为零,就应该明确说出来
(flow-schema.md ——说明原因,以及跳过这一步导致上线的缺陷)。
将请求转换为架构术语。 用户使用的名词很少就是元素 type——没有 button 或 toggle 元素,而标签页是由五个元素组成的复合结构。请使用 flow-schema.md → Vocabulary 中的请求映射;对于配置中尚不存在的形状,请按顺序从 patterns.md → Where to source a pattern, in order 获取。
要编辑多个屏幕中的一个?用脚本就地打补丁——绝不要把该屏幕单独切出来。 一旦孤立出来的流程中屏幕跳转到已不存在的屏幕,就会无法通过发布检查;而孤立屏幕在任一检查阶段都不会更快——这一点已经过实测,而且还省去了重新拼接的麻烦
(transforms.md)。用 jq 或简短的 Python 补丁定位屏幕,而不要把整个文件读入上下文。
应用修改时,保留所有未有意更改的键——包括无法识别的键。嵌套的未知键在往返转换中会保留;config 顶层的未知键则会被丢弃,因此绝不要把任何内容放在那里。
将结果写入本地文件。第 3 和第 4 阶段都基于该文件进行,此时尚未保存任何内容——无论交付物是流程写入操作还是文件本身,这些阶段都适用。 “没有发生 CLI 写入”可以免除审批关卡,但绝不能免除这些阶段。
如果文件本身就是交付物,那么写入时就必须遵守其约定。 源导出包含顶层的 status 和 id;绝不要输出 "status": "published"——这样导入后会变成看似已上线的内容;而 id 指的是该导出来源的流程。请删除这些字段或将 status 降级,并说明你选择了哪种处理方式,同时说明导入时必须指定用户所指的流程。references/verify-config.py 会针对这两个字段发出警告;这个警告正是此规则触发的信号——务必采取相应措施,绝不要原样照搬。
3. 检查结构,然后通过发布门槛
先看 Verify——它在本地运行且免费,并能一次找出所有缺陷,下面的命令则做不到。然后一次调用通过全部三道门槛:
BASELINE=flow.backup.json references/gates.sh draft.json <APP_UUID> <FLOW_ID>
它会对同一组字节进行结构遍历、架构结构检查和发布门槛检查,输出一个判定;只有发现阻塞问题时才会以非零状态退出。一次调用,而不是三次——每道门槛耗时远不到一秒,而额外往返却要花上几十秒,因此耗时的是调用轮次。省略应用和流程 ID 时,它会明确说明,而不是假装本地检查就是发布门槛。如果需要单独运行其中一个底层命令,请参阅 validate.md。
只要获取过配置,就传入 BASELINE=——即第 2 步保存的原始副本。全新流程没有可获取的配置(config get 会返回错误),因此无需传入基准,也没有更早的状态可供报告。架构描述的是最新的
schemaVersion,而大多数线上流程版本更旧,因此对 v9 或 v10 流程运行未指定基准的检查时,会报告既有的不匹配项,这些都不是你造成的。详见
flow-schema.md → 两种不同的验证器。
validate 运行的是用于发布门槛检查的同一转换服务,因此它是这里唯一能代表发布门槛的写入前检查。它不会保存任何内容,无需确认,也不需要基准——v9 配置可以通过验证。它接收的是裸 config,而不是封套,并且流程必须已存在,因此处理新任务时,要在 flows create 之后运行。
看判定结果,不要只看退出码。 退出码 1 表示“不可发布”或“调用失败”,只有 --json 能区分两者:分别对应 valid 字段和 error 对象。只根据退出码进行门槛检查的 Agent,会把正确的配置报告为损坏,并把失败的调用报告为缺陷。
到这里完成的标准,是针对你即将写入的确切字节运行后,输出了
valid: true。 每次运行只报告一个致命问题,因此要修复后重新运行,再重复此过程——列表变短不代表取得了进展。
产品尚未稳定绑定时,它只会报告产品问题——绑定处于早期阶段,因此未绑定的卡片或未声明的产品会掩盖其后的所有缺陷。这正是上面的本地遍历虽然快速,却不够全面的原因:它无需任何往返,也能指出这些缺陷。 validate.md。
这两种检查都不能证明万无一失,而且检查范围并不重叠。 validate 能捕获架构看不到的悬空引用——未声明的产品、指向已删除对象的 groupId 或 navigate。它也会放过 fill: "banana"、schemaVersion: 999、缺少 states 的元素,以及设备上服务会静默丢弃的所有属性。架构检查回答的是相反的问题,而且不了解配置是否可发布。两方面的覆盖范围,以及如何解读各类消息,请参阅:validate.md。
4. 预览,并反复调整直到效果满意
一次调用渲染你修改的屏幕,并返回一张长图供查看:
references/shoot.sh draft.json scr_a scr_b scr_c # preview + screenshot + montage
它会在本地预览(无需 --app、无需身份验证、无需保存——也包括仅涉及文件的任务),并通过看门狗为每个屏幕截屏,将截图从左到右拼接起来,然后打印出唯一需要打开的路径。打开图片并仔细查看,对照用户的要求;如果用户提供了参考图片文件,也要重新打开文件进行对照,不要凭记忆判断。
只渲染你修改的内容。 Chrome 冷启动一次截图约需 18 秒,因此渲染次数就是此阶段的成本;为检查一个屏幕上的修改而重新拍摄七个屏幕,就浪费了六次启动。查看一张长图也只需看一次,而前后对比或默认状态与选中状态的对比,只有将两部分并排放置,才容易看出差异。
不要试图加快截图本身的速度——在快速主机上,缩短 --virtual-time-budget 没有任何效果,而且并行运行 Chrome 比串行更慢。但如果截图结果为空,就应延长预算:在渲染较慢的主机上,8 s 不会生成文件,而 60 s 可以正确渲染,因此没有文件通常意味着主机较慢,而不是配置有误——shoot.sh 会为你重试,预算为 60 s;此后,请先在真实浏览器中加载 URL,再怀疑你的工作是否有问题
(preview.md)。渲染失败绝不是报告工作已完成的理由。
使用 references/render-measure.py 进行测量,不要只凭目测。务必尝试预览:不要仅根据配置尺寸作出判断。
每张图片都要在此检查其属性——无论是否进行参考构建——因为没有其他关卡会检查图片。 从截图中读取绘制出的框,然后选择:height: hug 根据资源的宽高比确定高度,因此文件一旦更改,布局就会移动,而尺寸上的任何 value 都会失效;height: fixed 则保持框的尺寸,让资源适应不匹配之处——cover 会裁剪,fit 会留出黑边和空白带。objectFit 只能是 fit 或 cover,不设置 CSS。每次更改后都要重新渲染(media.md → Geometry)。
含价格变量的屏幕,应按真实价格长度进行评审和交付。 每个未绑定的价格都会以完整的 {{…prod_price}} 令牌呈现;令牌可能换行,并将文本挤到固定按钮下方。复制一份临时配置,将每个令牌替换为合理的价格,并据此修正布局;然后交付该渲染结果,并注明其中的价格只是示例。配置仍保留变量(preview.md)。
提供了参考图时,标准就从“符合需求”提升为“符合参考图”——每次收到参考图,都要先完成保真度检查,再着手编写任何内容。“没发现明显问题”不算结果。按元素整理一份书面的差异清单——颜色、字体、图标风格、图像、比例——并逐项标记为匹配、有差异或无法实现,然后弥补格式允许解决的所有差异,并将其余差异整理成明确的需求。清单才是关键,而不只是查看:有测量记录表明,只“看过”的 Agent 会交付用表情符号代替设计图标、凭记忆选颜色的结果,即使披露了这些问题也一样;而写下清单的 Agent 则修复了所有可解决的问题。
完成意味着所有剩余差异都已列入需求清单——用户拒绝预览,只代表放弃交付物,并不免除这项检查。要盘点的内容以及每种差异的处理方式,见: fidelity.md。
在报告截图能证明什么之前,请阅读 preview.md → What a render cannot show you——其中列出的每项盲区都有实测依据,而且有两项的情况恰好相反:渲染会画出设备上不会显示的内容。这里需要处理其中两项:渲染不会画出刘海和主屏幕指示条,因此要设置 safeArea: true,并将小屏设备上的裁切问题留待设备检查。
绝不要为了让截图看起来完整,就把正确的元素降级成预览中可见的替代品。 如果某个元素在预览中不可见——例如在此屏幕上不会绘制任何内容的 spinner、video、切换开关的 selected 状态或进度条的进度——正确做法是保留真实元素,说明预览无法展示它,并交由设备检查;而不是换成渲染能够绘制的内容。用静态 icon 代替 spinner(或用任何冒牌元素代替它所模仿的元素),会交付一个通过截图检查、但在设备上毫无作用的东西——这就是发生在新位置上的 fake-footer 错误,而且没有任何本地关卡会捕捉到它。渲染结果空白时,应使用设备(Adapty 应用)检查,而不是编造一个假元素。加载屏幕的形式以及 spinner 的两个不可臆测的事实,见 patterns.md。
而且,这条规则不仅针对无法预览的元素——也禁止因为觉得正确构建太难,就伪造一个看似可完整预览的元素。 carousel 会在预览中渲染,因此最容易为这个陷阱找借口:一张静态评价卡片加上三个装饰性的点状 stack,截图看起来就像评价轮播,实际上却只是一个——被冻结的单页,不能滑动,圆点也不起作用。carousel 是真正的元素,内置 dots,所以通常实现真东西比造假的工作量更少,而且你之前获取的种子流程里往往已经有一个可供复制。先通过 flow-schema.md 中的映射,解析用户的请求,再考虑使用外观相似的元素——评价、滑块、可滑动卡片和圆点都应映射到 carousel,绝不能手工构建圆点。component-catalog.json 提供了填充好的 reviews-carousel 模板,flowkit.carousel() 则可以从头构建;verify-config.py 遇到手工构建的指示器行会报错,遇到没有圆点的形式则会发出警告。同样的陷阱也适用于 progress-bar:静态填充的 stack 或一行步骤 stack 看起来像进度条,却永远不会前进——应构建真正的 components 条目,并通过 props.progressBar 按屏幕进行配置,绝不要用无法移动的进度条。
如果构建的屏幕会自动前进,第一次请求时就附上诊断信息。 页面不会导航,所以正常工作的自动前进和故障的自动前进看起来完全一样,只有用户的设备能分辨出来——而且每次尝试都得等一个完整周期。给 timer 添加一个子级 text,并使用 timer_minutes/timer_seconds 令牌:数字始终不出现,说明元素没有挂载;数字到零后什么也没发生,说明触发器没有触发。没有这些信息,测试失败只会返回一个比特的信息,你只能再次猜测。数字必须始终留在 timer 中:只有 timer 内的文本显示数字时,timer 才会触发——如果它唯一的子元素是 logo 或普通文案,就不会触发。用户确认后若要隐藏数字,可将其颜色设为与背景相同;绝不要删除数字。经设备验证的结构见 patterns.md。
确认你确实截取了流程的屏幕截图。 错误的 --device、损坏的片段和错误的主机都会渲染成页面,通过“是否有内容绘制”的检查。如果渲染为空白、缓慢或错误,请切换到 preview-with-playwright.mjs,它使用页面的文件输入,而不是 URL——不要缩减配置。如果完全无法渲染,请如实说明并请用户检查;绝不要仅凭验证通过就报告工作已完成。
缺少元素未必是你的问题,预览正常也不代表构建器已打开流程。 这两点,以及四个界面分别能验证的内容,见:preview.md。
渲染结果符合请求,屏幕仍可能很弱;每个渲染结果都符合请求,整个流程仍可能很弱。 上述每项检查都在问你是否构建了所要求的内容;没有一项会问它是否有说服力。如果设计是你选的——没有参考资料,也没有源屏幕——对已构建的内容进行拆解分析是工作的一部分,而不是额外的客套。 路由方式与阶段 2 相同:付费墙使用 paywall-teardown,序列使用 onboarding-teardown,兼具两者的流程则两者都用。将渲染结果和配置一起交给它(一个显示可见内容,另一个显示实际配置),然后应用它标记为“优先修复”或“高优先级”的项,并重新渲染。你设计的内容中发现的问题是缺陷,不是建议——如果跳过了一些模式,还把这些内容交上去,那工作就尚未完成——而现在修复成本最低,只需改一张截图,而不是执行一次 config update。只有纯粹的编辑才可跳过:例如修复拼写错误、添加语言区域。
序列会根据整个序列评分,所以要提供其中的每个屏幕。 渲染每个屏幕并传入整组屏幕
— references/montage.py 会将它们拼接成一条长图,这样才能看出“屏幕 4 承诺的内容在
屏幕 5 中没有兑现”。六个屏幕只展示一个,无法看出衔接问题。
长图无法展示屏幕之间的连接关系时,就展示连接方式。 长图按 screens[] 顺序排列,所以
会隐藏所有分支、跳转和循环。绘制地图并在用户的浏览器中打开:
python3 references/canvas.py draft.json --open # canvas.html beside the config, renders as thumbnails
| 何时 | 操作 |
|---|---|
| 你构建或重新连线了一个包含 10 个以上屏幕的流程,或添加或更改了分支 | 打开地图,用一句话说明它展示的内容以及需要检查的地方 |
| 用户询问屏幕如何连接、按钮会跳转到哪里,或分支的作用 | 打开地图,用一句话回答 |
| 此机器上没有浏览器(远程或无头会话) | 在回复的代码块中粘贴 canvas.py draft.json --text |
它只读取配置,因此可以在任何写入之前运行,也可以用于你没有更改过的已获取流程。
先用 shoot.sh 渲染屏幕,它们就会显示为缩略图;没有渲染图时,它们会显示为列出按钮的卡片。交给用户之前先查看它的标记 — 没有入口 和 没有出口 是需要修复或说明的连线缺陷,不是装饰。
在这里迭代。 发现任何问题,都返回去修复,然后重新运行阶段 3 和 4。尚未保存任何内容,因此每次迭代只需多拍一张截图,而不是执行写入。
5. 获取批准,然后交付 — 写入或文件
写入之前是否需要征得同意,取决于一个可观察的事实:目标流程是否已有配置?
没有配置 — 这是刚用 flows create 创建、且 config get 返回 404 的流程。直接写入。
没有内容会丢失,也不会覆盖任何内容;停下来询问只会给一份空文档增加阻碍。写入后报告你写入了什么。
已有配置 — 即阶段 2 中获取的任何流程。停止并在写入前取得明确同意。
config update 会替换整个配置:无法部分写入,也没有撤销功能或版本历史,因此你要替换的文档仅在另一个地方存在 — 阶段 2 的备份。请在一条消息中向用户说明以下所有内容,然后等待:
首先,计算这次写入会删除什么 — 绝不要凭记忆描述。 在最后一次编辑后,对即将写入的字节运行以下命令:
python3 references/diff-config.py flow.backup.json draft.json # REMOVES = what you destroy
备份是基准,而不是你的工作文件:它是本次运行中未被触碰的唯一副本,因此也是回答“我来之前这里有什么”时唯一可信的依据。(你获取配置后才发生的编辑,属于锁的职责,不属于这里。)
以下询问中必须包含每一条 REMOVES 行,并指出对应的是哪项请求;无法追溯到请求的一项,就是他人的工作:指出它并询问,绝不要覆盖它。退出码 1 表示列表非空,并不表示有问题 — 删除屏幕是受支持的转换,静默删除则不是(见 merge.md)。
然后向用户展示更改 — 批准一段描述,不等于批准屏幕。 将内容分成两部分,因为读者对这两部分需要关注的内容不同。
发生变化的屏幕 — 每个屏幕一行,让人一眼就能看出“共有四个”。
从草稿渲染每个受影响的屏幕,并从备份渲染其 before 版本(preview 会原样读取备份封装,无需使用 jq)。新屏幕没有之前的版本;请明确说明,而不是省略该行。
屏幕 更改内容 查看 付费墙 (新建) 结果行、两个方案、试用徽章 付费墙,修改后 — 在浏览器中打开 每日目标 视觉效果无变化 每日目标,修改后
屏幕名称要与构建器显示的名称一致,并以屏幕名称作为文本链接到每张渲染图 — 文件名由你决定,不是由他们决定。
没有可见效果的更改 — 单独列出并说明原因。 否则,刚看完四张截图的读者会认为图片展示了全部更改:
- 每日目标:现在,继续按钮会打开付费墙,而不是关闭流程。
- 两张新的套餐卡片已注册对应产品,因此可以发布该流程。
- 已将 38 段文本翻译成德语。4 张图片在所有语言中都保持不变。预览只会显示英文,因此请在设备上检查德语效果。
请在其中恰好打开一个,并标明对应的行。 你就在他们的机器上,所以请直接打开,而不是交给他们一条命令让他们粘贴:
URL="$($ADAPTY flows config preview draft.json --screen <id>)"
open "$URL" # macOS; xdg-open on Linux, start on Windows
需要明确使用打开操作,是因为你的 shell 会捕获 stdout,所以 CLI 会打印 URL,而不会像在终端中那样启动浏览器。preview 完全在本地运行——无需身份验证、应用 ID 或写入操作。只有在没有显示器时(远程或无头环境)才改为打印命令,并说明原因。
无论改动有多大,都只打开一个标签页:页面只渲染单个屏幕,不会走完整个流程,因此 N 个标签页只会带来 N 倍的噪声,却仍然无法展示完整流程。打开状态最重要的屏幕——例如选择器、开关或已选套餐——因为静态 PNG 唯一无法展示的就是状态。截图相同不代表配置相同 (preview.md);第二个列表就是为此准备的。
即将覆盖 ,当前状态为 <status, as the dashboard shows it: draft, published, has unpublished changes>。
<上述两个列表>
移除内容:<用通俗措辞列出每一条
REMOVES行,并说明是哪项请求要求移除——或者写“无”>。 我已保存当前版本的副本,需要时可以恢复。保存吗?
不要把这段话改写成“要保存吗?”——流程名称、状态、移除的内容以及恢复方式都是必要信息,缺少这些信息时作出的批准并非知情同意。请等待明确答复;用户喜欢某张截图不等于同意。
如果流程状态为 published,请说明保存会带来什么变化,然后在用户同意后就地编辑。 保存 published 流程会使其变为 dirty:整个团队都能看到此状态,而且无法通过 CLI 撤销;与此同时,终端用户仍会看到已发布版本,直到下次发布。请将这句话写入征求批准的内容,然后继续操作——编辑线上流程是常见情况,而预览对照、备份和锁的存在正是为了确保操作安全。只有当工作属于探索性质——用户想在原版旁边另做一个重新设计的版本——才建议新建 flows create;绝不要把它作为编辑时的默认答复。
从备份恢复——已端到端验证。 请在上次写入留在 flow.working.json 中的版本上加锁,以免恢复操作覆盖他人在你完成写入后所做的编辑:
jq '.config' flow.backup.json > restore.json
$ADAPTY flows config update $FLOW --app $APP --config-file restore.json \
--expected-updated-at "$(jq -r .updated_at flow.working.json)" --json > write.json \
&& mv write.json flow.working.json
已在真实流程上验证字节完全一致——因此恢复确实有效,但前提是第 2 阶段实际创建了备份,这就是该步骤不可省略的原因。
仅在此之后:
# the lock is the version draft.json was patched from; the mv stores the next one on success
$ADAPTY flows config update $FLOW --app $APP --config-file draft.json \
--expected-updated-at "$(jq -r .updated_at flow.working.json)" --json > write.json \
&& mv write.json flow.working.json
验证和预览都针对本地文件运行,因此写入放在最后:确认无误后写入一次,而不是每次迭代都写入一次。如果你已经写入后才发现问题,也没关系:mv 已经将本次写入的 updated_at 放入 flow.working.json,所以后续写入会直接对其加锁,无需额外读取。
该 --json 重定向本身就是回读——无需额外调用。 写入操作返回的 envelope 与 get 相同,因此你的工作文件已经与最新状态同步,可用于下一轮操作,也可以与发送的内容进行比较。忠实往返是常态,因此任何差异都是真正需要关注的发现——例如消失的顶层键,或本不应输出的 status。
一次不产生任何变更的写入不会更新 updated_at(只测量一次),因此重新提交相同内容并不会生成新的锁令牌。如果遇到 409("已由 更新。请重新加载后再保存…", 退出码 1),说明你获取流程后有人编辑了它,而且没有写入任何内容:重新 get 并写入 flow.working.json,在对方的配置上重新应用你的修改,告诉用户期间有人保存了更改,然后再次写入。绝不要强行覆盖,也绝不要重新发送你的本地副本——那会抹掉对方的工作。如果对方的版本包含你没有做出的改动,请先询问,而不是恢复旧版本。
如果你是通过脚本重建配置,而不是修改获取到的配置,那么你拿错了文档——重建会替换线上流程,同时覆盖 _meta.screens、它所依据的 screens[].products 注册表(products.md)以及所有手动编辑。请修改获取到的配置;merge.md 列出了仅有的两种适合重建的情况。
文件交付物
如果用户要的是文件而不是写入,就不需要审批关卡——约定在第 2 阶段生效,也就是写入文件时;结束报告中要再次说明你选择的 status/id 形状。交付文件时如果没有这句话,就不算完成交付。
提醒他们导入前先备份。 形状无效的配置在构建器中可能显示为空白,下一次保存就会用这个空白覆盖真实流程——没有可处理的错误,也不需要碰运气才能发生。只有未经改动的配置副本能在这种情况下保留下来(merge.md)。
绝不要以工作仍留在本地文件中作为结尾。 config update 是此界面唯一的保存方式,默认情况下,保存就是你该停下来的地方。发布是单独且必须明确确认的步骤,只有用户提出要求时才进行——绝不要把发布并入写入操作。
然后每次都以此提示结束。 保存不等于发布;要完成发布,必须由用户明确要求。填好占位内容,并保留全部三个步骤以及结尾那一行——那一行很重要。**如果用户要求你发布且你已完成,**标题改为“已发布——已上线至 <placement>”(或“已发布——尚未关联到任何 placement”),删除步骤 3 和结尾那一行,其余内容保持不变。
绝不要发布条款或隐私政策仍是纯文本的流程。 这适合作为草稿的默认处理方式(transforms.md 决策 10),但对于已发布的付费墙,flow-audit 会将其视为阻塞问题,两个应用商店也都会拒绝。发布前,如果用户要求你发布,请先获取这两个 URL;如果没有,就保存但不要发布,并说明原因。
已保存为草稿——用户目前还看不到。
在构建器中检查;如果页面已经打开,请刷新:
https://app.adapty.io/flows/<FLOW_ID>/builder
在真实设备上预览,并检查本次构建无法验证的具体事项。 <如果用户要求提供二维码图片,则填写二维码图片行;否则使用以下内容:> 想用 Adapty 移动应用扫描二维码吗?告诉我一声,我来生成。
发布:告诉我一声,我就会发布;或者点击编辑器右上角的 Publish。之后,我可以将它关联到 placement,这样你的应用才会显示它。
<仅当某个产品卡尚未选择产品时:>选择<按标签列出卡片>的产品前,无法发布——告诉我该选哪些,或者在构建器中自行选择。
<仅当第 2 阶段的缺失资源列表中仍有未完成项目时,添加一行:>仍有<n>个资源是占位内容——请查看上方列表。
<仅当流程包含自定义标签时:>其中有<n>个值只有你的应用才知道——<name>、<name>——在你的应用传入这些值之前,所有人都会看到后备文本。要我现在更新你的应用代码来传入这些值吗?
<按流程状态填写结尾:>dirty— 发布前,所有人仍会看到先前的版本。draft— 目前尚未上线:此流程发布并关联到 placement 之前,任何人都看不到。 在插槽 2 中提供二维码;不要粘贴预览链接。 只有用户说要二维码时才生成。mobile-preview.mjs会根据应用 ID、流程 ID 和配置中的语言区域生成二维码,不会发起网络请求,也不需要身份验证:
(mkdir -p ~/.cache/adapty-flow-qr && cd ~/.cache/adapty-flow-qr && node <abs-path>/references/mobile-preview.mjs \
--app <APP_UUID> --flow <FLOW_ID> --config <abs-path>/flow.working.json --qr --md-base <your working directory>)
写入后再运行,绝不要提前运行。 应用会获取已保存的草稿,因此,基于未保存文件生成的二维码会预览上一版本,让人觉得“我的修改没有生效”。之后再写入也不会让二维码失效,所以只生成一次,不要每次修改都生成。它会在工作树中写入一个临时 PNG 并将其打开;将它打印出的  行粘贴到回答中。绝不要提交这张图片。
| 用户的说法 | 你要做的事 |
|---|---|
| 同意生成二维码,或要求用设备扫码或测试 | 使用 --qr,并在本次会话中持续这样做 |
| 要求提供链接 | 原样给出脚本打印的链接,单独占一行,绝不要放在反引号中 |
| 拒绝 | 删除这一行,且不要再主动提供 |
| 还没说 | 在插槽 2 中放置那句单行邀请 |
有关二维码和链接为何如此处理、--md-base 规则,以及为何没有字符画二维码,请参阅:preview.md。
在该插槽中填写实际列表,绝不要写“检查它是否可用”。 你知道渲染器无法触及你所做的哪些选择——点击时触发的分支、切换开关、非默认语言区域、会前进的进度条、会自动切换的屏幕、iOS 上不同的字形度量。泛泛的指示会被跳过;列出三个具体项目,用户就会逐一点击。本技能交付给用户的每个缺陷,都源于存在这个插槽要弥补的遗漏(preview.md)。
自定义标签必须通过应用验证才算完成,因此要提供代码;用户同意后,就写入代码。 提供代码的方式就是上面的提示行。请根据你写入的字节列出标签,绝不要凭记忆列出——jq -r '.variables[]? | select(.external == true)'。用户同意后:
- 找到应用的代码。 通常就是你当前所在的工作区;如果无法访问任何应用项目,请询问项目路径,不要猜测。
- 找到创建流程视图的调用位置。 搜索创建流程视图的调用;如果有多个,就匹配获取此流程 placement 的那个;无法判断时请询问。若完全没有此类调用,或使用的 SDK 低于 4.x(该版本没有流程 API),则表示应用尚未渲染流程:这是 SDK 集成工作,因此应提供
adapty-integration,不要在此处实现。 - 找到应用已有的各项值存放在哪里——用户模型、钱包、商店。找不到时请询问;绝不要传入示例值、占位符或回退值本身。
- 在 flow-schema.md → trap 25 中查找该平台的准确参数——查阅对应文档页面;如果页面没有说明,则查阅应用发布版本对应标签中的 SDK 源码。然后在该调用处传入这些值:键必须与标签名称完全一致,值必须是经过格式化、用于显示的字符串,并在创建视图前立即读取。应用传入的短语必须在流程所包含的每种语言区域中都有对应版本;若某种语言区域缺少对应内容,则不要传入任何内容,使用回退值;绝不要改用其他语言的文本。
- 如果项目允许,就进行构建,并为每个文件用一行展示差异。
将修改限制在该调用位置及其必需内容——旁边的辅助函数、它读取的字符串。应用的模型和其他所有屏幕都不属于本技能的修改范围。
6. 将其关联到一个 placement,然后交出 ID
已发布的流程只有关联到 placement 才能触达用户。 placement 的开发者 ID是应用用来获取流程的字符串,因此发布只是链路的中间环节,而不是终点:配置 → 发布 → placement → 应用的调用位置。每当流程达到 published 状态时,都应提供这一步。切勿未经用户要求就执行——placement 无法撤销。
三种进入方式。 你发布了流程并轮询到 published;用户说他们已发布流程(很可能是在构建器中);或者对话从这里开始——此时阶段 1 已将你带到这里,阶段 2-5 无事可做。
无论由谁发布,都要自行验证状态。 flows get <FLOW_ID> --app <APP_UUID> 必须显示 published;publishing 表示需要轮询,其他任何状态都意味着它尚不可关联。
然后先读取 placements,再提出方案——一次调用即可回答两个问题:此位置是否已有 placement,以及你打算提出的 ID 是否可用:
$ADAPTY placements list --app "$APP" --page-size 100 --json # the default page size is 20
list 显示的内容 | 你要做的事 |
|---|---|
| 此位置没有任何内容 | 使用新的开发者 ID 执行 placements create |
| 一个现在应该指向此流程的 flow placement | 执行 placements update — 它会重写所有受众,因此请原样传回要保留的受众、A/B 测试条目(flow_ab_test)和共享条目(flow_cross_ab_test),与 placements get 返回的内容一致。例如,将运行中的测试替换掉的写入(如默认受众上由测试持有的此流程)会在没有胜出者的情况下停止该测试:用表格展示修改前后的受众,并附上“测试 X 将在没有胜出者的情况下停止;此流程将启动”这句话,等待用户确认;写入前再次读取 placement;如果它发生了变化,展示新的修改前后表格并再次询问(无人值守时:停止并报告)。用户未回复 → 此更改会在控制面板中进行 |
| 一个 paywall placement,即使它未被使用 | 什么也不做。 类型在创建时固定,写入会被拒绝。提出不同的 ID 并说明原因 |
| 一个仅应向部分用户提供此流程的 placement — 某个国家、版本或细分群体 | 使用 audiences 技能。它会在现有受众旁添加细分受众,不会改变其他用户看到的内容 |
ID 必须获得明确同意,这是第四个固定确认环节。 这种操作不可逆,而阶段 5 的写入并非如此:ID 在应用的所有 placement 类型中全局唯一,无法重命名,也无法通过 CLI 删除。
准备将 placement 指向
<flow name>(<flow-id>,published)。
- 开发者 ID:
<id>— 应用将使用此字符串进行获取。- Placement:
<new, titled "…">/<existing "…", now pointing at <what>>- 永久生效。 ID 在应用的所有 placement 类型中全局唯一,无法重命名,也无法通过 CLI 删除 — 选错就浪费了。
要创建吗?
用户同意后,执行一次调用:
$ADAPTY placements create --app "$APP" --title "<Title>" --developer-id "<id>" \
--audiences '[{"content_type":"flow","flow_id":"'"$FLOW"'","segment_ids":[],"priority":0}]'
--audiences 的流程形式是常规路径 — 在生产环境中也可接受。所有拒绝情形、哪些由你负责修复、update 变体以及控制面板回退方案:
placements.md。
然后交出 ID 并停止。 除了阶段 5 中自定义标签的调用位置,此技能不会修改应用代码:
已在
<developer-id>上线。 https://app.adapty.io/placements/flows/`<PLACEMENT_UUID>`你的应用尚未发生任何变化 — 它仍需获取此 placement 并渲染返回的内容。
adapty-integration技能负责后续工作:将此开发者 ID 提供给它,它便会在你的调用位置接入获取和渲染逻辑。
此链接使用的是 placement 的 UUID,而不是开发者 ID — 两者是不同字段,只有 UUID 用于路由。placements create 会将其输出为 id。中间部分是 placement 的类型,因此 flow placement 的路径为 /placements/flows/…,paywall placement 的路径则为 /placements/paywalls/…(placements.md)。
安全性
除首次写入外,每次写入都要传入 --expected-updated-at。 值过期时会失败,而不会覆盖内容,因此这是真正的保障,而不是警告。请从 flow.working.json 中获取该值,即你的草稿所基于的版本——切勿在写入前刚运行 config get 后从中获取,因为那会给你另一个人的令牌;也切勿从 flows get 获取,因为其中的 updated_at 是另一个字段。省略此参数就意味着以后写入者获胜,并会悄无声息地覆盖别人在此期间所做的编辑。
锁并非合并操作。 它只保护写入的时机,不涉及写入的内容:如果文档从未基于当前配置——而是重新生成,或从过期的本地文件打补丁——它仍能通过锁检查并覆盖流程。因此,请基于你获取的内容打补丁,并在写入前进行差异比较(merge.md)。
绝不要写入用户未指定的流程。 flows list 用于查找正确的流程并向用户确认,而不是让你自行挑选。
config update 会替换整个配置。 它不支持部分写入,也没有撤销功能或版本历史记录。新建内容时,优先使用新的 flows create。
覆盖现有配置需要明确同意。 第 5 阶段负责填写表单。是否需要确认,取决于流程是否已有配置,而不是你对编辑有多大把握——验证通过、截图效果好、用户喜欢设计,这些都发生在提问之前,且都不代表用户已批准。
删除流程需要在仪表板中操作。 没有 flows delete,因此绝不要声称已删除某个流程——包括你自己创建的临时流程。请说出流程名称,并告诉用户在哪里删除。
validate 和 preview 不会更改任何内容,也不产生费用。请放心使用。
验证
在第 3 阶段、执行两个命令之前完成了遍历,因为这样可以在本地一次找到所有行;而
validate 每次往返只会报告一行。
references/verify-config.py 已随项目提供,第 3 阶段会运行它,并且它能自动检查除两项之外的所有引用关系。 因此不要凭肉眼重新推导:运行脚本,并将其输出作为检查清单。
每条不变量的完整说明——不变量是什么、什么情况会破坏它、违反后会发生什么——见
flow-schema.md → 不变量,由该文档负责说明。
工具无法回答、必须由你判断的事项:
| 检查项 | 工具无法捕获的原因 |
|---|---|
fill 保持输入所用的表单形式——对象或数组,绝不转换,且只嵌套一层 | 只有相对于你获取的输入,表单才可能有误 |
图片 URL 是本次会话中由 flows media upload 打印的 URL | 看似合理的 public-media.adapty.io 路径无法与真实路径区分 |
_meta.icons[].raw 是真实 SVG——通过 icons.py 解析,而非由你编写 | 可以检查是否存在,但无法验证真伪 |
| 每个价格变量的字段都与其产品的周期一致 | 需要产品目录,而不是配置 |
最后一项是必须停止的条件,而非需要披露的事项:如果产品目录中没有设计所需周期的产品,请在写入前停止。
发布阻塞项(常见问题):没有任何元素的屏幕、没有关联产品的产品元素、不完整的交互。validate 每次运行只报告一项。绝不要把发布阻塞项当作你留下缺陷的保障——用户会清除阻塞项,而清除某项通常会激活它所掩盖的问题
(products.md)。
警告——报告,绝不“修复”: 未被引用的组件;variables[] 或 theme 中已声明但未被引用的条目;所有分支都解析为 nothing 的无效 conditional。真实配置中这些情况都存在。
有一项警告要根据编写者区分处理:localization.locales 中某个区域设置 ID 下的目录值,不等于声明了该区域设置。 如果这是你本次运行写入的内容,那就是你的缺陷——请声明该区域设置,并翻译添加区域设置所涉及的内容。如果它是随你获取的配置一同提供的,就像其他警告一样,只报告、绝不修复:
这通常是某人开始但未完成的区域设置工作;继续完成或删除他人的工作,不是由你决定的事。说明这两种处理方式,并询问对方。