kuikly-devtools
v1.0.7
Published
Chrome-DevTools-style inspector for Kuikly (KMP) pages: live view tree, props, component state, logs, network and native module calls.
Maintainers
Readme
Kuikly DevTools
English: README-EN.md | 原理详解:ARCHITECTURE.md | 协议细节:PROTOCOL.md
给 Kuikly(Kotlin Multiplatform)页面用的类 Chrome DevTools 调试面板。页面跑在真机上,浏览器里实时看到节点树、属性与布局、组件成员变量、日志、网络请求和原生模块调用。
插桩是可选开关。不带开关时构建产物与今天完全一致:业务源码一行不改,不新增任何依赖,插桩代码不可能进入发布产物。
一行命令,让 Kuikly 页面拥有调试能力
在 Kuikly 业务工程根目录(能找到 gradlew)执行下面其中一条命令即可。
# iOS:编译支持调试的 JS 产物,并启动 DevTools server 和调试面板
npx kuikly-devtools dev
# Android:编译支持调试的热重载 APK,并启动 DevTools server 和调试面板
npx kuikly-devtools build-apk两条命令都会在构建成功后打印调试面板地址(默认是 http://localhost:8090)。在浏览器打开这个地址,再在设备上打开或刷新页面,即可查看节点树、组件状态、日志、网络请求和原生调用。
效果预览
Elements:节点树 + Live 截图,点选截图可命中树上对应节点。

Console:按级别 / tag / 关键字过滤,自动滚动。

一、常用命令
npx kuikly-devtools serve # 只起服务,不构建
npx kuikly-devtools doctor # 体检:路径、端口、网卡、adb 状态
npx kuikly-devtools build-js # 启动或复用服务,再插桩打 JS Bundle
npx kuikly-devtools build-apk # 启动或复用服务,再插桩打热重载 APK
npx kuikly-devtools gradle -- :sampled:packLocalJSBundleDebug # 启动或复用服务,再执行任意 Gradle 任务
npx kuikly-devtools inspect sessions # 为 AI 或命令行按需检索已连接页面最后一条命令适合自己指定 Gradle 任务:
npx kuikly-devtools gradle调用 CLI 的gradle子命令。CLI 会找到当前目录(或--project指定目录)下的gradlew,并注入 DevTools 的 init script 和连接参数,因此这次构建会启用源码插桩。--是参数分隔符;它后面的内容原样传给 Gradle。除了任务名,也可以放--info、-x test、-Pkey=value等 Gradle 参数。:sampled是 Gradle 项目路径,表示sampled模块;packLocalJSBundleDebug是该模块的 JS Bundle 调试构建任务。需要只编译 Kotlin/JS,或执行其他模块任务时,可以把它替换成对应的 Gradle task;命令会和其他构建命令一样启动 DevTools 服务,已运行时直接复用。
这条命令适合快速验证指定模块能否通过插桩构建。也可以直接使用 npx kuikly-devtools build-js 生成默认 JS Bundle,或使用 npx kuikly-devtools build-apk 生成 Android 调试 APK。运行前请确认工程有 gradlew,且改写器已构建(源码开发时先执行 npm run build:instrumentor)。
AI 页面检索 Skill
npm 包内置 kuikly-page-inspect Skill,供 Codex、Claude Code 与 Cursor 在排查 UI、日志、网络和原生调用问题时按需读取正在运行的 Kuikly 页面信息。它不读取全量页面快照,先通过分页检索定位所需记录,避免大页面、长日志和接口 body 膨胀 AI 上下文。在业务工程根目录执行一次即可为三个客户端创建项目级入口:
npx kuikly-devtools init-skill- Codex:
.codex/skills/kuikly-page-inspect/SKILL.md - Claude Code:
.claude/skills/kuikly-page-inspect/SKILL.md - Cursor:
.cursor/skills/kuikly-page-inspect/SKILL.md
默认不会覆盖已有的项目自定义 Skill;需要用包内版本重新生成时执行 npx kuikly-devtools init-skill --force。包内权威源文件为 skills/kuikly-page-inspect/SKILL.md。
先确保已用 dev、build-js、build-apk 或 gradle -- <task> 启动/复用服务,并在设备上进入目标页面。AI 或终端从会话列表开始,再执行针对性的查询:
# 1. 获取已连接页面,选择 pagerId
npx kuikly-devtools inspect sessions
# 2. 只检索匹配的日志、网络、原生调用或节点;默认每页最多 50 条
npx kuikly-devtools inspect logs --pager 7 --query timeout
npx kuikly-devtools inspect network --pager 7 --query /api/search --status 500
npx kuikly-devtools inspect native --pager 7 --query CalendarModule
npx kuikly-devtools inspect nodes --pager 7 --query SearchBar
# 3. 找到 ID 后再取单条详情
npx kuikly-devtools inspect network-detail --pager 7 --id cb_42
npx kuikly-devtools inspect native-detail --pager 7 --id nc_1
npx kuikly-devtools inspect log-detail --pager 7 --id 42
npx kuikly-devtools inspect node-detail --pager 7 --id 42logs 支持 --level、--tag;network 支持 --status、--kind;native 支持 --kind(sync / async / stream);所有列表支持 --limit(默认 50,最大 200)和 --offset 分页。检索结果只返回摘要、属性键与请求/响应预览。返回 JSON 不超过 15 KiB 时直接返回原数据;只有单条详情严格超过 15 KiB,CLI 才会把完整 JSON 写到业务项目根目录的 .kuiklyDevtoolTemp/,终端仅返回 savedTo 路径;AI 应只读取该文件需要的字段或片段,不能直接将完整文件注入上下文。该目录已被 Git 忽略,调试后可手动删除,或执行:
npx kuikly-devtools inspect clean-temp让 AI 修改页面属性与状态
kuikly-page-inspect 现在支持定位节点、读取可写类型并修改页面。已有 skill 可在业务工程根目录执行 npx kuikly-devtools init-skill --force 更新(会覆盖该 skill 的本地自定义内容)。
# 获取节点详情及 e 中的可写字段;组件状态需先拉取
npx kuikly-devtools inspect node-detail --pager 7 --id 42
npx kuikly-devtools inspect state --pager 7 --id 42
# p = 属性,s = view 状态,as = attr 状态;--value 使用 JSON
npx kuikly-devtools inspect edit --pager 7 --id 42 --target p --key width --value '120'
npx kuikly-devtools inspect edit --pager 7 --id 42 --target p --key backgroundColor --value '"#80FF0000"'编辑命令会等待设备回执并返回实际读取的值,失败退出非零;超时不代表未执行,重试前应先读取节点。修改只作用于当前页面内存。编辑输入框也会显示字段的合法值或格式提示,包括枚举、布尔、颜色、间距和数值范围。
参数
| 参数 | 默认值 | 说明 |
| --- | --- | --- |
| --host <ip> | 自动探测 | 编译期写进产物的局域网地址 |
| --port <n> | 8089 | ingest 端口(设备 → 本机) |
| --panel-port <n> | 8090 | 面板端口(浏览器访问) |
| --sample <ms> | 500 | 初始采样间隔 |
| --project <dir> | 最近的 gradlew | Gradle 工程根目录 |
| --modules <paths> | 自动 | 指定要插桩的 Gradle 模块路径,逗号分隔 |
| --task <name> | 按命令 | 覆盖 build-js / build-apk 用的任务 |
| --debug | 关 | 打印 ingest 地址、局域网地址和 adb reverse 连接详情 |
| --copy-only | 关 | 只做源码重定向、不改写(用来隔离接线问题) |
| --no-adb | 关 | 不尝试建立 reverse 隧道 |
要让某个文件不被插桩,在文件里任意位置写一行注释 kuikly-devtools:ignore 即可。
二、五个面板
Elements
节点树来自 ViewContainer.templateChildren(),也就是 DSL 结构,不是原生 view 树,因此虚拟容器和 ComposeView 边界也能看到。Inspector 可查看节点信息、布局(含 FlexNode 的 margin / padding)、全部 props、按需读取的成员变量和 Live 截图。
树上的视觉约定:紫色标签表示 ComposeView,蓝色标签表示普通节点,◌ 表示没有对应原生 view 的虚拟节点,S 表示有可 dump 的成员变量。节点后面的 x,y · w×h 是相对页面根节点的布局坐标(convertFrame)。成员变量按需拉取,只有点开节点时才会通知设备 dump,避免每帧序列化整棵树的状态。Live 截图在树变化时才调用 toImage,最多每 2s 一帧;点击截图会按可视坐标命中节点(布局框减去祖先 Scroller 的 contentOffset,再叠 transform,并按 overflow 裁剪)。重叠时按绘制顺序:zIndex、后声明的兄弟、子节点;铺满全屏但不绘制自身的 overlay 会把空白处透传下去,避免挡住下面的页卡。
Components
只列 Pager 和 ComposeView 节点,适合按组件定位页面内容。
Console
按级别(info / debug / error / println)、tag、关键字过滤并自动滚动。tag 会从 KLog 的 [KLog][tag]:message 格式中反解;设备侧缓冲溢出时会显示 N dropped,不会静默丢弃。
Network
展示 HTTP(KRNetworkModule、TDF network.fetch、TMNetworkModule.fetchMapServer)及长连接请求(TMLongLinkModule、QMLink、MQTT)。选中请求可查看请求头、请求体、Response、状态码、耗时和 callbackId;普通 HTTP(KRNetworkModule / network.fetch)可一键复制为 curl。请求发出时先显示 pend,回包后补齐状态和耗时;长连接推送会单独列出 Frames。
原生调用
展示 Kotlin → Native 的 Module 调用(使用 Module 调用 Native 方法):Module.toNative / toTDFNative,以及业务里常见的 KuiklyTDFModule.asyncCall/syncCall、TMKuiklyBridgeModule、BridgeModule 等。TDF 包装会被解开,所以 KuiklyTDFModule.asyncCall("CalendarModule", "getReminderList", …) 显示为 CalendarModule.getReminderList,入参和异步返回值(FIRE_CALLBACK)都能看到。
同步调用的立即返回值通过拦截 NativeBridge.toNative 统一采集(含官方 CalendarModule.get 等 klib 内部调用)。请重新插桩编译后再看返回值。异步回调始终能显示。日志、HTTP、长连接、MQTT 以及每帧 vsync 不在此面板出现(分别在控制台、网络里,含 cookie / cancel 等辅助调用)。鸿蒙上走 Kotlin Delegate、不经过 bridge 的调用也无法采集。
日志、网络请求和原生调用以 serve 端为权威存档。设备上报成功后本地缓冲会清掉,但服务端会保留;只有 Kuikly 页面销毁(DESTROY_INSTANCE)时,该页数据才会删除。
三、工作原理概览
设备端 agent 通过 Kuikly 的公开视图 API 和 bridge observer 采集节点树、属性、布局、日志、网络请求和原生模块调用;CLI 只在需要调试的那次 Gradle 构建中注入 init script,把 agent 挂到 @Page,为源码类生成私有成员变量 dumper,并把 println 接到控制台。同步 Native 返回值在运行时拦截 NativeBridge.toNative。设备把变化上传到 ingest 服务,浏览器面板通过 WebSocket 查看服务端维护的会话数据。
原理、数据流、插桩规则、Gradle 接入和缓存隔离的详细说明见 ARCHITECTURE.md。
四、设备可达性
| 平台 | 设备怎么连到本机 |
| --- | --- |
| Android | adb reverse tcp:8089 tcp:8089,CLI 自动建立,设备上报到 127.0.0.1 |
| iOS | 编译期写进产物的局域网地址 |
| 鸿蒙 | 同上 |
运行时先试 127.0.0.1,失败再退到编译期的局域网地址,成功后锁定并缓存。明文 HTTP 访问内网地址需要 debug 包允许 cleartext traffic。
五、开销与边界
设备每个采样周期走一遍树,只发序列化结果变化过的节点和消失的 id;日志、网络和原生调用在设备侧缓冲,约 1.5s 批量上报。完全空闲的页面仍会每 5s 发送一个空心跳,满足 8s 内至少一次;服务端连续 10s 未收到数据就移除该页面会话,兜底处理突然退出。成员变量只 dump 面板当前展开的节点,Live 截图只在树变化后采集,最多 2s 一帧。采样间隔可以在面板顶栏改(100ms ~ 5s),或者构建时用 --sample 指定。
只有被插桩模块里的源码可以读取私有成员;已发布 klib 中的组件只能看到节点和 props。BridgeManager.addCallObserver 每个 pagerId 只保存一个 observer,挂载 agent 会替换同一页面上的其它 observer。展开节点会读取其 by lazy 属性,可能触发初始化。树是采样的,不是逐帧录制;截图依赖 Kuikly 2.17+ 的 DeclarativeBaseView.toImage,虚拟节点不能单独截图。插桩后的源码是副本,行号与原文件一致,但修改副本没有意义。
六、排查手册
面板提示 panel bundle is missing:在 devtools 仓库执行 npm run build:ui。
Gradle 提示 instrumentor jar missing:执行 npm run build:instrumentor。临时只验证接线可以加 --copy-only,但这样拿不到成员变量、println 和同步 Native 返回值。
面板一直显示 Waiting for a Kuikly page:确认构建日志出现 [kuikly-devtools] instrumented N files: M pages,设备日志出现 attached ... -> host:port,并检查 Android 的 adb reverse、iOS/鸿蒙的局域网连通性和 debug 明文 HTTP 配置。
端口被占用:换端口用 --port / --panel-port;--port 会写进产物,所以修改后要重新构建。
切回普通构建时报 Redeclaration 或 internal compiler error:通常是中途终止 Gradle 导致缓存清理没有执行。手动清理对应模块的 build/kotlin 后再构建,例如:
rm -rf sampled/build/kotlin鸿蒙链路构建失败:先不带插桩运行 ./gradlew -c settings.ohos.gradle.kts :sampled:compileKotlinJs,确认是否为原有依赖或环境问题。
七、二次开发
如果要修改 DevTools 本身,再从源码安装并构建:
git clone https://github.com/ailuoku6/kuikly-devtools.git
cd kuikly-devtools
npm install
npm run buildnpm run build 会生成插桩改写器 jar 和调试面板静态资源;完成后可在任意 Kuikly 工程中使用上面的 npx kuikly-devtools dev 或 npx kuikly-devtools build-apk。
npm install
npm test
npm run build:instrumentor
npm run build:ui没有设备时,可用 http://localhost:8090/?mock=1 或 npm run simulate 开发和验证面板。协议格式见 PROTOCOL.md。
发布到 npm
此仓库使用 npm Trusted Publisher:在 npm 包的 Trusted Publisher 设置中,将 GitHub owner/repository、工作流文件 .github/workflows/publish.yml 与发布环境(留空)配置为与本仓库一致。无需在 GitHub Actions Secrets 中配置 NPM_TOKEN。把 package.json 的 version 改到要发的版本后打 tag 推送即可:
git tag v0.1.3
git push origin v0.1.3tag 必须是 v + package.json 里的版本号,GitHub Actions 会跑测试、构建插桩 jar 和面板,并通过 GitHub OIDC 执行 npm publish。
实时编辑属性与状态
在「元素」或「组件」中选择节点后,点击右侧可写的属性值或状态值即可原位输入;可编辑值在鼠标悬停时显示虚线边框。回车或失去焦点自动应用,Esc 取消,Shift+Enter 换行。非法输入或设备拒绝修改时退出输入框,恢复显示设备的合法值并提示原因。只读值不会进入输入框,未改动的值不会提交。状态先通过「拉取成员变量」读取。
- 支持已有的渲染属性、尺寸、margin/padding、flex 和布局枚举;组件 view/attr 中可写的基础类型与
Color成员会生成 setter,包含 private、observable 和自定义 setter。 val、未初始化字段、复杂对象/集合、过长或无法无损传输的值保持只读。nullable 基础类型在源码显式标注可空时支持写入null。- 颜色可输入
0xAARRGGBB、#RRGGBB、#AARRGGBB或十进制;8 位格式的前两位是 alpha。服务端负责显示格式及回写转换,按原类型恢复十进制字符串、signed Int、Long 或 Color 的数值;运行时仅负责构造 KuiklyColor实例。原生颜色 token 也可用于字符串或 Color 字段。 - 截图上传时会在同一个 ingest 包附带最新完整节点树;树采集发生在上传时,原生截图是异步生成,两者不是像素级原子快照。
修改只影响当前页面内存,不会改业务源码;后续业务状态更新可能覆盖手动设置的属性。升级后需要重新进行插桩编译并重新打开页面,旧包仍可查看,但不会出现编辑入口。
TODO
- [ ] Mock 网络数据返回。
- [ ] Mock 原生调用返回。
新增 attr 中没有写的属性
选择节点后,在右侧「属性」区点击 + 添加属性,搜索并选择当前节点支持的属性,输入值后回车或失焦应用。合法值和格式会就近显示;未输入不会提交,Esc 取消。成功后属性成为普通可点击编辑的行。非法输入不会写入;新增行没有原值时清空草稿并提示原因。
支持基础外观(背景色、透明度、可见性、触摸开关)、尺寸与 Flex 布局、TextAttr 的文本/颜色/字号。虚拟节点、复杂渐变/变换、动态新增状态成员暂不支持。颜色在 server 内转换,八位颜色按 AARRGGBB;新增模式不接受主题 token。修改为默认布局值后仍能看到真实回读。
npx kuikly-devtools inspect props --pager 7 --id 42
npx kuikly-devtools inspect edit --pager 7 --id 42 --target p --key backgroundColor --value '"#80C8FF"' --set-supported--set-supported 会先查询设备支持能力再发送一次设置,并等待设备确认。默认 inspect edit 保留只修改已上报可写字段的行为。节点编号使用最新查询结果;按钮背景通常要选择文本的父容器。升级后重启 server、刷新面板,并重新插桩构建/加载业务页面;只有更新 server 不足以启用这项能力。已有项目可用 init-skill --force 更新 AI skill(会覆盖该 skill 的本地自定义)。
