peachsim 0.1.527

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (324) hide show
  1. package/LICENSE +22 -0
  2. package/README.md +640 -0
  3. package/cli/app-config.ts +82 -0
  4. package/cli/app-fonts.ts +388 -0
  5. package/cli/app-project.ts +59 -0
  6. package/cli/app-splash.ts +171 -0
  7. package/cli/app-state-reset.ts +26 -0
  8. package/cli/auth.ts +184 -0
  9. package/cli/bin.ts +59 -0
  10. package/cli/bridge-diagnostics.ts +226 -0
  11. package/cli/bridge-flow-runner.ts +2806 -0
  12. package/cli/browser-backend.ts +44 -0
  13. package/cli/browser-evals.ts +96 -0
  14. package/cli/cli-update.ts +261 -0
  15. package/cli/cloud-client.ts +1530 -0
  16. package/cli/cloud-dispatch.ts +206 -0
  17. package/cli/cloud-session.ts +227 -0
  18. package/cli/command-contract.ts +221 -0
  19. package/cli/command-registry.ts +304 -0
  20. package/cli/commands/agent-wrapper.ts +1048 -0
  21. package/cli/commands/agent.ts +439 -0
  22. package/cli/commands/app-fonts.ts +99 -0
  23. package/cli/commands/assert.ts +542 -0
  24. package/cli/commands/auth.ts +71 -0
  25. package/cli/commands/box/bundle-plane.ts +71 -0
  26. package/cli/commands/box/checkout-plane.ts +256 -0
  27. package/cli/commands/box/plane-share.ts +208 -0
  28. package/cli/commands/box/rnx-command.ts +60 -0
  29. package/cli/commands/box/search-command.ts +157 -0
  30. package/cli/commands/box.ts +925 -0
  31. package/cli/commands/camera.ts +266 -0
  32. package/cli/commands/cleanup.ts +169 -0
  33. package/cli/commands/compat.ts +176 -0
  34. package/cli/commands/config.ts +34 -0
  35. package/cli/commands/control.ts +2154 -0
  36. package/cli/commands/cpu-profile.ts +941 -0
  37. package/cli/commands/daemon-mac-app.ts +165 -0
  38. package/cli/commands/daemon.ts +800 -0
  39. package/cli/commands/debug.ts +751 -0
  40. package/cli/commands/desktop.ts +39 -0
  41. package/cli/commands/detect.ts +201 -0
  42. package/cli/commands/detox.ts +481 -0
  43. package/cli/commands/device.ts +134 -0
  44. package/cli/commands/diagnose.ts +589 -0
  45. package/cli/commands/do-chain.ts +408 -0
  46. package/cli/commands/electron.ts +94 -0
  47. package/cli/commands/film.ts +379 -0
  48. package/cli/commands/flow.ts +1240 -0
  49. package/cli/commands/inspect/actions.ts +516 -0
  50. package/cli/commands/inspect/core.ts +2086 -0
  51. package/cli/commands/inspect/count.ts +17 -0
  52. package/cli/commands/inspect/describe.ts +119 -0
  53. package/cli/commands/inspect/env.ts +25 -0
  54. package/cli/commands/inspect/find.ts +169 -0
  55. package/cli/commands/inspect/get-layout.ts +53 -0
  56. package/cli/commands/inspect/keyboard.ts +54 -0
  57. package/cli/commands/inspect/list.ts +175 -0
  58. package/cli/commands/inspect/memory.ts +215 -0
  59. package/cli/commands/inspect/redaction.ts +39 -0
  60. package/cli/commands/inspect/resolve-target.ts +77 -0
  61. package/cli/commands/inspect/screens.ts +78 -0
  62. package/cli/commands/inspect/settle.ts +32 -0
  63. package/cli/commands/inspect/settling.ts +197 -0
  64. package/cli/commands/inspect/shared.ts +259 -0
  65. package/cli/commands/inspect/sleep.ts +15 -0
  66. package/cli/commands/inspect/tree.ts +23 -0
  67. package/cli/commands/inspect/url.ts +17 -0
  68. package/cli/commands/inspect/wait-event.ts +211 -0
  69. package/cli/commands/inspect/wait-idle.ts +25 -0
  70. package/cli/commands/inspect/wait-ready.ts +87 -0
  71. package/cli/commands/inspect/wait-selector.ts +59 -0
  72. package/cli/commands/inspect/wait.ts +32 -0
  73. package/cli/commands/inspect.ts +4738 -0
  74. package/cli/commands/install-cli.ts +275 -0
  75. package/cli/commands/install-desktop.ts +385 -0
  76. package/cli/commands/login.ts +357 -0
  77. package/cli/commands/logout.ts +24 -0
  78. package/cli/commands/maestro-generate.ts +375 -0
  79. package/cli/commands/maestro.ts +536 -0
  80. package/cli/commands/mode.ts +58 -0
  81. package/cli/commands/no-bridge-hint.ts +80 -0
  82. package/cli/commands/perf.ts +67 -0
  83. package/cli/commands/permissions.ts +203 -0
  84. package/cli/commands/platform.ts +410 -0
  85. package/cli/commands/preview.ts +103 -0
  86. package/cli/commands/profile.ts +106 -0
  87. package/cli/commands/react.ts +353 -0
  88. package/cli/commands/record.ts +1435 -0
  89. package/cli/commands/report-issue.ts +305 -0
  90. package/cli/commands/reset.ts +89 -0
  91. package/cli/commands/runtime.ts +348 -0
  92. package/cli/commands/screenshot-command.ts +106 -0
  93. package/cli/commands/screenshot-layers.ts +144 -0
  94. package/cli/commands/screenshot-mode.ts +38 -0
  95. package/cli/commands/screenshot.ts +492 -0
  96. package/cli/commands/screenshots-capture.ts +608 -0
  97. package/cli/commands/screenshots.ts +127 -0
  98. package/cli/commands/serve.ts +139 -0
  99. package/cli/commands/setup.ts +416 -0
  100. package/cli/commands/shell-boolean-mode.ts +82 -0
  101. package/cli/commands/skills.ts +468 -0
  102. package/cli/commands/slides.ts +357 -0
  103. package/cli/commands/state.ts +94 -0
  104. package/cli/commands/storage.ts +58 -0
  105. package/cli/commands/telemetry.ts +55 -0
  106. package/cli/commands/test.ts +535 -0
  107. package/cli/commands/three-mode.ts +771 -0
  108. package/cli/commands/timeline.ts +121 -0
  109. package/cli/commands/upgrade.ts +167 -0
  110. package/cli/commands/upload.ts +1203 -0
  111. package/cli/commands/version.ts +63 -0
  112. package/cli/commands/welcome.ts +17 -0
  113. package/cli/commands/what-happened.ts +328 -0
  114. package/cli/current-sim.ts +217 -0
  115. package/cli/desktop-companion.ts +315 -0
  116. package/cli/drivers/electron.ts +70 -0
  117. package/cli/drivers/index.ts +20 -0
  118. package/cli/drivers/playwright-provisioning.ts +184 -0
  119. package/cli/drivers/playwright-sim-host.ts +537 -0
  120. package/cli/drivers/playwright.ts +209 -0
  121. package/cli/drivers/registry.ts +65 -0
  122. package/cli/drivers/types.ts +102 -0
  123. package/cli/flow-export.ts +55 -0
  124. package/cli/flow-file.ts +142 -0
  125. package/cli/flow-live-status.ts +126 -0
  126. package/cli/flow-session.ts +140 -0
  127. package/cli/help-core.ts +143 -0
  128. package/cli/help.ts +44 -0
  129. package/cli/hidden-runtime-alias.ts +19 -0
  130. package/cli/hints.ts +214 -0
  131. package/cli/inspect-notice-state.ts +114 -0
  132. package/cli/internal-child.ts +176 -0
  133. package/cli/maestro-js.ts +323 -0
  134. package/cli/main.ts +704 -0
  135. package/cli/open-url.ts +8 -0
  136. package/cli/outbound-endpoints.ts +830 -0
  137. package/cli/parent-pid.ts +204 -0
  138. package/cli/parse-args.ts +189 -0
  139. package/cli/privacy.ts +53 -0
  140. package/cli/prompt.ts +56 -0
  141. package/cli/recording-access.ts +108 -0
  142. package/cli/registry.ts +2 -0
  143. package/cli/resolve-assets.ts +63 -0
  144. package/cli/rnx-in-process.ts +98 -0
  145. package/cli/run-registry.ts +226 -0
  146. package/cli/run-rnx-core.ts +148 -0
  147. package/cli/run-rnx.ts +137 -0
  148. package/cli/runtime-notes.ts +66 -0
  149. package/cli/runtime-summary.ts +25 -0
  150. package/cli/secure-file.ts +31 -0
  151. package/cli/self-invocation.ts +58 -0
  152. package/cli/send-to-box.ts +109 -0
  153. package/cli/setup-repository.ts +191 -0
  154. package/cli/shell-init.ts +98 -0
  155. package/cli/standalone.ts +4 -0
  156. package/cli/telemetry.ts +84 -0
  157. package/cli/ui.ts +68 -0
  158. package/cli/ws-bridge.ts +1020 -0
  159. package/detox/attempt-events.cjs +23 -0
  160. package/detox/colors.ts +54 -0
  161. package/detox/config-loader.ts +135 -0
  162. package/detox/element-types.ts +48 -0
  163. package/detox/expectations.ts +534 -0
  164. package/detox/gestures.ts +501 -0
  165. package/detox/index.ts +2694 -0
  166. package/detox/jest-preset.cjs +29 -0
  167. package/detox/jest-setup-after-env.cjs +22 -0
  168. package/detox/matchers.ts +30 -0
  169. package/detox/motion-change.cjs +109 -0
  170. package/detox/motion-change.d.cts +35 -0
  171. package/detox/navigation-lifecycle.ts +290 -0
  172. package/detox/navigation.ts +43 -0
  173. package/detox/proof-frame.cjs +114 -0
  174. package/detox/record-block-execution.cjs +78 -0
  175. package/detox/screenshots/animated-color-test-rest-norngh.png +0 -0
  176. package/detox/screenshots/color-test-after-drag-norngh.png +0 -0
  177. package/detox/screenshots/color-test-rest-norngh.png +0 -0
  178. package/detox/screenshots/theme-blue-toggle.png +0 -0
  179. package/detox/screenshots/theme-blue.png +0 -0
  180. package/detox/screenshots/theme-red-toggle.png +0 -0
  181. package/detox/screenshots/theme-red.png +0 -0
  182. package/dist-lib/agent-daemon-client.cjs +502 -0
  183. package/dist-lib/agent-events.cjs +48 -0
  184. package/dist-lib/agent-identity.cjs +53 -0
  185. package/dist-lib/agent-sessions.cjs +759 -0
  186. package/dist-lib/attached-projects.cjs +526 -0
  187. package/dist-lib/auth/shared-session.cjs +208 -0
  188. package/dist-lib/backend-origin.cjs +88 -0
  189. package/dist-lib/beta.cjs +41 -0
  190. package/dist-lib/beta.mjs +13 -0
  191. package/dist-lib/bridge-constants.cjs +161 -0
  192. package/dist-lib/bridge-contract-input.cjs +451 -0
  193. package/dist-lib/bridge-contract-input.mjs +414 -0
  194. package/dist-lib/bridge-contract.cjs +130 -0
  195. package/dist-lib/bridge-contract.mjs +94 -0
  196. package/dist-lib/capture-contract.cjs +220 -0
  197. package/dist-lib/capture-contract.mjs +190 -0
  198. package/dist-lib/cli-constants.cjs +32 -0
  199. package/dist-lib/cloud-contract.cjs +194 -0
  200. package/dist-lib/cloud-contract.mjs +145 -0
  201. package/dist-lib/cloud.cjs +670 -0
  202. package/dist-lib/cloud.mjs +639 -0
  203. package/dist-lib/config.cjs +96 -0
  204. package/dist-lib/detox/index.cjs +3789 -0
  205. package/dist-lib/detox/jest-preset.cjs +29 -0
  206. package/dist-lib/dev-bundle-resolution.cjs +290 -0
  207. package/dist-lib/home-paths.cjs +638 -0
  208. package/dist-lib/host/bridge-host.cjs +10903 -0
  209. package/dist-lib/host/fetch-proxy-handler.cjs +421 -0
  210. package/dist-lib/host/fetch-proxy-overrides.cjs +63 -0
  211. package/dist-lib/host/fetch-proxy-overrides.mjs +37 -0
  212. package/dist-lib/host/replacement-module-handler.cjs +469 -0
  213. package/dist-lib/host/websocket-proxy.cjs +298 -0
  214. package/dist-lib/index.cjs +4268 -0
  215. package/dist-lib/jump-to-source-babel.cjs +93 -0
  216. package/dist-lib/jump-to-source-native.cjs +112 -0
  217. package/dist-lib/menu.cjs +646 -0
  218. package/dist-lib/menu.mjs +604 -0
  219. package/dist-lib/metro-fingerprint-registry.cjs +186 -0
  220. package/dist-lib/metro-fingerprint-registry.mjs +156 -0
  221. package/dist-lib/metro-production-bundle.cjs +329 -0
  222. package/dist-lib/metro-production-bundle.mjs +295 -0
  223. package/dist-lib/metro.cjs +904 -0
  224. package/dist-lib/profiles.cjs +246 -0
  225. package/dist-lib/public-brand.cjs +39 -0
  226. package/dist-lib/react-native-host-modules.cjs +51 -0
  227. package/dist-lib/react-native-host-modules.mjs +25 -0
  228. package/dist-lib/render-mode.cjs +54 -0
  229. package/dist-lib/scripts/dev-server-scanner.cjs +1232 -0
  230. package/dist-lib/sdk.cjs +2433 -0
  231. package/dist-lib/sdk.mjs +2336 -0
  232. package/dist-lib/skills.cjs +39132 -0
  233. package/dist-lib/swift-submit.cjs +92 -0
  234. package/dist-lib/swift-submit.mjs +65 -0
  235. package/dist-lib/swift.cjs +1646 -0
  236. package/dist-lib/vite.cjs +1591 -0
  237. package/package.json +290 -0
  238. package/scripts/dev-server-scanner.ts +1291 -0
  239. package/scripts/optional-demo-registry.ts +113 -0
  240. package/skills/contrast/SKILL.md +165 -0
  241. package/skills/rnx-debug/SKILL.md +433 -0
  242. package/skills/rnx-perf/SKILL.md +399 -0
  243. package/skills/rnx-setup/SKILL.md +172 -0
  244. package/skills/rnx-test/SKILL.md +321 -0
  245. package/skills/rnx-visual/SKILL.md +126 -0
  246. package/src/agent-daemon-client.ts +410 -0
  247. package/src/agent-events.ts +71 -0
  248. package/src/agent-identity.ts +23 -0
  249. package/src/agent-prompt.ts +71 -0
  250. package/src/agent-sessions.ts +580 -0
  251. package/src/app-url.ts +39 -0
  252. package/src/attached-projects.ts +539 -0
  253. package/src/auth/shared-session.ts +235 -0
  254. package/src/backend-origin.ts +70 -0
  255. package/src/beta.ts +19 -0
  256. package/src/bridge-constants.ts +175 -0
  257. package/src/bridge-contract-input.ts +502 -0
  258. package/src/bridge-contract.ts +558 -0
  259. package/src/browser-cache.ts +16 -0
  260. package/src/capture-contract.ts +587 -0
  261. package/src/cli-constants.ts +1 -0
  262. package/src/cli-version.ts +40 -0
  263. package/src/cloud-contract.ts +274 -0
  264. package/src/cloud.ts +762 -0
  265. package/src/codex-client.ts +234 -0
  266. package/src/config.ts +132 -0
  267. package/src/connect.ts +101 -0
  268. package/src/dev-bundle-resolution.ts +346 -0
  269. package/src/dev-server-open.ts +198 -0
  270. package/src/disk-cleanup.ts +540 -0
  271. package/src/flow-timeout-scale.ts +15 -0
  272. package/src/home-paths.ts +806 -0
  273. package/src/host/agent-host.ts +666 -0
  274. package/src/host/bridge-host.ts +2788 -0
  275. package/src/host/fetch-proxy-handler.ts +449 -0
  276. package/src/host/fetch-proxy-overrides.ts +51 -0
  277. package/src/host/open-url.ts +89 -0
  278. package/src/host/plane-host.ts +203 -0
  279. package/src/host/replacement-module-handler.ts +158 -0
  280. package/src/host/websocket-proxy.ts +315 -0
  281. package/src/index.ts +71 -0
  282. package/src/jump-to-source-babel.ts +135 -0
  283. package/src/jump-to-source-native.ts +102 -0
  284. package/src/menu.ts +777 -0
  285. package/src/metro-fingerprint-registry.ts +302 -0
  286. package/src/metro-plugin.ts +341 -0
  287. package/src/metro-production-bundle.ts +471 -0
  288. package/src/native-dev-bundle-url.ts +118 -0
  289. package/src/native-seam-manifest.ts +341 -0
  290. package/src/playwright-host-log.ts +16 -0
  291. package/src/plist.ts +27 -0
  292. package/src/poll-until.ts +123 -0
  293. package/src/profiles.ts +179 -0
  294. package/src/public-brand.ts +9 -0
  295. package/src/react-native-host-modules.ts +26 -0
  296. package/src/render-mode.ts +27 -0
  297. package/src/rolldown-plugin-swift.ts +240 -0
  298. package/src/runtime-assets.ts +233 -0
  299. package/src/runtime-delivery.ts +54 -0
  300. package/src/screenshots/compose.ts +422 -0
  301. package/src/screenshots/frame-compose.ts +514 -0
  302. package/src/screenshots/orchestrate.ts +256 -0
  303. package/src/screenshots/org-deck.ts +91 -0
  304. package/src/screenshots/registry.ts +58 -0
  305. package/src/screenshots/schema.ts +364 -0
  306. package/src/sdk.ts +15 -0
  307. package/src/settings-bundle.ts +418 -0
  308. package/src/shell-bridge-identity.ts +15 -0
  309. package/src/sim-client.ts +697 -0
  310. package/src/skills/builtin/a11y-review.ts +132 -0
  311. package/src/skills/builtin/compat-check.ts +71 -0
  312. package/src/skills/builtin/maestro-test.ts +121 -0
  313. package/src/skills/builtin/perf-profile.ts +90 -0
  314. package/src/skills/builtin/screenshot-all.ts +52 -0
  315. package/src/skills/builtin/visual-diff.ts +100 -0
  316. package/src/skills/registry.ts +107 -0
  317. package/src/skills/types.ts +41 -0
  318. package/src/swift-submit.ts +185 -0
  319. package/src/swift.ts +7 -0
  320. package/src/team-machine-prompt.ts +73 -0
  321. package/src/vite-plugin-one.ts +264 -0
  322. package/src/vite-plugin-swift.ts +620 -0
  323. package/src/vite-plugin.ts +1946 -0
  324. package/src/worklets-babel.ts +250 -0
@@ -0,0 +1,132 @@
1
+ // built-in skill: accessibility audit
2
+
3
+ import { launchReapedChrome } from '../../../../../scripts/lib/reap-browser'
4
+ import type { RNXSkill } from '../types'
5
+
6
+ export const skill: RNXSkill = {
7
+ name: 'a11y-review',
8
+ description: 'Check the app for accessibility issues',
9
+ type: 'review',
10
+ version: '1.0.0',
11
+ triggers: ['accessibility', 'a11y', 'check accessibility', 'audit accessibility'],
12
+ tools: [
13
+ {
14
+ name: 'audit_accessibility',
15
+ description: 'Run an accessibility audit on the current screen',
16
+ parameters: {
17
+ screen: { type: 'string', description: 'Screen name or URL to audit' },
18
+ },
19
+ async execute(params, context) {
20
+ const { chromium } = await import('playwright')
21
+ const path = await import('path')
22
+ const fs = await import('fs')
23
+
24
+ // sootsim renders CanvasKit — require GPU-backed Chrome (Metal). the
25
+ // bundled swiftshader shell software-renders the canvas and pegs every
26
+ // core; never use it for sootsim.
27
+ const browser = await launchReapedChrome((options) => chromium.launch(options), {
28
+ headless: true,
29
+ })
30
+ const page = await browser.newPage({ viewport: { width: 500, height: 900 } })
31
+
32
+ try {
33
+ await page.goto(context.url, { waitUntil: 'networkidle' })
34
+ await page.waitForFunction('window.__sootsimTest', { timeout: 10000 })
35
+ await page.waitForTimeout(2000)
36
+
37
+ // get accessibility tree
38
+ const tree = await page.evaluate(
39
+ 'window.__sootsimTest.dumpAccessibilityTree(10)',
40
+ )
41
+ const allNodes = await page.evaluate('window.__sootsimTest.queryAll({})')
42
+
43
+ // analyze for common issues
44
+ const issues: {
45
+ severity: 'error' | 'warning' | 'info'
46
+ message: string
47
+ node?: string
48
+ }[] = []
49
+
50
+ if (Array.isArray(allNodes)) {
51
+ for (const node of allNodes) {
52
+ // check for missing accessibility labels on interactive elements
53
+ if (
54
+ (node.accessibilityRole === 'button' ||
55
+ node.accessibilityRole === 'link' ||
56
+ node.type === 'TouchableOpacity' ||
57
+ node.type === 'Pressable') &&
58
+ !node.accessibilityLabel &&
59
+ !node.text
60
+ ) {
61
+ issues.push({
62
+ severity: 'error',
63
+ message: `Interactive element missing accessibility label`,
64
+ node: node.testID || node.id || node.type,
65
+ })
66
+ }
67
+
68
+ // check for missing roles
69
+ if (
70
+ (node.type === 'TouchableOpacity' || node.type === 'Pressable') &&
71
+ !node.accessibilityRole
72
+ ) {
73
+ issues.push({
74
+ severity: 'warning',
75
+ message: `Touchable element missing accessibility role`,
76
+ node: node.testID || node.id || node.type,
77
+ })
78
+ }
79
+
80
+ // check for images without alt text
81
+ if (node.type === 'Image' && !node.accessibilityLabel && !node.accessible) {
82
+ issues.push({
83
+ severity: 'warning',
84
+ message: `Image missing accessibility label`,
85
+ node: node.testID || node.id || 'Image',
86
+ })
87
+ }
88
+
89
+ // check for small touch targets
90
+ if (
91
+ node.layout &&
92
+ (node.accessibilityRole === 'button' ||
93
+ node.type === 'TouchableOpacity') &&
94
+ (node.layout.width < 44 || node.layout.height < 44)
95
+ ) {
96
+ issues.push({
97
+ severity: 'warning',
98
+ message: `Touch target too small (${Math.round(node.layout.width)}x${Math.round(node.layout.height)}pt, minimum 44x44pt)`,
99
+ node: node.testID || node.text || node.type,
100
+ })
101
+ }
102
+ }
103
+ }
104
+
105
+ // write report
106
+ const report = {
107
+ timestamp: new Date().toISOString(),
108
+ screen: params.screen || 'current',
109
+ nodeCount: Array.isArray(allNodes) ? allNodes.length : 0,
110
+ issues,
111
+ errors: issues.filter((i) => i.severity === 'error').length,
112
+ warnings: issues.filter((i) => i.severity === 'warning').length,
113
+ tree: typeof tree === 'string' ? tree : JSON.stringify(tree),
114
+ }
115
+
116
+ const reportPath = path.join(context.outputDir, 'a11y-report.json')
117
+ fs.mkdirSync(path.dirname(reportPath), { recursive: true })
118
+ fs.writeFileSync(reportPath, JSON.stringify(report, null, 2))
119
+
120
+ return {
121
+ success: issues.filter((i) => i.severity === 'error').length === 0,
122
+ message: `${report.errors} errors, ${report.warnings} warnings across ${report.nodeCount} nodes`,
123
+ artifacts: [{ type: 'report', name: 'a11y-report', path: reportPath }],
124
+ data: report,
125
+ }
126
+ } finally {
127
+ await browser.close()
128
+ }
129
+ },
130
+ },
131
+ ],
132
+ }
@@ -0,0 +1,71 @@
1
+ // built-in skill: package compatibility check
2
+
3
+ import type { RNXSkill } from '../types'
4
+
5
+ export const skill: RNXSkill = {
6
+ name: 'compat-check',
7
+ description: 'Check package compatibility with rnx',
8
+ type: 'review',
9
+ version: '1.0.0',
10
+ triggers: ['compatibility', 'compat', 'check package', 'supported packages'],
11
+ tools: [
12
+ {
13
+ name: 'check_compatibility',
14
+ description: 'Scan project dependencies for rnx compatibility',
15
+ parameters: {
16
+ packageName: {
17
+ type: 'string',
18
+ description: 'Specific package to check (optional)',
19
+ },
20
+ },
21
+ async execute(params, context) {
22
+ const fs = await import('node:fs')
23
+ const path = await import('node:path')
24
+ const { COMPAT_RESULT_CAVEAT, formatPackageStatus, formatScanCounts, scanDeps } =
25
+ await import('../../../../compat/src/web.ts')
26
+
27
+ const packagePath = path.join(context.projectDir, 'package.json')
28
+ if (!fs.existsSync(packagePath)) {
29
+ return { success: false, message: 'No package.json found' }
30
+ }
31
+ const parsed: unknown = JSON.parse(fs.readFileSync(packagePath, 'utf8'))
32
+ if (!parsed || typeof parsed !== 'object') {
33
+ return { success: false, message: 'package.json is not an object' }
34
+ }
35
+
36
+ const dependencies = readDependencies(parsed)
37
+ const scan = params.packageName
38
+ ? scanDeps({
39
+ dependencies: {
40
+ [params.packageName]: dependencies[params.packageName] ?? '*',
41
+ },
42
+ })
43
+ : scanDeps(parsed)
44
+ const packageResult = params.packageName ? scan.packages[0] : undefined
45
+ const message = packageResult
46
+ ? `${params.packageName}: ${formatPackageStatus(packageResult)}. ${COMPAT_RESULT_CAVEAT}`
47
+ : `${formatScanCounts(scan)}. ${COMPAT_RESULT_CAVEAT}`
48
+
49
+ const reportPath = path.join(context.outputDir, 'compat-report.json')
50
+ fs.mkdirSync(path.dirname(reportPath), { recursive: true })
51
+ fs.writeFileSync(reportPath, `${JSON.stringify(scan, null, 2)}\n`)
52
+
53
+ return {
54
+ success: true,
55
+ message,
56
+ artifacts: [{ type: 'report', name: 'compat-report', path: reportPath }],
57
+ data: scan,
58
+ }
59
+ },
60
+ },
61
+ ],
62
+ }
63
+
64
+ function readDependencies(value: object): Record<string, string> {
65
+ const dependencies = Reflect.get(value, 'dependencies')
66
+ const devDependencies = Reflect.get(value, 'devDependencies')
67
+ return {
68
+ ...(dependencies && typeof dependencies === 'object' ? dependencies : {}),
69
+ ...(devDependencies && typeof devDependencies === 'object' ? devDependencies : {}),
70
+ }
71
+ }
@@ -0,0 +1,121 @@
1
+ // built-in skill: run Maestro tests against the app
2
+
3
+ import type { RNXSkill } from '../types'
4
+
5
+ function sleep(ms: number) {
6
+ return new Promise((resolve) => setTimeout(resolve, ms))
7
+ }
8
+
9
+ export const skill: RNXSkill = {
10
+ name: 'maestro-test',
11
+ description: 'Run Maestro YAML tests against the app',
12
+ type: 'testing',
13
+ version: '1.0.0',
14
+ triggers: ['run test', 'test flow', 'run maestro', 'test the app'],
15
+ tools: [
16
+ {
17
+ name: 'run_maestro',
18
+ description: 'Run a Maestro YAML test against the app',
19
+ parameters: {
20
+ flowPath: {
21
+ type: 'string',
22
+ description: 'Path to the YAML flow file',
23
+ required: true,
24
+ },
25
+ record: { type: 'boolean', description: 'Record video of the flow' },
26
+ },
27
+ async execute(params, context) {
28
+ const fs = await import('fs')
29
+ const path = await import('path')
30
+ const yaml = await import('yaml')
31
+ const { DEFAULT_SOOTSIM_BRIDGE_PORT } = await import('../../bridge-constants')
32
+ const { SootSimBridgeHost } = await import('../../host/bridge-host')
33
+ const { createBridge } = await import('../../../cli/ws-bridge')
34
+ const { runOpenCommand } = await import('../../../cli/commands/control')
35
+ const { SootSimBridgeFlowRunner } =
36
+ await import('../../../cli/bridge-flow-runner')
37
+
38
+ const host = new SootSimBridgeHost({ port: DEFAULT_SOOTSIM_BRIDGE_PORT })
39
+ host.start({ silent: true })
40
+
41
+ try {
42
+ const flowContent = fs.readFileSync(params.flowPath, 'utf8')
43
+ const fmMatch = flowContent.match(/^---\n([\s\S]*?)\n---\n?/)
44
+ const stepsBody = fmMatch ? flowContent.slice(fmMatch[0].length) : flowContent
45
+ const steps = yaml.parse(stepsBody)
46
+ const beforeIds = new Set(host.listSims().map((sim) => sim.id))
47
+
48
+ await runOpenCommand([context.url, '--new', '--headless', '--no-describe'], {
49
+ port: DEFAULT_SOOTSIM_BRIDGE_PORT,
50
+ })
51
+
52
+ let simId: string | null = null
53
+ for (let i = 0; i < 60; i++) {
54
+ const sims = host.listSims()
55
+ const opened =
56
+ sims.find((sim) => !beforeIds.has(sim.id) && sim.readyState === 'open') ||
57
+ sims.find((sim) => sim.readyState === 'open')
58
+ if (opened) {
59
+ simId = opened.id
60
+ break
61
+ }
62
+ await sleep(500)
63
+ }
64
+
65
+ if (!simId) {
66
+ throw new Error('no sim connected to the bridge host')
67
+ }
68
+
69
+ const bridge = createBridge(DEFAULT_SOOTSIM_BRIDGE_PORT, {
70
+ simId,
71
+ commandTimeoutMs: 15000,
72
+ })
73
+ const driver = new SootSimBridgeFlowRunner(bridge, {
74
+ screenshotDir: context.outputDir,
75
+ flowDir: path.dirname(path.resolve(params.flowPath)),
76
+ simId,
77
+ recordingOutputDir: params.record ? context.outputDir : undefined,
78
+ })
79
+
80
+ let videoPath: string | null = null
81
+
82
+ try {
83
+ await driver.waitForTree(120000)
84
+ if (params.record) {
85
+ await driver.startRecording()
86
+ }
87
+ await driver.runFlow(steps)
88
+ if (params.record) {
89
+ videoPath = await driver.stopRecording()
90
+ }
91
+
92
+ return {
93
+ success: true,
94
+ message: `Flow completed: ${steps.length} steps passed`,
95
+ data: {
96
+ steps: steps.length,
97
+ simId,
98
+ videoPath: videoPath || undefined,
99
+ },
100
+ artifacts: videoPath
101
+ ? [{ type: 'video', name: path.basename(videoPath), path: videoPath }]
102
+ : undefined,
103
+ }
104
+ } finally {
105
+ try {
106
+ await bridge.closeSim(simId)
107
+ } catch {}
108
+ bridge.close()
109
+ }
110
+ } catch (err: any) {
111
+ return {
112
+ success: false,
113
+ message: `Flow failed: ${err.message}`,
114
+ }
115
+ } finally {
116
+ await host.close().catch(() => {})
117
+ }
118
+ },
119
+ },
120
+ ],
121
+ }
@@ -0,0 +1,90 @@
1
+ // built-in skill: performance profiling
2
+
3
+ import { launchReapedChrome } from '../../../../../scripts/lib/reap-browser'
4
+ import type { RNXSkill } from '../types'
5
+
6
+ interface OverlayProfile {
7
+ syncCount?: number
8
+ avgSyncMs?: number
9
+ [key: string]: unknown
10
+ }
11
+
12
+ export const skill: RNXSkill = {
13
+ name: 'perf-profile',
14
+ description: 'Profile render performance of the app',
15
+ type: 'performance',
16
+ version: '1.0.0',
17
+ triggers: ['performance', 'profile', 'render time', 'benchmark', 'perf'],
18
+ tools: [
19
+ {
20
+ name: 'profile_render',
21
+ description: 'Profile render performance of the current screen',
22
+ parameters: {
23
+ duration: {
24
+ type: 'number',
25
+ description: 'Profile duration in seconds (default: 5)',
26
+ },
27
+ interactions: {
28
+ type: 'boolean',
29
+ description: 'Include user interactions in profiling',
30
+ },
31
+ },
32
+ async execute(params, context) {
33
+ const { chromium } = await import('playwright')
34
+ const path = await import('path')
35
+ const fs = await import('fs')
36
+
37
+ // sootsim renders CanvasKit — require GPU-backed Chrome (Metal). the
38
+ // bundled swiftshader shell software-renders the canvas and pegs every
39
+ // core; never use it for sootsim.
40
+ const browser = await launchReapedChrome((options) => chromium.launch(options), {
41
+ headless: true,
42
+ })
43
+ const page = await browser.newPage({ viewport: { width: 500, height: 900 } })
44
+
45
+ try {
46
+ await page.goto(context.url, { waitUntil: 'networkidle' })
47
+ await page.waitForFunction('window.__sootsimTest && window.__sootsimA11y', {
48
+ timeout: 10000,
49
+ })
50
+ await page.waitForTimeout(1000)
51
+
52
+ // reset profiler
53
+ await page.evaluate('window.__sootsimA11y.resetProfile()')
54
+
55
+ // wait for profile duration
56
+ const duration = (params.duration || 5) * 1000
57
+ await page.waitForTimeout(duration)
58
+
59
+ // collect results
60
+ const profile = (await page.evaluate(
61
+ 'window.__sootsimA11y.profile()',
62
+ )) as OverlayProfile
63
+ const nodeCount = await page.evaluate('window.__sootsimTest.getNodeCount()')
64
+ const syncCount = profile.syncCount ?? 0
65
+
66
+ const report = {
67
+ timestamp: new Date().toISOString(),
68
+ duration: params.duration || 5,
69
+ nodeCount,
70
+ ...profile,
71
+ fps: syncCount > 0 ? (syncCount / (duration / 1000)).toFixed(1) : '0',
72
+ }
73
+
74
+ const reportPath = path.join(context.outputDir, 'perf-report.json')
75
+ fs.mkdirSync(path.dirname(reportPath), { recursive: true })
76
+ fs.writeFileSync(reportPath, JSON.stringify(report, null, 2))
77
+
78
+ return {
79
+ success: true,
80
+ message: `${report.fps} fps, ${profile.avgSyncMs?.toFixed(1)}ms avg sync, ${nodeCount} nodes`,
81
+ artifacts: [{ type: 'report', name: 'perf-report', path: reportPath }],
82
+ data: report,
83
+ }
84
+ } finally {
85
+ await browser.close()
86
+ }
87
+ },
88
+ },
89
+ ],
90
+ }
@@ -0,0 +1,52 @@
1
+ // built-in skill: capture all screens in light/dark mode
2
+
3
+ import { launchReapedChrome } from '../../../../../scripts/lib/reap-browser'
4
+ import type { RNXSkill } from '../types'
5
+
6
+ export const skill: RNXSkill = {
7
+ name: 'screenshot-all',
8
+ description: 'Capture screenshots of all screens in the app',
9
+ type: 'screenshot',
10
+ version: '1.0.0',
11
+ triggers: ['screenshot', 'capture screens', 'capture all', 'take screenshots'],
12
+ tools: [
13
+ {
14
+ name: 'capture_screenshot',
15
+ description: 'Capture a screenshot of the current screen',
16
+ parameters: {
17
+ name: { type: 'string', description: 'Screenshot name', required: true },
18
+ theme: { type: 'string', description: 'Color scheme: light or dark' },
19
+ },
20
+ async execute(params, context) {
21
+ const { chromium } = await import('playwright')
22
+ const path = await import('path')
23
+ const fs = await import('fs')
24
+
25
+ // sootsim renders CanvasKit — require GPU-backed Chrome (Metal). the
26
+ // bundled swiftshader shell software-renders the canvas and pegs every
27
+ // core; never use it for sootsim.
28
+ const browser = await launchReapedChrome((options) => chromium.launch(options), {
29
+ headless: true,
30
+ })
31
+ const page = await browser.newPage({ viewport: { width: 500, height: 900 } })
32
+
33
+ try {
34
+ await page.goto(context.url, { waitUntil: 'networkidle' })
35
+ await page.waitForTimeout(2000)
36
+
37
+ const outputPath = path.join(context.outputDir, `${params.name}.png`)
38
+ fs.mkdirSync(path.dirname(outputPath), { recursive: true })
39
+ await page.screenshot({ path: outputPath })
40
+
41
+ return {
42
+ success: true,
43
+ message: `Screenshot saved: ${params.name}`,
44
+ artifacts: [{ type: 'screenshot', name: params.name, path: outputPath }],
45
+ }
46
+ } finally {
47
+ await browser.close()
48
+ }
49
+ },
50
+ },
51
+ ],
52
+ }
@@ -0,0 +1,100 @@
1
+ // built-in skill: visual diff against baselines
2
+
3
+ import { launchReapedChrome } from '../../../../../scripts/lib/reap-browser'
4
+ import type { RNXSkill } from '../types'
5
+
6
+ export const skill: RNXSkill = {
7
+ name: 'visual-diff',
8
+ description: 'Compare current rendering against baseline screenshots',
9
+ type: 'review',
10
+ version: '1.0.0',
11
+ triggers: ['visual diff', 'compare', 'visual regression', 'diff screenshots'],
12
+ tools: [
13
+ {
14
+ name: 'visual_diff',
15
+ description: 'Compare current screen against a baseline screenshot',
16
+ parameters: {
17
+ baseline: {
18
+ type: 'string',
19
+ description: 'Path to baseline screenshot',
20
+ required: true,
21
+ },
22
+ name: { type: 'string', description: 'Name for the diff output', required: true },
23
+ threshold: {
24
+ type: 'number',
25
+ description: 'Pixel match threshold (0-1, default 0.1)',
26
+ },
27
+ },
28
+ async execute(params, context) {
29
+ const { chromium } = await import('playwright')
30
+ const path = await import('path')
31
+ const fs = await import('fs')
32
+
33
+ // sootsim renders CanvasKit — require GPU-backed Chrome (Metal). the
34
+ // bundled swiftshader shell software-renders the canvas and pegs every
35
+ // core; never use it for sootsim.
36
+ const browser = await launchReapedChrome((options) => chromium.launch(options), {
37
+ headless: true,
38
+ })
39
+ const page = await browser.newPage({ viewport: { width: 500, height: 900 } })
40
+
41
+ try {
42
+ await page.goto(context.url, { waitUntil: 'networkidle' })
43
+ await page.waitForTimeout(2000)
44
+
45
+ // capture current
46
+ const currentPath = path.join(context.outputDir, `${params.name}-current.png`)
47
+ fs.mkdirSync(path.dirname(currentPath), { recursive: true })
48
+ await page.screenshot({ path: currentPath })
49
+
50
+ // compare using pixelmatch if available
51
+ try {
52
+ const { PNG } = await import('pngjs')
53
+ const pixelmatch = (await import('pixelmatch')).default
54
+
55
+ const baseline = PNG.sync.read(fs.readFileSync(params.baseline))
56
+ const current = PNG.sync.read(fs.readFileSync(currentPath))
57
+
58
+ const { width, height } = baseline
59
+ const diff = new PNG({ width, height })
60
+
61
+ const numDiffPixels = pixelmatch(
62
+ baseline.data,
63
+ current.data,
64
+ diff.data,
65
+ width,
66
+ height,
67
+ { threshold: params.threshold || 0.1 },
68
+ )
69
+
70
+ const diffPath = path.join(context.outputDir, `${params.name}-diff.png`)
71
+ fs.writeFileSync(diffPath, PNG.sync.write(diff))
72
+
73
+ const totalPixels = width * height
74
+ const diffPercent = ((numDiffPixels / totalPixels) * 100).toFixed(2)
75
+
76
+ return {
77
+ success: numDiffPixels === 0,
78
+ message: `${diffPercent}% difference (${numDiffPixels} pixels)`,
79
+ artifacts: [
80
+ { type: 'screenshot', name: `${params.name}-current`, path: currentPath },
81
+ { type: 'screenshot', name: `${params.name}-diff`, path: diffPath },
82
+ ],
83
+ data: { diffPixels: numDiffPixels, totalPixels, diffPercent },
84
+ }
85
+ } catch {
86
+ return {
87
+ success: true,
88
+ message: 'Screenshot captured (pixelmatch not available for comparison)',
89
+ artifacts: [
90
+ { type: 'screenshot', name: `${params.name}-current`, path: currentPath },
91
+ ],
92
+ }
93
+ }
94
+ } finally {
95
+ await browser.close()
96
+ }
97
+ },
98
+ },
99
+ ],
100
+ }
@@ -0,0 +1,107 @@
1
+ // skill registry — load, discover, and match skills
2
+
3
+ import * as fs from 'fs'
4
+ import * as path from 'path'
5
+ import { skill as a11yReviewSkill } from './builtin/a11y-review'
6
+ import { skill as compatCheckSkill } from './builtin/compat-check'
7
+ import { skill as maestroTestSkill } from './builtin/maestro-test'
8
+ import { skill as perfProfileSkill } from './builtin/perf-profile'
9
+ import { skill as screenshotAllSkill } from './builtin/screenshot-all'
10
+ import { skill as visualDiffSkill } from './builtin/visual-diff'
11
+ import type { RNXSkill, SkillContext } from './types'
12
+
13
+ const _skills: Map<string, RNXSkill> = new Map()
14
+ const BUILTIN_SKILLS = [
15
+ a11yReviewSkill,
16
+ compatCheckSkill,
17
+ perfProfileSkill,
18
+ screenshotAllSkill,
19
+ maestroTestSkill,
20
+ visualDiffSkill,
21
+ ]
22
+
23
+ export const skillRegistry = {
24
+ register(skill: RNXSkill) {
25
+ _skills.set(skill.name, skill)
26
+ },
27
+
28
+ get(name: string): RNXSkill | undefined {
29
+ return _skills.get(name)
30
+ },
31
+
32
+ listAll(): RNXSkill[] {
33
+ return Array.from(_skills.values())
34
+ },
35
+
36
+ // match skills by natural language query against triggers
37
+ match(query: string): RNXSkill[] {
38
+ const q = query.toLowerCase()
39
+ return this.listAll().filter((skill) =>
40
+ skill.triggers.some((trigger) => q.includes(trigger.toLowerCase())),
41
+ )
42
+ },
43
+
44
+ // load built-in skills
45
+ async loadBuiltins() {
46
+ for (const skill of BUILTIN_SKILLS) {
47
+ this.register(skill)
48
+ }
49
+ },
50
+
51
+ // load skills from a project manifest (rnx-skills.json)
52
+ async loadFromManifest(projectDir: string) {
53
+ const manifestPath = path.join(projectDir, 'rnx-skills.json')
54
+ if (!fs.existsSync(manifestPath)) return
55
+
56
+ try {
57
+ const manifest = JSON.parse(fs.readFileSync(manifestPath, 'utf8'))
58
+ if (Array.isArray(manifest.skills)) {
59
+ for (const entry of manifest.skills) {
60
+ if (typeof entry === 'string') {
61
+ // npm package name
62
+ try {
63
+ const mod = await import(entry)
64
+ if (mod.skill) this.register(mod.skill)
65
+ } catch {}
66
+ } else if (entry.file) {
67
+ // local file
68
+ try {
69
+ const mod = await import(path.resolve(projectDir, entry.file))
70
+ if (mod.skill) this.register(mod.skill)
71
+ } catch {}
72
+ }
73
+ }
74
+ }
75
+ } catch {}
76
+ },
77
+
78
+ // auto-discover skills based on project dependencies
79
+ suggestSkills(projectDir: string): string[] {
80
+ const suggestions: string[] = []
81
+ const pkgPath = path.join(projectDir, 'package.json')
82
+ if (!fs.existsSync(pkgPath)) return suggestions
83
+
84
+ try {
85
+ const pkg = JSON.parse(fs.readFileSync(pkgPath, 'utf8'))
86
+ const deps = { ...pkg.dependencies, ...pkg.devDependencies }
87
+
88
+ // suggest testing skill if detox or maestro is in deps
89
+ if (deps['detox'] || deps['maestro']) {
90
+ suggestions.push('maestro-test')
91
+ }
92
+
93
+ // suggest a11y review if no a11y testing library
94
+ if (!deps['jest-axe'] && !deps['@testing-library/jest-dom']) {
95
+ suggestions.push('a11y-review')
96
+ }
97
+
98
+ // always suggest visual-diff and screenshot
99
+ suggestions.push('visual-diff', 'screenshot-all')
100
+
101
+ // suggest perf if app has many screens
102
+ suggestions.push('perf-profile')
103
+ } catch {}
104
+
105
+ return suggestions
106
+ },
107
+ }