peachsim 0.1.577 → 0.1.579

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 (178) hide show
  1. package/LICENSE +1 -1
  2. package/README.md +11 -9
  3. package/cli/app-catalog.ts +77 -0
  4. package/cli/app-fonts.ts +4 -20
  5. package/cli/app-splash.ts +2 -18
  6. package/cli/bridge-flow-runner.ts +505 -139
  7. package/cli/cloud-client.ts +1 -4
  8. package/cli/cloud-session.ts +1 -4
  9. package/cli/cloud-test-result-files.ts +3 -0
  10. package/cli/cloud-test-tunnel.ts +2 -5
  11. package/cli/command-registry.ts +2 -0
  12. package/cli/commands/agent.ts +149 -23
  13. package/cli/commands/assert.ts +1 -4
  14. package/cli/commands/box/peach-command.ts +62 -0
  15. package/cli/commands/box/plane-share.ts +0 -70
  16. package/cli/commands/box/rnx-command.ts +1 -60
  17. package/cli/commands/box/search-command.ts +14 -0
  18. package/cli/commands/box.ts +2 -2
  19. package/cli/commands/control.ts +121 -27
  20. package/cli/commands/cpu-profile.ts +23 -10
  21. package/cli/commands/daemon.ts +1 -9
  22. package/cli/commands/detox.ts +26 -18
  23. package/cli/commands/device.ts +129 -1
  24. package/cli/commands/film.ts +25 -16
  25. package/cli/commands/flow.ts +9 -2
  26. package/cli/commands/inspect/actions.ts +75 -1
  27. package/cli/commands/inspect/core.ts +81 -2
  28. package/cli/commands/inspect/describe.ts +2 -6
  29. package/cli/commands/inspect/env.ts +1 -1
  30. package/cli/commands/inspect/settling.ts +20 -3
  31. package/cli/commands/inspect/shared.ts +1 -4
  32. package/cli/commands/inspect/sleep.ts +1 -1
  33. package/cli/commands/inspect.ts +16 -49
  34. package/cli/commands/install-desktop.ts +8 -6
  35. package/cli/commands/login.ts +3 -5
  36. package/cli/commands/maestro-generate.ts +1 -4
  37. package/cli/commands/maestro.ts +38 -21
  38. package/cli/commands/no-bridge-hint.ts +0 -14
  39. package/cli/commands/permissions.ts +2 -2
  40. package/cli/commands/platform.ts +13 -6
  41. package/cli/commands/screenshot.ts +6 -1
  42. package/cli/commands/skills.ts +1 -1
  43. package/cli/commands/test.ts +6 -2
  44. package/cli/commands/three-mode.ts +162 -19
  45. package/cli/commands/upload.ts +3 -18
  46. package/cli/current-sim.ts +0 -1
  47. package/cli/desktop-companion.ts +40 -8
  48. package/cli/drivers/playwright-provisioning.ts +1 -4
  49. package/cli/flow-export.ts +1 -8
  50. package/cli/flow-session.ts +0 -4
  51. package/cli/hints.ts +1 -16
  52. package/cli/inspect-notice-state.ts +0 -4
  53. package/cli/main.ts +2 -4
  54. package/cli/outbound-endpoints.ts +1 -24
  55. package/cli/parse-args.ts +1 -2
  56. package/cli/rnx-in-process.ts +3 -1
  57. package/cli/run-rnx-core.ts +4 -2
  58. package/cli/run-rnx.ts +18 -10
  59. package/cli/send-to-box.ts +1 -4
  60. package/cli/telemetry.ts +1 -1
  61. package/cli/ws-bridge.ts +1 -5
  62. package/detox/gestures.ts +20 -6
  63. package/detox/index.ts +379 -123
  64. package/detox/navigation-lifecycle.ts +22 -13
  65. package/dist-lib/agent-daemon-client.cjs +21 -15
  66. package/dist-lib/agent-events.cjs +1 -1
  67. package/dist-lib/agent-identity.cjs +3 -17
  68. package/dist-lib/agent-sessions.cjs +5 -4
  69. package/dist-lib/attached-projects.cjs +1 -77
  70. package/dist-lib/auth/shared-session.cjs +1 -1
  71. package/dist-lib/backend-origin.cjs +11 -1
  72. package/dist-lib/beta.cjs +1 -1
  73. package/dist-lib/beta.mjs +1 -1
  74. package/dist-lib/bridge-constants.cjs +1 -1
  75. package/dist-lib/bridge-contract-input.cjs +7 -5
  76. package/dist-lib/bridge-contract-input.mjs +7 -5
  77. package/dist-lib/bridge-contract.cjs +1 -1
  78. package/dist-lib/bridge-contract.mjs +1 -1
  79. package/dist-lib/capture-contract.cjs +30 -7
  80. package/dist-lib/capture-contract.mjs +30 -7
  81. package/dist-lib/cli-constants.cjs +1 -1
  82. package/dist-lib/cloud-contract.cjs +1 -1
  83. package/dist-lib/cloud-contract.mjs +1 -1
  84. package/dist-lib/cloud-test-args.cjs +1 -1
  85. package/dist-lib/cloud-test-args.mjs +1 -1
  86. package/dist-lib/cloud.cjs +17 -11
  87. package/dist-lib/cloud.mjs +17 -11
  88. package/dist-lib/config.cjs +1 -1
  89. package/dist-lib/detox/index.cjs +321 -122
  90. package/dist-lib/dev-bundle-resolution.cjs +1 -1
  91. package/dist-lib/home-paths.cjs +1 -1
  92. package/dist-lib/host/bridge-host.cjs +427 -212
  93. package/dist-lib/host/fetch-proxy-handler.cjs +5 -28
  94. package/dist-lib/host/fetch-proxy-overrides.cjs +1 -1
  95. package/dist-lib/host/fetch-proxy-overrides.mjs +1 -1
  96. package/dist-lib/host/replacement-module-handler.cjs +1 -1
  97. package/dist-lib/host/websocket-proxy.cjs +1 -1
  98. package/dist-lib/index.cjs +315 -58
  99. package/dist-lib/jump-to-source-babel.cjs +1 -1
  100. package/dist-lib/jump-to-source-native.cjs +1 -1
  101. package/dist-lib/menu.cjs +67 -31
  102. package/dist-lib/menu.mjs +64 -28
  103. package/dist-lib/metro-fingerprint-registry.cjs +8 -4
  104. package/dist-lib/metro-fingerprint-registry.mjs +6 -4
  105. package/dist-lib/metro-production-bundle.cjs +2 -2
  106. package/dist-lib/metro-production-bundle.mjs +2 -2
  107. package/dist-lib/metro.cjs +8 -4
  108. package/dist-lib/profiles.cjs +1 -1
  109. package/dist-lib/public-brand.cjs +7 -4
  110. package/dist-lib/react-native-host-modules.cjs +1 -1
  111. package/dist-lib/react-native-host-modules.mjs +1 -1
  112. package/dist-lib/render-mode.cjs +3 -13
  113. package/dist-lib/scripts/dev-server-scanner.cjs +5 -17
  114. package/dist-lib/sdk.cjs +129 -10
  115. package/dist-lib/sdk.mjs +127 -10
  116. package/dist-lib/skills.cjs +1635 -1121
  117. package/dist-lib/swift-compile.cjs +277 -70
  118. package/dist-lib/swift-compile.mjs +275 -70
  119. package/dist-lib/swift-package.cjs +17 -5
  120. package/dist-lib/swift-package.mjs +15 -4
  121. package/dist-lib/swift-submit.cjs +32 -8
  122. package/dist-lib/swift-submit.mjs +29 -7
  123. package/dist-lib/swift.cjs +30912 -577
  124. package/dist-lib/vite-kotlin.cjs +356 -0
  125. package/dist-lib/vite-swift.cjs +1743 -0
  126. package/dist-lib/vite.cjs +208 -1039
  127. package/kotlin/README.md +18 -0
  128. package/kotlin/Sources/androidx/annotation/Keep.kt +4 -0
  129. package/kotlin/Sources/dev/onejs/one/OneNativeSourceDispatch.kt +7 -0
  130. package/kotlin/Sources/dev/onejs/one/source/OneModule.kt +4 -0
  131. package/kotlin/Sources/org/json/JSON.kt +244 -0
  132. package/kotlin/toolchain.json +19 -0
  133. package/package.json +14 -2
  134. package/scripts/dev-server-scanner.ts +0 -18
  135. package/scripts/optional-demo-registry.ts +0 -17
  136. package/skills/contrast/SKILL.md +2 -2
  137. package/skills/peach-setup/SKILL.md +16 -0
  138. package/src/agent-daemon-client.ts +36 -22
  139. package/src/agent-identity.ts +0 -15
  140. package/src/agent-prompt.ts +12 -0
  141. package/src/attached-projects.ts +0 -72
  142. package/src/backend-origin.ts +10 -0
  143. package/src/bridge-contract-input.ts +2 -5
  144. package/src/bridge-contract.ts +2 -0
  145. package/src/capture-contract.ts +53 -11
  146. package/src/cloud.ts +4 -9
  147. package/src/dev-server-open.ts +0 -99
  148. package/src/home-paths.ts +2 -3
  149. package/src/host/agent-host.ts +82 -36
  150. package/src/host/bridge-host.ts +5 -11
  151. package/src/host/fetch-proxy-handler.ts +19 -43
  152. package/src/is-record.ts +4 -0
  153. package/src/menu.ts +82 -37
  154. package/src/metro-fingerprint-registry.ts +1 -4
  155. package/src/metro-production-bundle.ts +1 -1
  156. package/src/plist.ts +1 -4
  157. package/src/public-brand.ts +5 -3
  158. package/src/render-mode.ts +0 -12
  159. package/src/runtime-assets.ts +9 -2
  160. package/src/screenshots/compose.ts +7 -1
  161. package/src/screenshots/frame-compose.ts +21 -10
  162. package/src/screenshots/schema.ts +1 -4
  163. package/src/sim-client.ts +2 -4
  164. package/src/skills/builtin/maestro-test.ts +1 -4
  165. package/src/sleep.ts +4 -0
  166. package/src/swift-compile.ts +209 -47
  167. package/src/swift-icu.ts +140 -0
  168. package/src/swift-package.ts +26 -2
  169. package/src/swift-submit.ts +84 -4
  170. package/src/swift.ts +2 -0
  171. package/src/vite-plugin-kotlin.ts +376 -0
  172. package/src/vite-plugin-one.ts +224 -36
  173. package/src/vite-plugin-swift.ts +382 -96
  174. package/src/vite-plugin.ts +121 -134
  175. package/src/vite.d.ts +10 -0
  176. package/cli/resolve-assets.ts +0 -63
  177. package/src/rolldown-plugin-swift.ts +0 -244
  178. package/src/team-machine-prompt.ts +0 -73
@@ -0,0 +1,18 @@
1
+ # peachsim kotlin
2
+
3
+ The Android side of a One app's directly imported Kotlin in Peach. Owner spec:
4
+ [`specs/rnx-swift.md`](../../../specs/rnx-swift.md) (direct native source imports).
5
+
6
+ - `toolchain.json`: the pinned Kotlin compiler and the kotlinx-coroutines
7
+ Kotlin/Wasm klibs, with their sha256. `peachsim/vite-kotlin` fetches them once
8
+ into `~/.peach/kotlin/<version>`. kotlinc needs a Java runtime (17 or newer)
9
+ from `JAVA_HOME` or `PATH`, the one an Android build uses.
10
+ - `Sources/`: the Android and One declarations app Kotlin and One's generated
11
+ glue compile against: `org.json` with Android's parse and print rules,
12
+ `androidx.annotation.Keep`, `dev.onejs.one.OneNativeSourceDispatch` and
13
+ `dev.onejs.one.source.OneModule`.
14
+
15
+ The dev server compiles every `.kt` source of the app, One's glue for each and
16
+ a generated entry into one Kotlin/Wasm module, held to the Kotlin language
17
+ version React Native's Android build pins. Replay:
18
+ `scripts/debug/peach-one-native-source-guest.sh`.
@@ -0,0 +1,4 @@
1
+ package androidx.annotation
2
+
3
+ // R8 keeps what this marks on device; Peach compiles no shrinker
4
+ annotation class Keep
@@ -0,0 +1,7 @@
1
+ package dev.onejs.one
2
+
3
+ // One's android interface, as packages/one/android declares it
4
+ interface OneNativeSourceDispatch {
5
+ val contractHash: String
6
+ suspend fun call(module: String, method: String, argsJson: String): String
7
+ }
@@ -0,0 +1,4 @@
1
+ package dev.onejs.one.source
2
+
3
+ // One's android marker interface for a directly imported Kotlin module
4
+ interface OneModule
@@ -0,0 +1,244 @@
1
+ package org.json
2
+
3
+ // the part of android's org.json that One's generated glue and app modules
4
+ // use, with android's parse and print rules: an integer literal parses as Int,
5
+ // or Long past Int, anything with a fraction or exponent as Double; a number
6
+ // whose value is integral prints without a fraction; '/' prints escaped.
7
+
8
+ class JSONException(message: String) : RuntimeException(message)
9
+
10
+ private class JSONTokener(private val text: String) {
11
+ private var at = 0
12
+
13
+ private fun skip() {
14
+ while (at < text.length && text[at].isWhitespace()) at++
15
+ }
16
+
17
+ private fun next(): Char {
18
+ if (at >= text.length) throw JSONException("End of input at character $at of $text")
19
+ return text[at++]
20
+ }
21
+
22
+ private fun peek(): Char {
23
+ skip()
24
+ if (at >= text.length) throw JSONException("End of input at character $at of $text")
25
+ return text[at]
26
+ }
27
+
28
+ fun nextValue(): Any = when (peek()) {
29
+ '[' -> { at++; array() }
30
+ '{' -> { at++; objectValue() }
31
+ '"', '\'' -> string(next())
32
+ else -> literal()
33
+ }
34
+
35
+ private fun array(): JSONArray {
36
+ val result = JSONArray()
37
+ if (peek() == ']') { at++; return result }
38
+ while (true) {
39
+ result.put(nextValue())
40
+ when (peek().also { at++ }) {
41
+ ']' -> return result
42
+ ',', ';' -> continue
43
+ else -> throw JSONException("Unterminated array at character $at of $text")
44
+ }
45
+ }
46
+ }
47
+
48
+ private fun objectValue(): JSONObject {
49
+ val result = JSONObject()
50
+ if (peek() == '}') { at++; return result }
51
+ while (true) {
52
+ val name = nextValue() as? String ?: throw JSONException("Names must be strings at character $at of $text")
53
+ if (peek() != ':' && text[at] != '=') throw JSONException("Expected ':' after $name at character $at of $text")
54
+ at++
55
+ if (text.getOrNull(at) == '>') at++
56
+ result.put(name, nextValue())
57
+ when (peek().also { at++ }) {
58
+ '}' -> return result
59
+ ',', ';' -> continue
60
+ else -> throw JSONException("Unterminated object at character $at of $text")
61
+ }
62
+ }
63
+ }
64
+
65
+ private fun string(quote: Char): String {
66
+ val out = StringBuilder()
67
+ while (true) {
68
+ val c = next()
69
+ if (c == quote) return out.toString()
70
+ if (c != '\\') { out.append(c); continue }
71
+ when (val escaped = next()) {
72
+ 'u' -> {
73
+ if (at + 4 > text.length) throw JSONException("Unterminated escape sequence at character $at of $text")
74
+ out.append(text.substring(at, at + 4).toInt(16).toChar())
75
+ at += 4
76
+ }
77
+ 't' -> out.append('\t')
78
+ 'b' -> out.append('\b')
79
+ 'n' -> out.append('\n')
80
+ 'r' -> out.append('\r')
81
+ 'f' -> out.append('\u000c')
82
+ else -> out.append(escaped)
83
+ }
84
+ }
85
+ }
86
+
87
+ private fun literal(): Any {
88
+ val start = at
89
+ while (at < text.length && text[at] !in "\r\n\t ,:#/\\;=[]{}\"'") at++
90
+ val word = text.substring(start, at)
91
+ if (word.isEmpty()) throw JSONException("Expected literal value at character $at of $text")
92
+ when (word.lowercase()) {
93
+ "null" -> return JSONObject.NULL
94
+ "true" -> return true
95
+ "false" -> return false
96
+ }
97
+ if (word.none { it == '.' || it == 'e' || it == 'E' }) {
98
+ val long = word.toLongOrNull()
99
+ if (long != null) return if (long in Int.MIN_VALUE..Int.MAX_VALUE) long.toInt() else long
100
+ }
101
+ return word.toDoubleOrNull() ?: word
102
+ }
103
+ }
104
+
105
+ private fun quote(value: String, out: StringBuilder) {
106
+ out.append('"')
107
+ for (c in value) {
108
+ when (c) {
109
+ '"', '\\', '/' -> out.append('\\').append(c)
110
+ '\t' -> out.append("\\t")
111
+ '\b' -> out.append("\\b")
112
+ '\n' -> out.append("\\n")
113
+ '\r' -> out.append("\\r")
114
+ '\u000c' -> out.append("\\f")
115
+ else -> if (c.code <= 0x1f) out.append("\\u").append(c.code.toString(16).padStart(4, '0')) else out.append(c)
116
+ }
117
+ }
118
+ out.append('"')
119
+ }
120
+
121
+ private fun numberToString(number: Number): String {
122
+ val double = number.toDouble()
123
+ if (double.isNaN() || double.isInfinite()) throw JSONException("Forbidden numeric value: $number")
124
+ if (double == 0.0 && 1.0 / double < 0) return "-0"
125
+ val long = number.toLong()
126
+ if (double == long.toDouble()) return long.toString()
127
+ return number.toString()
128
+ }
129
+
130
+ private fun write(value: Any?, out: StringBuilder) {
131
+ when (value) {
132
+ null, JSONObject.NULL -> out.append("null")
133
+ is String -> quote(value, out)
134
+ is Boolean -> out.append(value)
135
+ is Number -> out.append(numberToString(value))
136
+ is JSONArray -> value.writeTo(out)
137
+ is JSONObject -> value.writeTo(out)
138
+ else -> quote(value.toString(), out)
139
+ }
140
+ }
141
+
142
+ class JSONArray() {
143
+ private val values = ArrayList<Any?>()
144
+
145
+ constructor(json: String) : this() {
146
+ val parsed = JSONTokener(json).nextValue()
147
+ if (parsed !is JSONArray) throw JSONException("Value $json of type ${parsed::class.simpleName} cannot be converted to JSONArray")
148
+ values.addAll(parsed.values)
149
+ }
150
+
151
+ constructor(collection: Collection<*>) : this() {
152
+ for (item in collection) values.add(JSONObject.wrap(item))
153
+ }
154
+
155
+ fun length(): Int = values.size
156
+
157
+ fun opt(index: Int): Any? = values.getOrNull(index)
158
+
159
+ fun get(index: Int): Any {
160
+ if (index < 0 || index >= values.size) throw JSONException("Index $index out of range [0..${values.size})")
161
+ return values[index] ?: throw JSONException("Value at $index is null.")
162
+ }
163
+
164
+ fun put(value: Any?): JSONArray {
165
+ values.add(value)
166
+ return this
167
+ }
168
+
169
+ internal fun writeTo(out: StringBuilder) {
170
+ out.append('[')
171
+ values.forEachIndexed { index, value ->
172
+ if (index > 0) out.append(',')
173
+ write(value, out)
174
+ }
175
+ out.append(']')
176
+ }
177
+
178
+ override fun toString(): String = StringBuilder().also { writeTo(it) }.toString()
179
+ }
180
+
181
+ class JSONObject() {
182
+ private val fields = LinkedHashMap<String, Any?>()
183
+
184
+ constructor(json: String) : this() {
185
+ val parsed = JSONTokener(json).nextValue()
186
+ if (parsed !is JSONObject) throw JSONException("Value $json of type ${parsed::class.simpleName} cannot be converted to JSONObject")
187
+ fields.putAll(parsed.fields)
188
+ }
189
+
190
+ constructor(map: Map<*, *>) : this() {
191
+ for ((key, value) in map) fields[key as? String ?: throw NullPointerException("key == null")] = wrap(value)
192
+ }
193
+
194
+ fun length(): Int = fields.size
195
+
196
+ fun has(name: String): Boolean = fields.containsKey(name)
197
+
198
+ fun opt(name: String): Any? = fields[name]
199
+
200
+ fun get(name: String): Any = fields[name] ?: throw JSONException("No value for $name")
201
+
202
+ fun put(name: String, value: Any?): JSONObject {
203
+ if (value == null) fields.remove(name) else {
204
+ if (value is Number) numberToString(value)
205
+ fields[name] = value
206
+ }
207
+ return this
208
+ }
209
+
210
+ fun keys(): Iterator<String> = fields.keys.iterator()
211
+
212
+ internal fun writeTo(out: StringBuilder) {
213
+ out.append('{')
214
+ var first = true
215
+ for ((key, value) in fields) {
216
+ if (!first) out.append(',')
217
+ first = false
218
+ quote(key, out)
219
+ out.append(':')
220
+ write(value, out)
221
+ }
222
+ out.append('}')
223
+ }
224
+
225
+ override fun toString(): String = StringBuilder().also { writeTo(it) }.toString()
226
+
227
+ companion object {
228
+ val NULL: Any = object {
229
+ override fun equals(other: Any?): Boolean = other == null || other === this
230
+ override fun hashCode(): Int = 0
231
+ override fun toString(): String = "null"
232
+ }
233
+
234
+ fun wrap(value: Any?): Any? = when (value) {
235
+ null -> NULL
236
+ is JSONArray, is JSONObject, NULL -> value
237
+ is Collection<*> -> JSONArray(value)
238
+ is Array<*> -> JSONArray(value.toList())
239
+ is Map<*, *> -> JSONObject(value)
240
+ is Boolean, is Byte, is Char, is Double, is Float, is Int, is Long, is Short, is String -> value
241
+ else -> null
242
+ }
243
+ }
244
+ }
@@ -0,0 +1,19 @@
1
+ {
2
+ "compiler": {
3
+ "version": "2.4.20",
4
+ "url": "https://github.com/JetBrains/kotlin/releases/download/v2.4.20/kotlin-compiler-2.4.20.zip",
5
+ "sha256": "59e9ca74c7904ef2c122b12114937673ccce68de820a663f0ed66ccf8799e0b7"
6
+ },
7
+ "libraries": [
8
+ {
9
+ "name": "kotlinx-coroutines-core-wasm-js-1.11.0.klib",
10
+ "url": "https://repo1.maven.org/maven2/org/jetbrains/kotlinx/kotlinx-coroutines-core-wasm-js/1.11.0/kotlinx-coroutines-core-wasm-js-1.11.0.klib",
11
+ "sha256": "1280b9f74850a4892a3641efa2a5779e5423b84aa7077cc60ddd50decbab921f"
12
+ },
13
+ {
14
+ "name": "atomicfu-wasm-js-0.32.1.klib",
15
+ "url": "https://repo1.maven.org/maven2/org/jetbrains/kotlinx/atomicfu-wasm-js/0.32.1/atomicfu-wasm-js-0.32.1.klib",
16
+ "sha256": "1bdc5a5216944b8f9a396d2f0c7df320b43d6921725baad1afc04cc8e2ae48e3"
17
+ }
18
+ ]
19
+ }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "peachsim",
3
- "version": "0.1.577",
3
+ "version": "0.1.579",
4
4
  "description": "Vite and Metro plugins, testing drivers, and SDK exports for peach.",
5
5
  "author": "Tamagui LLC",
6
6
  "license": "SEE LICENSE IN LICENSE",
@@ -30,7 +30,7 @@
30
30
  "default": "./dist-lib/auth/shared-session.cjs"
31
31
  },
32
32
  "./vite": {
33
- "types": "./src/vite-plugin-one.ts",
33
+ "types": "./src/vite.d.ts",
34
34
  "source": "./src/vite-plugin-one.ts",
35
35
  "default": "./dist-lib/vite.cjs"
36
36
  },
@@ -39,6 +39,16 @@
39
39
  "source": "./src/swift.ts",
40
40
  "default": "./dist-lib/swift.cjs"
41
41
  },
42
+ "./vite-swift": {
43
+ "types": "./src/vite-plugin-swift.ts",
44
+ "source": "./src/vite-plugin-swift.ts",
45
+ "default": "./dist-lib/vite-swift.cjs"
46
+ },
47
+ "./vite-kotlin": {
48
+ "types": "./src/vite-plugin-kotlin.ts",
49
+ "source": "./src/vite-plugin-kotlin.ts",
50
+ "default": "./dist-lib/vite-kotlin.cjs"
51
+ },
42
52
  "./swift-submit": {
43
53
  "types": "./src/swift-submit.ts",
44
54
  "source": "./src/swift-submit.ts",
@@ -260,6 +270,7 @@
260
270
  "dist-lib/",
261
271
  "src/",
262
272
  "cli/",
273
+ "kotlin/",
263
274
  "skills/",
264
275
  "scripts/dev-server-scanner.ts",
265
276
  "scripts/optional-demo-registry.ts",
@@ -283,6 +294,7 @@
283
294
  "oxc-parser": "^0.112.0",
284
295
  "playwright-core": "1.61.0",
285
296
  "sucrase": "3.35.1",
297
+ "svgpath": "2.6.0",
286
298
  "terser": "^5.49.0",
287
299
  "ws": "^8.18.0"
288
300
  },
@@ -707,22 +707,6 @@ function applyManifest(
707
707
  return result
708
708
  }
709
709
 
710
- export function __applyManifestForTests(
711
- result: DiscoveredServer,
712
- manifestBody: string,
713
- buildIconProxyUrl?: (externalUrl: string) => string,
714
- ): DiscoveredServer {
715
- return applyManifest(
716
- result,
717
- {
718
- statusCode: 200,
719
- body: manifestBody,
720
- hostname: new URL(result.bundleUrl).hostname,
721
- },
722
- buildIconProxyUrl,
723
- )
724
- }
725
-
726
710
  // ── name/icon enrichment ────────────────────────────────────────────────────
727
711
  // a server's display name + icon come ONLY from its expo manifest (GET / with
728
712
  // expo-platform), which is far slower than the cheap signature probes: One apps
@@ -1115,8 +1099,6 @@ function isSootSimSelfServer(server: DiscoveredServer): boolean {
1115
1099
  return false
1116
1100
  }
1117
1101
 
1118
- export const __isSootSimSelfServerForTests = isSootSimSelfServer
1119
-
1120
1102
  // per-(port, pid) probe cache. as long as the same process still owns the
1121
1103
  // port, its signature endpoints can't have changed, so we return the cached
1122
1104
  // result without issuing any HTTP. negative results (null) are cached too,
@@ -94,20 +94,3 @@ export async function loadOptionalDemoApps(): Promise<OptionalDemoApp[]> {
94
94
  export function optionalDemoAppsSync(): OptionalDemoApp[] {
95
95
  return cached ?? []
96
96
  }
97
-
98
- /** replace the registry with a fixed list, and restore it on the returned
99
- * handle. what the real registry holds depends on which third-party app
100
- * checkouts and credential files happen to exist on the machine, so a test
101
- * that reads it asserts on the developer's home directory rather than on the
102
- * code. it passes on the box that has them and fails everywhere else. */
103
- export function __withOptionalDemoAppsForTests(apps: OptionalDemoApp[]): {
104
- restore: () => void
105
- } {
106
- const previous = cached
107
- cached = apps
108
- return {
109
- restore: () => {
110
- cached = previous
111
- },
112
- }
113
- }
@@ -9,7 +9,7 @@ description: >-
9
9
 
10
10
  overview of all available skills and CLI commands in this repo.
11
11
 
12
- > this file is auto-generated by `bun scripts/generate-contrast-skill.ts`.
12
+ > this file is auto-generated by `bun scripts/generate/contrast-skill.ts`.
13
13
  > to update, edit the source registries and re-run the generator.
14
14
 
15
15
  ## Peach CLI commands
@@ -50,7 +50,7 @@ automation, inspecting the render tree, and managing sims.
50
50
  | `peach use` | select a connected sim |
51
51
  | `peach skill` | install and inspect bundled peach agent skills |
52
52
  | `peach claim` | take an exclusive lease on a sim |
53
- | `peach agent` | attached projects + local agent sessions (codex/claude) |
53
+ | `peach agent` | inspect messages, attached projects, and local agent sessions |
54
54
  | `peach close` | close a connected sim |
55
55
  | `peach setup` | configure optional local conveniences |
56
56
  | `peach compat` | check package compatibility |
@@ -5,6 +5,22 @@ description: Get Peach connected to your React Native app with no project setup
5
5
 
6
6
  # Peach setup
7
7
 
8
+ ## receive inspect messages
9
+
10
+ after opening and pinning a sim, run this whenever you finish a turn and can
11
+ accept feedback from the simulator:
12
+
13
+ ```sh
14
+ peach agent listen
15
+ ```
16
+
17
+ it returns one pending inspect message immediately, or waits for the next one,
18
+ then exits. handle the request and run it again. the printed context is meant
19
+ to be sufficient for the usual edit. only open the saved `describe.txt` when
20
+ you need more screen context, `inspect.json` for exact inspect data, or the
21
+ screenshot when color, clipping, spacing, or another visual detail matters.
22
+ `peach agent info <message-id>` prints the complete message and artifact paths.
23
+
8
24
  you want to drive a React Native app with Peach from the CLI. this skill
9
25
  takes you from a running development server to a connected, pinned sim that
10
26
  every other Peach skill assumes exists. Peach requires no project setup or per-app
@@ -17,7 +17,11 @@ import { resolveSootsimInvocation, type Provider } from './agent-sessions.ts'
17
17
  import { DEFAULT_SOOTSIM_BRIDGE_PORT } from './bridge-constants.ts'
18
18
  import { shouldSkipPersistentDaemon } from './home-paths.ts'
19
19
  import type { AgentEvent } from './agent-events.ts'
20
- import type { AgentPromptEnvelope } from './agent-prompt.ts'
20
+ import type {
21
+ AgentInspectListenerStatus,
22
+ AgentInspectMessage,
23
+ AgentPromptEnvelope,
24
+ } from './agent-prompt.ts'
21
25
  import type { AgentSession, AttachedProject } from './attached-projects.ts'
22
26
 
23
27
  export interface AgentDaemonClientOptions {
@@ -74,7 +78,7 @@ export class AgentDaemonError extends Error {
74
78
  type PendingEntry = {
75
79
  resolve: (value: unknown) => void
76
80
  reject: (err: Error) => void
77
- timer: ReturnType<typeof setTimeout>
81
+ timer: ReturnType<typeof setTimeout> | null
78
82
  }
79
83
 
80
84
  type EventCallback = (payload: { sessionId: string; event: AgentEvent }) => void
@@ -165,16 +169,23 @@ export class AgentDaemonClient {
165
169
  })
166
170
  }
167
171
 
168
- async sendClaimedPrompt(
172
+ async listenForInspect(simId: string): Promise<AgentInspectMessage> {
173
+ return this.send<AgentInspectMessage>('agent:listen-inspect', { simId })
174
+ }
175
+
176
+ async getInspectListenerStatus(simId: string): Promise<AgentInspectListenerStatus> {
177
+ return this.send<AgentInspectListenerStatus>('agent:get-inspect-listener-status', {
178
+ simId,
179
+ })
180
+ }
181
+
182
+ async sendInspectMessage(
169
183
  simId: string,
170
184
  prompt: AgentPromptEnvelope,
171
- ): Promise<{ ok: true; routed: 'team-machine'; sessionId: string }> {
172
- return this.send<{ ok: true; routed: 'team-machine'; sessionId: string }>(
173
- 'agent:send-claimed-prompt',
174
- {
175
- simId,
176
- prompt,
177
- },
185
+ ): Promise<{ ok: true; queued: boolean; messageId: string }> {
186
+ return this.send<{ ok: true; queued: boolean; messageId: string }>(
187
+ 'agent:send-inspect-message',
188
+ { simId, prompt },
178
189
  )
179
190
  }
180
191
 
@@ -238,15 +249,18 @@ export class AgentDaemonClient {
238
249
  }
239
250
  const id = this.nextId++
240
251
  return new Promise<T>((resolve, reject) => {
241
- const timer = setTimeout(() => {
242
- this.pending.delete(id)
243
- reject(
244
- new AgentDaemonError(
245
- `${type} timed out after ${Math.round(this.commandTimeoutMs / 1000)}s`,
246
- 'TIMEOUT',
247
- ),
248
- )
249
- }, this.commandTimeoutMs)
252
+ const timer =
253
+ this.commandTimeoutMs > 0
254
+ ? setTimeout(() => {
255
+ this.pending.delete(id)
256
+ reject(
257
+ new AgentDaemonError(
258
+ `${type} timed out after ${Math.round(this.commandTimeoutMs / 1000)}s`,
259
+ 'TIMEOUT',
260
+ ),
261
+ )
262
+ }, this.commandTimeoutMs)
263
+ : null
250
264
  this.pending.set(id, {
251
265
  resolve: resolve as (value: unknown) => void,
252
266
  reject,
@@ -255,7 +269,7 @@ export class AgentDaemonClient {
255
269
  try {
256
270
  this.ws.send(JSON.stringify({ id, type, ...payload }))
257
271
  } catch (err) {
258
- clearTimeout(timer)
272
+ if (timer) clearTimeout(timer)
259
273
  this.pending.delete(id)
260
274
  reject(err instanceof Error ? err : new Error(String(err)))
261
275
  }
@@ -293,7 +307,7 @@ export class AgentDaemonClient {
293
307
  const entry = this.pending.get(msg.id)
294
308
  if (!entry) return
295
309
  this.pending.delete(msg.id)
296
- clearTimeout(entry.timer)
310
+ if (entry.timer) clearTimeout(entry.timer)
297
311
  if (msg.error) {
298
312
  entry.reject(new AgentDaemonError(msg.error, msg.code))
299
313
  } else {
@@ -305,7 +319,7 @@ export class AgentDaemonClient {
305
319
  if (this.closed) return
306
320
  this.closed = true
307
321
  for (const [, entry] of this.pending) {
308
- clearTimeout(entry.timer)
322
+ if (entry.timer) clearTimeout(entry.timer)
309
323
  entry.reject(new AgentDaemonError('daemon disconnected', 'DISCONNECT'))
310
324
  }
311
325
  this.pending.clear()
@@ -1,5 +1,3 @@
1
- const TEAM_MACHINE_SESSION_RE = /^[A-Za-z0-9._:-]{1,160}$/
2
-
3
1
  export function agentSessionIdFromCliIdentity(identity: string | null): string | null {
4
2
  if (!identity) return null
5
3
  const colon = identity.indexOf(':')
@@ -8,16 +6,3 @@ export function agentSessionIdFromCliIdentity(identity: string | null): string |
8
6
  const value = identity.slice(colon + 1).trim()
9
7
  return source === 'PEACH_CLI_IDENTITY' && value ? value : null
10
8
  }
11
-
12
- export function teamMachineSessionIdFromCliIdentity(
13
- identity: string | null,
14
- ): string | null {
15
- if (!identity?.startsWith('TM_SESSION:')) return null
16
- const sessionId = identity.slice('TM_SESSION:'.length).trim()
17
- if (!TEAM_MACHINE_SESSION_RE.test(sessionId)) return null
18
- return sessionId
19
- }
20
-
21
- export function isValidTeamMachineSessionId(sessionId: string): boolean {
22
- return TEAM_MACHINE_SESSION_RE.test(sessionId)
23
- }
@@ -5,6 +5,18 @@ export interface AgentPromptEnvelope {
5
5
  inspectTrace?: string
6
6
  }
7
7
 
8
+ export interface AgentInspectMessage {
9
+ id: string
10
+ simId: string
11
+ createdAt: string
12
+ prompt: AgentPromptEnvelope
13
+ }
14
+
15
+ export interface AgentInspectListenerStatus {
16
+ simId: string
17
+ listening: boolean
18
+ }
19
+
8
20
  const ENVELOPE_MARKER = 'sootsim-agent-prompt-v1'
9
21
 
10
22
  function cleanPromptText(value: string | undefined): string {