termcast 1.3.19 → 1.3.24

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 (412) hide show
  1. package/dist/ai.d.ts +104 -0
  2. package/dist/ai.d.ts.map +1 -0
  3. package/dist/ai.js +135 -0
  4. package/dist/ai.js.map +1 -0
  5. package/dist/apis/browser-extension.d.ts +18 -0
  6. package/dist/apis/browser-extension.d.ts.map +1 -0
  7. package/dist/apis/browser-extension.js +14 -0
  8. package/dist/apis/browser-extension.js.map +1 -0
  9. package/dist/apis/localstorage.d.ts.map +1 -1
  10. package/dist/apis/localstorage.js +4 -7
  11. package/dist/apis/localstorage.js.map +1 -1
  12. package/dist/apis/oauth.d.ts.map +1 -1
  13. package/dist/apis/oauth.js +5 -1
  14. package/dist/apis/oauth.js.map +1 -1
  15. package/dist/apis/preferences.d.ts.map +1 -1
  16. package/dist/apis/preferences.js +38 -19
  17. package/dist/apis/preferences.js.map +1 -1
  18. package/dist/build.d.ts.map +1 -1
  19. package/dist/build.js +2 -1
  20. package/dist/build.js.map +1 -1
  21. package/dist/cache.d.ts +32 -0
  22. package/dist/cache.d.ts.map +1 -0
  23. package/dist/cache.js +205 -0
  24. package/dist/cache.js.map +1 -0
  25. package/dist/cli.d.ts.map +1 -1
  26. package/dist/cli.js +69 -33
  27. package/dist/cli.js.map +1 -1
  28. package/dist/clipboard.d.ts +36 -0
  29. package/dist/clipboard.d.ts.map +1 -0
  30. package/dist/clipboard.js +154 -0
  31. package/dist/clipboard.js.map +1 -0
  32. package/dist/compile.d.ts.map +1 -1
  33. package/dist/compile.js +24 -6
  34. package/dist/compile.js.map +1 -1
  35. package/dist/components/actions.d.ts.map +1 -1
  36. package/dist/components/actions.js +56 -30
  37. package/dist/components/actions.js.map +1 -1
  38. package/dist/components/detail.d.ts.map +1 -1
  39. package/dist/components/detail.js +4 -0
  40. package/dist/components/detail.js.map +1 -1
  41. package/dist/components/dropdown.d.ts.map +1 -1
  42. package/dist/components/dropdown.js +38 -15
  43. package/dist/components/dropdown.js.map +1 -1
  44. package/dist/components/extension-preferences.d.ts.map +1 -1
  45. package/dist/components/extension-preferences.js +40 -13
  46. package/dist/components/extension-preferences.js.map +1 -1
  47. package/dist/components/form/checkbox.d.ts.map +1 -1
  48. package/dist/components/form/checkbox.js +5 -3
  49. package/dist/components/form/checkbox.js.map +1 -1
  50. package/dist/components/form/date-picker.d.ts.map +1 -1
  51. package/dist/components/form/date-picker.js +5 -3
  52. package/dist/components/form/date-picker.js.map +1 -1
  53. package/dist/components/form/description.d.ts.map +1 -1
  54. package/dist/components/form/description.js +2 -2
  55. package/dist/components/form/description.js.map +1 -1
  56. package/dist/components/form/dropdown.d.ts.map +1 -1
  57. package/dist/components/form/dropdown.js +84 -80
  58. package/dist/components/form/dropdown.js.map +1 -1
  59. package/dist/components/form/file-autocomplete.d.ts +3 -6
  60. package/dist/components/form/file-autocomplete.d.ts.map +1 -1
  61. package/dist/components/form/file-autocomplete.js +61 -66
  62. package/dist/components/form/file-autocomplete.js.map +1 -1
  63. package/dist/components/form/file-picker.d.ts.map +1 -1
  64. package/dist/components/form/file-picker.js +33 -30
  65. package/dist/components/form/file-picker.js.map +1 -1
  66. package/dist/components/form/form-end.d.ts.map +1 -1
  67. package/dist/components/form/form-end.js +21 -1
  68. package/dist/components/form/form-end.js.map +1 -1
  69. package/dist/components/form/form-type-only.d.ts +174 -0
  70. package/dist/components/form/form-type-only.d.ts.map +1 -0
  71. package/dist/components/form/form-type-only.js +2 -0
  72. package/dist/components/form/form-type-only.js.map +1 -0
  73. package/dist/components/form/index.d.ts +3 -1
  74. package/dist/components/form/index.d.ts.map +1 -1
  75. package/dist/components/form/index.js +100 -28
  76. package/dist/components/form/index.js.map +1 -1
  77. package/dist/components/form/password-field.d.ts.map +1 -1
  78. package/dist/components/form/password-field.js +5 -3
  79. package/dist/components/form/password-field.js.map +1 -1
  80. package/dist/components/form/text-area.d.ts.map +1 -1
  81. package/dist/components/form/text-area.js +5 -3
  82. package/dist/components/form/text-area.js.map +1 -1
  83. package/dist/components/form/text-field.d.ts.map +1 -1
  84. package/dist/components/form/text-field.js +6 -4
  85. package/dist/components/form/text-field.js.map +1 -1
  86. package/dist/components/form/types.d.ts +5 -0
  87. package/dist/components/form/types.d.ts.map +1 -1
  88. package/dist/components/form/use-form-handling.d.ts +4 -0
  89. package/dist/components/form/use-form-handling.d.ts.map +1 -0
  90. package/dist/components/form/use-form-handling.js +37 -0
  91. package/dist/components/form/use-form-handling.js.map +1 -0
  92. package/dist/components/form/with-left-border.d.ts +2 -1
  93. package/dist/components/form/with-left-border.d.ts.map +1 -1
  94. package/dist/components/form/with-left-border.js +27 -3
  95. package/dist/components/form/with-left-border.js.map +1 -1
  96. package/dist/components/icon.d.ts +1 -0
  97. package/dist/components/icon.d.ts.map +1 -1
  98. package/dist/components/icon.js +24 -8
  99. package/dist/components/icon.js.map +1 -1
  100. package/dist/components/list.d.ts +2 -2
  101. package/dist/components/list.d.ts.map +1 -1
  102. package/dist/components/list.js +140 -62
  103. package/dist/components/list.js.map +1 -1
  104. package/dist/components/loading-bar.d.ts.map +1 -1
  105. package/dist/components/loading-bar.js +2 -2
  106. package/dist/components/loading-bar.js.map +1 -1
  107. package/dist/components/loading-text.d.ts +8 -0
  108. package/dist/components/loading-text.d.ts.map +1 -0
  109. package/dist/components/loading-text.js +58 -0
  110. package/dist/components/loading-text.js.map +1 -0
  111. package/dist/descendants.js +1 -1
  112. package/dist/descendants.js.map +1 -1
  113. package/dist/dev-ui.d.ts +7 -0
  114. package/dist/dev-ui.d.ts.map +1 -0
  115. package/dist/dev-ui.js +118 -0
  116. package/dist/dev-ui.js.map +1 -0
  117. package/dist/environment.d.ts +63 -0
  118. package/dist/environment.d.ts.map +1 -0
  119. package/dist/environment.js +189 -0
  120. package/dist/environment.js.map +1 -0
  121. package/dist/examples/datepicker.d.ts +2 -0
  122. package/dist/examples/datepicker.d.ts.map +1 -0
  123. package/dist/examples/datepicker.js +344 -0
  124. package/dist/examples/datepicker.js.map +1 -0
  125. package/dist/examples/file-autocomplete.vitest.d.ts +2 -0
  126. package/dist/examples/file-autocomplete.vitest.d.ts.map +1 -0
  127. package/dist/examples/file-autocomplete.vitest.js +223 -0
  128. package/dist/examples/file-autocomplete.vitest.js.map +1 -0
  129. package/dist/examples/form-basic-arrow-keys.vitest.d.ts +2 -0
  130. package/dist/examples/form-basic-arrow-keys.vitest.d.ts.map +1 -0
  131. package/dist/examples/form-basic-arrow-keys.vitest.js +46 -0
  132. package/dist/examples/form-basic-arrow-keys.vitest.js.map +1 -0
  133. package/dist/examples/form-basic.vitest.d.ts +2 -0
  134. package/dist/examples/form-basic.vitest.d.ts.map +1 -0
  135. package/dist/examples/form-basic.vitest.js +630 -0
  136. package/dist/examples/form-basic.vitest.js.map +1 -0
  137. package/dist/examples/form-dropdown-with-sections.d.ts +2 -0
  138. package/dist/examples/form-dropdown-with-sections.d.ts.map +1 -0
  139. package/dist/examples/form-dropdown-with-sections.js +13 -0
  140. package/dist/examples/form-dropdown-with-sections.js.map +1 -0
  141. package/dist/examples/form-dropdown-with-sections.vitest.d.ts +2 -0
  142. package/dist/examples/form-dropdown-with-sections.vitest.d.ts.map +1 -0
  143. package/dist/examples/form-dropdown-with-sections.vitest.js +75 -0
  144. package/dist/examples/form-dropdown-with-sections.vitest.js.map +1 -0
  145. package/dist/examples/form-dropdown.vitest.d.ts +2 -0
  146. package/dist/examples/form-dropdown.vitest.d.ts.map +1 -0
  147. package/dist/examples/form-dropdown.vitest.js +854 -0
  148. package/dist/examples/form-dropdown.vitest.js.map +1 -0
  149. package/dist/examples/form-multiselect-dropdown.d.ts +2 -0
  150. package/dist/examples/form-multiselect-dropdown.d.ts.map +1 -0
  151. package/dist/examples/form-multiselect-dropdown.js +13 -0
  152. package/dist/examples/form-multiselect-dropdown.js.map +1 -0
  153. package/dist/examples/form-scroll.d.ts.map +1 -1
  154. package/dist/examples/form-scroll.js +7 -1
  155. package/dist/examples/form-scroll.js.map +1 -1
  156. package/dist/examples/form-scroll.vitest.d.ts +2 -0
  157. package/dist/examples/form-scroll.vitest.d.ts.map +1 -0
  158. package/dist/examples/form-scroll.vitest.js +211 -0
  159. package/dist/examples/form-scroll.vitest.js.map +1 -0
  160. package/dist/examples/form-tagpicker.vitest.d.ts +2 -0
  161. package/dist/examples/form-tagpicker.vitest.d.ts.map +1 -0
  162. package/dist/examples/form-tagpicker.vitest.js +736 -0
  163. package/dist/examples/form-tagpicker.vitest.js.map +1 -0
  164. package/dist/examples/internal/descendants-filtering.js +1 -1
  165. package/dist/examples/internal/descendants-filtering.js.map +1 -1
  166. package/dist/examples/internal/descendants.js +1 -1
  167. package/dist/examples/internal/descendants.js.map +1 -1
  168. package/dist/examples/internal/nested-boxes.d.ts +2 -0
  169. package/dist/examples/internal/nested-boxes.d.ts.map +1 -0
  170. package/dist/examples/internal/nested-boxes.js +7 -0
  171. package/dist/examples/internal/nested-boxes.js.map +1 -0
  172. package/dist/examples/internal/rhf-custom-ref.js +2 -2
  173. package/dist/examples/internal/rhf-custom-ref.js.map +1 -1
  174. package/dist/examples/internal/scrollbox-demo.js +3 -22
  175. package/dist/examples/internal/scrollbox-demo.js.map +1 -1
  176. package/dist/examples/internal/scrollbox-descendants.d.ts +2 -0
  177. package/dist/examples/internal/scrollbox-descendants.d.ts.map +1 -0
  178. package/dist/examples/internal/scrollbox-descendants.js +83 -0
  179. package/dist/examples/internal/scrollbox-descendants.js.map +1 -0
  180. package/dist/examples/internal/scrollbox-with-descendants.js +4 -8
  181. package/dist/examples/internal/scrollbox-with-descendants.js.map +1 -1
  182. package/dist/examples/internal/simple-scrollbox.vitest.d.ts +2 -0
  183. package/dist/examples/internal/simple-scrollbox.vitest.d.ts.map +1 -0
  184. package/dist/examples/internal/simple-scrollbox.vitest.js +96 -0
  185. package/dist/examples/internal/simple-scrollbox.vitest.js.map +1 -0
  186. package/dist/examples/internal/unicode-square-repro.d.ts +2 -0
  187. package/dist/examples/internal/unicode-square-repro.d.ts.map +1 -0
  188. package/dist/examples/internal/unicode-square-repro.js +7 -0
  189. package/dist/examples/internal/unicode-square-repro.js.map +1 -0
  190. package/dist/examples/list-detail-metadata.d.ts +2 -0
  191. package/dist/examples/list-detail-metadata.d.ts.map +1 -0
  192. package/dist/examples/list-detail-metadata.js +8 -0
  193. package/dist/examples/list-detail-metadata.js.map +1 -0
  194. package/dist/examples/list-dropdown-default.vitest.d.ts +2 -0
  195. package/dist/examples/list-dropdown-default.vitest.d.ts.map +1 -0
  196. package/dist/examples/list-dropdown-default.vitest.js +234 -0
  197. package/dist/examples/list-dropdown-default.vitest.js.map +1 -0
  198. package/dist/examples/list-fetch-data.vitest.d.ts +2 -0
  199. package/dist/examples/list-fetch-data.vitest.d.ts.map +1 -0
  200. package/dist/examples/list-fetch-data.vitest.js +111 -0
  201. package/dist/examples/list-fetch-data.vitest.js.map +1 -0
  202. package/dist/examples/list-filter-navigation.d.ts +2 -0
  203. package/dist/examples/list-filter-navigation.d.ts.map +1 -0
  204. package/dist/examples/list-filter-navigation.js +8 -0
  205. package/dist/examples/list-filter-navigation.js.map +1 -0
  206. package/dist/examples/list-scrollbox.vitest.d.ts +2 -0
  207. package/dist/examples/list-scrollbox.vitest.d.ts.map +1 -0
  208. package/dist/examples/list-scrollbox.vitest.js +93 -0
  209. package/dist/examples/list-scrollbox.vitest.js.map +1 -0
  210. package/dist/examples/list-with-detail-long.d.ts +2 -0
  211. package/dist/examples/list-with-detail-long.d.ts.map +1 -0
  212. package/dist/examples/list-with-detail-long.js +53 -0
  213. package/dist/examples/list-with-detail-long.js.map +1 -0
  214. package/dist/examples/list-with-detail.vitest.d.ts +2 -0
  215. package/dist/examples/list-with-detail.vitest.d.ts.map +1 -0
  216. package/dist/examples/list-with-detail.vitest.js +434 -0
  217. package/dist/examples/list-with-detail.vitest.js.map +1 -0
  218. package/dist/examples/list-with-dropdown.vitest.d.ts +2 -0
  219. package/dist/examples/list-with-dropdown.vitest.d.ts.map +1 -0
  220. package/dist/examples/list-with-dropdown.vitest.js +337 -0
  221. package/dist/examples/list-with-dropdown.vitest.js.map +1 -0
  222. package/dist/examples/list-with-sections.js +5 -1
  223. package/dist/examples/list-with-sections.js.map +1 -1
  224. package/dist/examples/list-with-sections.vitest.d.ts +2 -0
  225. package/dist/examples/list-with-sections.vitest.d.ts.map +1 -0
  226. package/dist/examples/list-with-sections.vitest.js +601 -0
  227. package/dist/examples/list-with-sections.vitest.js.map +1 -0
  228. package/dist/examples/scrollbox-vertical-centering.d.ts +6 -0
  229. package/dist/examples/scrollbox-vertical-centering.d.ts.map +1 -0
  230. package/dist/examples/scrollbox-vertical-centering.js +17 -0
  231. package/dist/examples/scrollbox-vertical-centering.js.map +1 -0
  232. package/dist/examples/simple-file-picker.vitest.d.ts +2 -0
  233. package/dist/examples/simple-file-picker.vitest.d.ts.map +1 -0
  234. package/dist/examples/simple-file-picker.vitest.js +678 -0
  235. package/dist/examples/simple-file-picker.vitest.js.map +1 -0
  236. package/dist/examples/simple-grid.vitest.d.ts +2 -0
  237. package/dist/examples/simple-grid.vitest.d.ts.map +1 -0
  238. package/dist/examples/simple-grid.vitest.js +521 -0
  239. package/dist/examples/simple-grid.vitest.js.map +1 -0
  240. package/dist/examples/simple-navigation.js +10 -4
  241. package/dist/examples/simple-navigation.js.map +1 -1
  242. package/dist/examples/simple-navigation.vitest.d.ts +2 -0
  243. package/dist/examples/simple-navigation.vitest.d.ts.map +1 -0
  244. package/dist/examples/simple-navigation.vitest.js +718 -0
  245. package/dist/examples/simple-navigation.vitest.js.map +1 -0
  246. package/dist/examples/store.vitest.d.ts +2 -0
  247. package/dist/examples/store.vitest.d.ts.map +1 -0
  248. package/dist/examples/store.vitest.js +69 -0
  249. package/dist/examples/store.vitest.js.map +1 -0
  250. package/dist/extensions/dev.d.ts +4 -2
  251. package/dist/extensions/dev.d.ts.map +1 -1
  252. package/dist/extensions/dev.js +61 -10
  253. package/dist/extensions/dev.js.map +1 -1
  254. package/dist/extensions/dev.vitest.d.ts +2 -0
  255. package/dist/extensions/dev.vitest.d.ts.map +1 -0
  256. package/dist/extensions/dev.vitest.js +197 -0
  257. package/dist/extensions/dev.vitest.js.map +1 -0
  258. package/dist/extensions/home.d.ts.map +1 -1
  259. package/dist/extensions/home.js +3 -0
  260. package/dist/extensions/home.js.map +1 -1
  261. package/dist/home-command.d.ts +8 -0
  262. package/dist/home-command.d.ts.map +1 -0
  263. package/dist/home-command.js +181 -0
  264. package/dist/home-command.js.map +1 -0
  265. package/dist/hover-repro.d.ts +2 -0
  266. package/dist/hover-repro.d.ts.map +1 -0
  267. package/dist/hover-repro.js +20 -0
  268. package/dist/hover-repro.js.map +1 -0
  269. package/dist/index.d.ts +1 -0
  270. package/dist/index.d.ts.map +1 -1
  271. package/dist/index.js +2 -0
  272. package/dist/index.js.map +1 -1
  273. package/dist/internal/dialog.d.ts +1 -0
  274. package/dist/internal/dialog.d.ts.map +1 -1
  275. package/dist/internal/dialog.js +27 -18
  276. package/dist/internal/dialog.js.map +1 -1
  277. package/dist/internal/navigation.d.ts +9 -1
  278. package/dist/internal/navigation.d.ts.map +1 -1
  279. package/dist/internal/navigation.js +5 -5
  280. package/dist/internal/navigation.js.map +1 -1
  281. package/dist/internal/offscreen.d.ts +6 -0
  282. package/dist/internal/offscreen.d.ts.map +1 -0
  283. package/dist/internal/offscreen.js +10 -0
  284. package/dist/internal/offscreen.js.map +1 -0
  285. package/dist/internal/providers.d.ts.map +1 -1
  286. package/dist/internal/providers.js +2 -2
  287. package/dist/internal/providers.js.map +1 -1
  288. package/dist/internal/scrollbox.d.ts +1 -10
  289. package/dist/internal/scrollbox.d.ts.map +1 -1
  290. package/dist/internal/scrollbox.js +2 -1
  291. package/dist/internal/scrollbox.js.map +1 -1
  292. package/dist/localstorage.d.ts +13 -0
  293. package/dist/localstorage.d.ts.map +1 -0
  294. package/dist/localstorage.js +190 -0
  295. package/dist/localstorage.js.map +1 -0
  296. package/dist/oauth.d.ts +142 -0
  297. package/dist/oauth.d.ts.map +1 -0
  298. package/dist/oauth.js +551 -0
  299. package/dist/oauth.js.map +1 -0
  300. package/dist/preferences.d.ts +23 -0
  301. package/dist/preferences.d.ts.map +1 -0
  302. package/dist/preferences.js +105 -0
  303. package/dist/preferences.js.map +1 -0
  304. package/dist/state.d.ts +2 -0
  305. package/dist/state.d.ts.map +1 -1
  306. package/dist/state.js +3 -0
  307. package/dist/state.js.map +1 -1
  308. package/dist/store.d.ts +21 -0
  309. package/dist/store.d.ts.map +1 -0
  310. package/dist/store.js +84 -0
  311. package/dist/store.js.map +1 -0
  312. package/dist/swift-loader.d.ts +3 -0
  313. package/dist/swift-loader.d.ts.map +1 -0
  314. package/dist/swift-loader.js +193 -0
  315. package/dist/swift-loader.js.map +1 -0
  316. package/dist/swift-runtime.d.ts +2 -0
  317. package/dist/swift-runtime.d.ts.map +1 -0
  318. package/dist/swift-runtime.js +27 -0
  319. package/dist/swift-runtime.js.map +1 -0
  320. package/dist/toast.d.ts +44 -0
  321. package/dist/toast.d.ts.map +1 -0
  322. package/dist/toast.js +221 -0
  323. package/dist/toast.js.map +1 -0
  324. package/dist/utils/file-system.d.ts +9 -0
  325. package/dist/utils/file-system.d.ts.map +1 -1
  326. package/dist/utils/file-system.js +49 -0
  327. package/dist/utils/file-system.js.map +1 -1
  328. package/dist/utils/run-command.d.ts +25 -1
  329. package/dist/utils/run-command.d.ts.map +1 -1
  330. package/dist/utils/run-command.js +47 -4
  331. package/dist/utils/run-command.js.map +1 -1
  332. package/dist/window.d.ts +12 -0
  333. package/dist/window.d.ts.map +1 -0
  334. package/dist/window.js +48 -0
  335. package/dist/window.js.map +1 -0
  336. package/package.json +13 -11
  337. package/src/apis/browser-extension.tsx +29 -0
  338. package/src/apis/localstorage.test.ts +14 -6
  339. package/src/apis/localstorage.tsx +8 -5
  340. package/src/apis/oauth.tsx +5 -1
  341. package/src/apis/preferences.tsx +48 -22
  342. package/src/build.test.tsx +52 -0
  343. package/src/build.tsx +2 -1
  344. package/src/cli.tsx +80 -39
  345. package/src/compile.tsx +24 -6
  346. package/src/components/actions.tsx +94 -32
  347. package/src/components/detail.tsx +4 -0
  348. package/src/components/dropdown.tsx +47 -14
  349. package/src/components/extension-preferences.tsx +44 -16
  350. package/src/components/form/checkbox.tsx +12 -6
  351. package/src/components/form/date-picker.tsx +12 -6
  352. package/src/components/form/description.tsx +7 -2
  353. package/src/components/form/dropdown.tsx +131 -119
  354. package/src/components/form/file-autocomplete.tsx +90 -108
  355. package/src/components/form/file-picker.tsx +54 -43
  356. package/src/components/form/form-end.tsx +23 -2
  357. package/src/components/form/index.tsx +152 -34
  358. package/src/components/form/password-field.tsx +12 -6
  359. package/src/components/form/text-area.tsx +13 -6
  360. package/src/components/form/text-field.tsx +13 -6
  361. package/src/components/form/types.tsx +6 -0
  362. package/src/components/form/with-left-border.tsx +41 -8
  363. package/src/components/icon.tsx +27 -8
  364. package/src/components/list.tsx +193 -74
  365. package/src/components/loading-bar.tsx +3 -2
  366. package/src/components/loading-text.tsx +79 -0
  367. package/src/descendants.tsx +1 -0
  368. package/src/examples/file-autocomplete.vitest.tsx +130 -125
  369. package/src/examples/form-basic.vitest.tsx +376 -176
  370. package/src/examples/form-dropdown.vitest.tsx +126 -126
  371. package/src/examples/form-scroll.tsx +2 -0
  372. package/src/examples/form-scroll.vitest.tsx +58 -58
  373. package/src/examples/form-tagpicker.vitest.tsx +99 -99
  374. package/src/examples/internal/descendants-filtering.tsx +1 -0
  375. package/src/examples/internal/descendants.tsx +1 -0
  376. package/src/examples/internal/rhf-custom-ref.tsx +2 -0
  377. package/src/examples/internal/scrollbox-demo.tsx +3 -27
  378. package/src/examples/internal/scrollbox-with-descendants.tsx +4 -7
  379. package/src/examples/internal/simple-scrollbox.vitest.tsx +7 -5
  380. package/src/examples/list-detail-metadata.tsx +49 -0
  381. package/src/examples/list-detail-metadata.vitest.tsx +88 -0
  382. package/src/examples/list-dropdown-default.vitest.tsx +51 -51
  383. package/src/examples/list-fetch-data.vitest.tsx +4 -4
  384. package/src/examples/list-scrollbox.vitest.tsx +73 -14
  385. package/src/examples/list-with-detail-long.tsx +70 -0
  386. package/src/examples/list-with-detail.vitest.tsx +198 -92
  387. package/src/examples/list-with-dropdown.vitest.tsx +53 -53
  388. package/src/examples/list-with-sections.tsx +1 -0
  389. package/src/examples/list-with-sections.vitest.tsx +213 -89
  390. package/src/examples/list-with-toast.vitest.tsx +4 -4
  391. package/src/examples/simple-file-picker.vitest.tsx +61 -471
  392. package/src/examples/simple-grid.vitest.tsx +238 -233
  393. package/src/examples/simple-navigation.tsx +15 -7
  394. package/src/examples/simple-navigation.vitest.tsx +121 -210
  395. package/src/examples/store.vitest.tsx +1 -1
  396. package/src/examples/swift-extension.vitest.tsx +148 -0
  397. package/src/examples/synonyms.vitest.tsx +159 -0
  398. package/src/extensions/dev.tsx +74 -7
  399. package/src/extensions/dev.vitest.tsx +97 -31
  400. package/src/extensions/home.tsx +6 -0
  401. package/src/index.tsx +3 -0
  402. package/src/internal/dialog.tsx +43 -30
  403. package/src/internal/navigation.tsx +3 -1
  404. package/src/internal/offscreen.tsx +15 -0
  405. package/src/internal/providers.tsx +4 -2
  406. package/src/internal/scrollbox.tsx +4 -8
  407. package/src/keyboard.test.tsx +69 -0
  408. package/src/state.tsx +7 -0
  409. package/src/swift-loader.tsx +239 -0
  410. package/src/swift-runtime.tsx +36 -0
  411. package/src/utils/file-system.ts +61 -0
  412. package/src/utils/run-command.tsx +75 -6
@@ -1,4 +1,5 @@
1
1
  import fs from 'node:fs'
2
+ import path from 'node:path'
2
3
  import React from 'react'
3
4
  import { createRoot } from '@opentui/react'
4
5
  import { createCliRenderer } from '@opentui/core'
@@ -17,6 +18,7 @@ import '../globals'
17
18
  interface ExtensionCommand {
18
19
  extensionName: string
19
20
  extensionTitle: string
21
+ extensionDir?: string
20
22
  command: any
21
23
  bundledPath?: string
22
24
  Component?: () => any
@@ -52,6 +54,8 @@ function ExtensionsList({
52
54
  const handleCommandSelect = async (item: ExtensionCommand) => {
53
55
  clearCommandArguments()
54
56
 
57
+
58
+
55
59
  try {
56
60
  await runCommand({
57
61
  command: item.command,
@@ -208,12 +212,14 @@ export default function Home({
208
212
  const packageJson = JSON.parse(
209
213
  fs.readFileSync(extension.packageJsonPath, 'utf-8'),
210
214
  )
215
+ const extensionPath = path.dirname(extension.packageJsonPath)
211
216
 
212
217
  for (const command of extension.commands) {
213
218
  if (command.bundledPath) {
214
219
  allCommands.push({
215
220
  extensionName: extension.name,
216
221
  extensionTitle: packageJson.title || extension.name,
222
+ extensionDir: extensionPath,
217
223
  command,
218
224
  bundledPath: command.bundledPath,
219
225
  packageJson,
package/src/index.tsx CHANGED
@@ -124,6 +124,9 @@ export type {
124
124
  // AI
125
125
  export { AI } from 'termcast/src/apis/ai'
126
126
 
127
+ // Browser Extension
128
+ export { BrowserExtension } from 'termcast/src/apis/browser-extension'
129
+
127
130
  // OAuth
128
131
  export { OAuth } from 'termcast/src/apis/oauth'
129
132
 
@@ -1,5 +1,5 @@
1
1
  import { useKeyboard, useTerminalDimensions } from '@opentui/react'
2
- import React, { type ReactNode, useRef } from 'react'
2
+ import React, { type ReactNode, useRef, useContext } from 'react'
3
3
  import { Theme } from 'termcast/src/theme'
4
4
  import { InFocus, useIsInFocus } from 'termcast/src/internal/focus-context'
5
5
  import { CommonProps } from 'termcast/src/utils'
@@ -10,13 +10,14 @@ import {
10
10
  } from 'termcast/src/state'
11
11
  import { logger } from '../logger'
12
12
  import { ToastOverlay } from 'termcast/src/apis/toast'
13
+ import { NavigationContext } from 'termcast/src/internal/navigation'
13
14
 
14
15
  const Border = {
15
- topLeft: '',
16
- topRight: '',
16
+ topLeft: '',
17
+ topRight: '',
17
18
  bottomLeft: '',
18
19
  bottomRight: '',
19
- horizontal: '',
20
+ horizontal: '',
20
21
  vertical: '┃',
21
22
  topT: '+',
22
23
  bottomT: '+',
@@ -107,7 +108,7 @@ export function Dialog({
107
108
  onMouseDown={handleBackdropClick}
108
109
  >
109
110
  <box
110
- border={false}
111
+ border
111
112
  customBorderChars={Border}
112
113
  width={76}
113
114
  maxWidth={dimensions.width - 2}
@@ -145,31 +146,6 @@ export function DialogProvider(props: DialogProviderProps): any {
145
146
  return (
146
147
  <>
147
148
  <InFocus inFocus={!dialogStack?.length}>{props.children}</InFocus>
148
- {dialogStack.length > 0 && (
149
- <box position='absolute'>
150
- {dialogStack.map((item, index) => {
151
- const isLastItem = index === dialogStack.length - 1
152
- return (
153
- <InFocus key={'dialog' + String(index)} inFocus={isLastItem}>
154
- <Dialog
155
- position={item.position}
156
- onClickOutside={() => {
157
- if (!isLastItem) return
158
- const state = useStore.getState()
159
- if (state.dialogStack.length > 0) {
160
- useStore.setState({
161
- dialogStack: state.dialogStack.slice(0, -1),
162
- })
163
- }
164
- }}
165
- >
166
- {item.element}
167
- </Dialog>
168
- </InFocus>
169
- )
170
- })}
171
- </box>
172
- )}
173
149
  <InFocus inFocus={false}>
174
150
  <ToastOverlay />
175
151
  </InFocus>
@@ -177,6 +153,43 @@ export function DialogProvider(props: DialogProviderProps): any {
177
153
  )
178
154
  }
179
155
 
156
+ export function DialogOverlay(): any {
157
+ const dialogStack = useStore((state) => state.dialogStack)
158
+ const navContext = useContext(NavigationContext)
159
+
160
+ if (dialogStack.length === 0) {
161
+ return null
162
+ }
163
+
164
+ return (
165
+ <box position='absolute'>
166
+ {dialogStack.map((item, index) => {
167
+ const isLastItem = index === dialogStack.length - 1
168
+ return (
169
+ <InFocus key={'dialog' + String(index)} inFocus={isLastItem}>
170
+ <Dialog
171
+ position={item.position}
172
+ onClickOutside={() => {
173
+ if (!isLastItem) return
174
+ const state = useStore.getState()
175
+ if (state.dialogStack.length > 0) {
176
+ useStore.setState({
177
+ dialogStack: state.dialogStack.slice(0, -1),
178
+ })
179
+ }
180
+ }}
181
+ >
182
+ <NavigationContext.Provider value={navContext}>
183
+ {item.element}
184
+ </NavigationContext.Provider>
185
+ </Dialog>
186
+ </InFocus>
187
+ )
188
+ })}
189
+ </box>
190
+ )
191
+ }
192
+
180
193
  export function useDialog() {
181
194
  const dialogStack = useStore((state) => state.dialogStack)
182
195
 
@@ -26,12 +26,13 @@ interface NavigationContextType {
26
26
  isPending: boolean
27
27
  }
28
28
 
29
- const NavigationContext = createContext<NavigationContextType | undefined>(
29
+ export const NavigationContext = createContext<NavigationContextType | undefined>(
30
30
  undefined,
31
31
  )
32
32
 
33
33
  interface NavigationProviderProps extends CommonProps {
34
34
  children: ReactNode
35
+ overlay?: ReactNode
35
36
  }
36
37
 
37
38
  export function NavigationProvider(props: NavigationProviderProps): any {
@@ -139,6 +140,7 @@ export function NavigationProvider(props: NavigationProviderProps): any {
139
140
  {React.cloneElement(currentItem?.element as React.ReactElement, {
140
141
  key: stack.length,
141
142
  })}
143
+ {props.overlay}
142
144
  </NavigationContext.Provider>
143
145
  )
144
146
  }
@@ -0,0 +1,15 @@
1
+ import { createContext, useContext, ReactNode } from 'react'
2
+
3
+ const OffscreenContext = createContext(false)
4
+
5
+ export function useIsOffscreen(): boolean {
6
+ return useContext(OffscreenContext)
7
+ }
8
+
9
+ export function Offscreen({ children }: { children: ReactNode }): any {
10
+ return (
11
+ <OffscreenContext.Provider value={true}>
12
+ {children}
13
+ </OffscreenContext.Provider>
14
+ )
15
+ }
@@ -6,7 +6,7 @@ import React, {
6
6
  } from 'react'
7
7
  import { QueryClient } from '@tanstack/react-query'
8
8
  import { PersistQueryClientProvider } from '@tanstack/react-query-persist-client'
9
- import { DialogProvider } from 'termcast/src/internal/dialog'
9
+ import { DialogProvider, DialogOverlay } from 'termcast/src/internal/dialog'
10
10
  import { NavigationProvider } from 'termcast/src/internal/navigation'
11
11
  import { CommonProps } from 'termcast/src/utils'
12
12
  import { Cache } from 'termcast/src/apis/cache'
@@ -420,7 +420,9 @@ export function TermcastProvider(props: ProvidersProps): any {
420
420
  <DialogProvider>
421
421
  <box padding={2}>
422
422
  {/* NavigationProvider must be last to ensure parent providers remain in the tree when navigation changes */}
423
- <NavigationProvider>{props.children}</NavigationProvider>
423
+ <NavigationProvider overlay={<DialogOverlay />}>
424
+ {props.children}
425
+ </NavigationProvider>
424
426
  </box>
425
427
  </DialogProvider>
426
428
  </PersistQueryClientProvider>
@@ -1,14 +1,8 @@
1
1
  import React from 'react'
2
2
  import Theme from '../theme'
3
+ import { MacOSScrollAccel } from '@opentui/core'
4
+ import { ScrollBoxProps } from '@opentui/react'
3
5
 
4
- interface ScrollBoxProps {
5
- children?: React.ReactNode
6
- focused?: boolean
7
- flexGrow?: number
8
- flexShrink?: number
9
- style?: any
10
- ref?: React.Ref<any>
11
- }
12
6
 
13
7
  export function ScrollBox({
14
8
  children,
@@ -23,6 +17,7 @@ export function ScrollBox({
23
17
  <scrollbox
24
18
  ref={ref}
25
19
  focused={focused}
20
+ scrollAcceleration={new MacOSScrollAccel()}
26
21
  flexGrow={flexGrow}
27
22
  flexShrink={flexShrink}
28
23
  style={{
@@ -34,6 +29,7 @@ export function ScrollBox({
34
29
  flexGrow: 1,
35
30
  flexShrink: 1,
36
31
  paddingRight: 1,
32
+
37
33
  ...(style?.viewportOptions || {}),
38
34
  },
39
35
  contentOptions: {
@@ -0,0 +1,69 @@
1
+ import { describe, test, expect } from 'bun:test'
2
+ import { Keyboard } from 'termcast/src/keyboard'
3
+ import type {
4
+ KeyboardKeyEquivalent,
5
+ KeyboardKeyModifier,
6
+ KeyboardShortcut,
7
+ } from 'termcast/src/keyboard'
8
+
9
+ describe('Keyboard', () => {
10
+ test('Keyboard.Shortcut.Common has all expected shortcuts', () => {
11
+ const commonShortcuts = Object.keys(Keyboard.Shortcut.Common)
12
+ expect(commonShortcuts).toMatchInlineSnapshot(`
13
+ [
14
+ "Copy",
15
+ "CopyDeeplink",
16
+ "CopyName",
17
+ "CopyPath",
18
+ "Save",
19
+ "Duplicate",
20
+ "Edit",
21
+ "MoveDown",
22
+ "MoveUp",
23
+ "New",
24
+ "Open",
25
+ "OpenWith",
26
+ "Pin",
27
+ "Refresh",
28
+ "Remove",
29
+ "RemoveAll",
30
+ "ToggleQuickLook",
31
+ ]
32
+ `)
33
+ })
34
+
35
+ test('Keyboard.Shortcut.Common.Open has correct structure', () => {
36
+ expect(Keyboard.Shortcut.Common.Open).toMatchInlineSnapshot(`
37
+ {
38
+ "key": "o",
39
+ "modifiers": [
40
+ "cmd",
41
+ ],
42
+ }
43
+ `)
44
+ })
45
+
46
+ test('Keyboard.Shortcut.Common.Copy has correct structure', () => {
47
+ expect(Keyboard.Shortcut.Common.Copy).toMatchInlineSnapshot(`
48
+ {
49
+ "key": "c",
50
+ "modifiers": [
51
+ "cmd",
52
+ "shift",
53
+ ],
54
+ }
55
+ `)
56
+ })
57
+
58
+ test('Keyboard.Shortcut.Common.Remove uses ctrl modifier', () => {
59
+ expect(Keyboard.Shortcut.Common.Remove).toMatchInlineSnapshot(`
60
+ {
61
+ "key": "x",
62
+ "modifiers": [
63
+ "ctrl",
64
+ ],
65
+ }
66
+ `)
67
+ })
68
+
69
+ })
package/src/state.tsx CHANGED
@@ -30,6 +30,10 @@ interface AppState {
30
30
  // OAuth state
31
31
  googleAccessToken?: string
32
32
  googleIdToken?: string
33
+ // Actions state - when true, auto-execute first action instead of showing sheet
34
+ shouldAutoExecuteFirstAction: boolean
35
+ // First action title for footer display (set by offscreen ActionPanel)
36
+ firstActionTitle: string
33
37
  }
34
38
 
35
39
  export const useStore = create<AppState>(() => ({
@@ -48,4 +52,7 @@ export const useStore = create<AppState>(() => ({
48
52
  // OAuth state
49
53
  googleAccessToken: undefined,
50
54
  googleIdToken: undefined,
55
+ // Actions state
56
+ shouldAutoExecuteFirstAction: false,
57
+ firstActionTitle: '',
51
58
  }))
@@ -0,0 +1,239 @@
1
+ import fs from 'node:fs'
2
+ import path from 'node:path'
3
+ import { spawn } from 'node:child_process'
4
+ import type { BunPlugin } from 'bun'
5
+ import { logger } from './logger'
6
+
7
+ const SWIFT_NAMESPACE = 'swift-loader'
8
+
9
+ interface SwiftBuildCache {
10
+ [swiftPath: string]: {
11
+ result: SwiftBuildResult
12
+ buildTime: number
13
+ }
14
+ }
15
+
16
+ const buildCache: SwiftBuildCache = {}
17
+
18
+ async function buildSwiftPackage(swiftPath: string): Promise<SwiftBuildResult> {
19
+ logger.log(`Swift: building package at ${swiftPath}`)
20
+
21
+ // Check if already built and cached in memory
22
+ const cached = buildCache[swiftPath]
23
+ if (cached && fs.existsSync(cached.result.binaryPath)) {
24
+ logger.log(`Swift: using cached build at ${cached.result.binaryPath}`)
25
+ return cached.result
26
+ }
27
+
28
+ // Check if binary already exists on disk (from previous build/process)
29
+ try {
30
+ const existingResult = await findSwiftBuildResult(swiftPath)
31
+ logger.log(`Swift: found existing binary at ${existingResult.binaryPath}`)
32
+ buildCache[swiftPath] = { result: existingResult, buildTime: Date.now() }
33
+ return existingResult
34
+ } catch {
35
+ // Binary doesn't exist, need to build
36
+ }
37
+
38
+ // Run swift build using Node's spawn
39
+ // Use debug build for faster compilation (skips optimizations)
40
+ logger.log(`Swift: running swift build -c debug in ${swiftPath}`)
41
+ const exitCode = await new Promise<number>((resolve, reject) => {
42
+ const proc = spawn('swift', ['build', '-c', 'debug'], {
43
+ cwd: swiftPath,
44
+ stdio: 'inherit',
45
+ })
46
+ proc.on('error', reject)
47
+ proc.on('close', (code) => resolve(code ?? 1))
48
+ })
49
+ logger.log(`Swift: build exited with code ${exitCode}`)
50
+
51
+ if (exitCode !== 0) {
52
+ throw new Error(`Swift build failed with exit code ${exitCode}`)
53
+ }
54
+ logger.log(`Swift: build completed successfully`)
55
+
56
+ // Find the binary and generated files
57
+ const result = await findSwiftBuildResult(swiftPath)
58
+
59
+ // Cache the result
60
+ buildCache[swiftPath] = {
61
+ result,
62
+ buildTime: Date.now(),
63
+ }
64
+
65
+ logger.log(`Swift: built binary at ${result.binaryPath}`)
66
+ return result
67
+ }
68
+
69
+ interface SwiftBuildResult {
70
+ binaryPath: string
71
+ raycastJsPath: string
72
+ packageName: string
73
+ }
74
+
75
+ function findFilesRecursively(dir: string, filename: string): string[] {
76
+ const results: string[] = []
77
+
78
+ if (!fs.existsSync(dir)) {
79
+ return results
80
+ }
81
+
82
+ const entries = fs.readdirSync(dir, { withFileTypes: true })
83
+ for (const entry of entries) {
84
+ const fullPath = path.join(dir, entry.name)
85
+ if (entry.isDirectory()) {
86
+ results.push(...findFilesRecursively(fullPath, filename))
87
+ } else if (entry.name === filename) {
88
+ results.push(fullPath)
89
+ }
90
+ }
91
+ return results
92
+ }
93
+
94
+ async function findSwiftBuildResult(swiftPath: string): Promise<SwiftBuildResult> {
95
+ // Parse Package.swift to find executable target name
96
+ const packageSwiftPath = path.join(swiftPath, 'Package.swift')
97
+ const packageSwift = fs.readFileSync(packageSwiftPath, 'utf-8')
98
+
99
+ // Look for: name: "PackageName" at the top level
100
+ const nameMatch = packageSwift.match(/name:\s*"([^"]+)"/)
101
+ if (!nameMatch) {
102
+ throw new Error(`Could not find package name in ${packageSwiftPath}`)
103
+ }
104
+
105
+ const packageName = nameMatch[1]
106
+
107
+ // Find binary - check debug first, then release
108
+ const debugBinaryPath = path.join(swiftPath, '.build', 'debug', packageName)
109
+ const releaseBinaryPath = path.join(swiftPath, '.build', 'release', packageName)
110
+
111
+ let binaryPath: string
112
+ if (fs.existsSync(debugBinaryPath)) {
113
+ binaryPath = debugBinaryPath
114
+ } else if (fs.existsSync(releaseBinaryPath)) {
115
+ binaryPath = releaseBinaryPath
116
+ } else {
117
+ throw new Error(`Swift binary not found at ${debugBinaryPath} or ${releaseBinaryPath}`)
118
+ }
119
+
120
+ // Find raycast.js using glob-like search
121
+ const buildDir = path.join(swiftPath, '.build')
122
+ const raycastJsFiles = findFilesRecursively(buildDir, 'raycast.js')
123
+
124
+ // Filter to find the one from RaycastTypeScriptPlugin for our package
125
+ const matchingFiles = raycastJsFiles.filter(f =>
126
+ f.includes('RaycastTypeScriptPlugin') && f.includes(packageName)
127
+ )
128
+
129
+ if (matchingFiles.length === 0) {
130
+ throw new Error(`Generated raycast.js not found in ${buildDir}. Found files: ${raycastJsFiles.join(', ') || 'none'}`)
131
+ }
132
+
133
+ // Prefer the one in plugins/outputs (not index-build)
134
+ const raycastJsPath = matchingFiles.find(f => f.includes('plugins/outputs')) || matchingFiles[0]
135
+
136
+ logger.log(`Swift: found raycast.js at ${raycastJsPath}`)
137
+ return { binaryPath, raycastJsPath, packageName }
138
+ }
139
+
140
+ function generateSwiftModule(result: SwiftBuildResult): string {
141
+ // Read the generated raycast.js from RaycastTypeScriptPlugin
142
+ // This file exports named functions that call runSwiftFunction(functionName, ...args)
143
+ // We prepend our runSwiftFunction implementation with the binary path
144
+ const generatedJs = fs.readFileSync(result.raycastJsPath, 'utf-8')
145
+
146
+ // Prepend our runSwiftFunction implementation using Node's child_process
147
+ // Use ESM import to match the export statements in generated raycast.js
148
+ const runtime = `
149
+ import { spawn as _spawn } from 'node:child_process';
150
+
151
+ const BINARY_PATH = ${JSON.stringify(result.binaryPath)};
152
+
153
+ function runSwiftFunction(functionName, ...args) {
154
+ return new Promise((resolve, reject) => {
155
+ const jsonArgs = args.map((arg) => JSON.stringify(arg));
156
+ const proc = _spawn(BINARY_PATH, [functionName, ...jsonArgs], {
157
+ stdio: ['pipe', 'pipe', 'inherit'],
158
+ });
159
+
160
+ let stdout = '';
161
+ proc.stdout.on('data', (data) => {
162
+ stdout += data.toString();
163
+ });
164
+
165
+ proc.on('error', (err) => {
166
+ reject(new Error('Swift function "' + functionName + '" failed to spawn: ' + err.message));
167
+ });
168
+
169
+ proc.on('close', (exitCode) => {
170
+ if (exitCode !== 0) {
171
+ reject(new Error('Swift function "' + functionName + '" failed with exit code ' + exitCode));
172
+ return;
173
+ }
174
+
175
+ const trimmed = stdout.trim();
176
+ if (!trimmed) {
177
+ resolve(undefined);
178
+ return;
179
+ }
180
+
181
+ try {
182
+ resolve(JSON.parse(trimmed));
183
+ } catch {
184
+ reject(new Error('Swift function "' + functionName + '" returned invalid JSON: ' + trimmed));
185
+ }
186
+ });
187
+ });
188
+ }
189
+
190
+ `
191
+
192
+ return runtime + generatedJs
193
+ }
194
+
195
+ export const swiftLoaderPlugin: BunPlugin = {
196
+ name: 'swift-loader',
197
+ async setup(build) {
198
+ // Resolve swift: imports
199
+ build.onResolve({ filter: /^swift:/ }, (args) => {
200
+ logger.log(`Swift: onResolve called for ${args.path} from ${args.importer}`)
201
+
202
+ // Extract the path after "swift:"
203
+ const swiftRelativePath = args.path.slice(6) // Remove "swift:"
204
+
205
+ // Resolve relative to the importer's directory
206
+ const importerDir = args.importer ? path.dirname(args.importer) : process.cwd()
207
+ const resolvedSwiftPath = path.resolve(importerDir, swiftRelativePath)
208
+
209
+ logger.log(`Swift: resolved to ${resolvedSwiftPath}`)
210
+
211
+ return {
212
+ path: resolvedSwiftPath,
213
+ namespace: SWIFT_NAMESPACE,
214
+ }
215
+ })
216
+
217
+ // Load swift packages
218
+ build.onLoad({ filter: /.*/, namespace: SWIFT_NAMESPACE }, async (args) => {
219
+ logger.log(`Swift: onLoad called for ${args.path}`)
220
+ const swiftPath = args.path
221
+
222
+ // Build the Swift package and get paths to binary + generated JS
223
+ logger.log(`Swift: calling buildSwiftPackage...`)
224
+ const result = await buildSwiftPackage(swiftPath)
225
+ logger.log(`Swift: buildSwiftPackage returned, binaryPath=${result.binaryPath}`)
226
+
227
+ // Generate module by combining runtime with generated raycast.js
228
+ logger.log(`Swift: generating module...`)
229
+ const contents = generateSwiftModule(result)
230
+ logger.log(`Swift: module generated, length=${contents.length}`)
231
+ // logger.log(`Swift: module contents:\n${contents}`)
232
+
233
+ return {
234
+ contents,
235
+ loader: 'js',
236
+ }
237
+ })
238
+ },
239
+ }
@@ -0,0 +1,36 @@
1
+ import { logger } from './logger'
2
+
3
+ export async function runSwiftFunction(
4
+ binaryPath: string,
5
+ functionName: string,
6
+ ...args: unknown[]
7
+ ): Promise<unknown> {
8
+ const jsonArgs = args.map((arg) => JSON.stringify(arg))
9
+
10
+ logger.log(`Swift: calling ${functionName} with args:`, jsonArgs)
11
+
12
+ const proc = Bun.spawn([binaryPath, functionName, ...jsonArgs], {
13
+ stdout: 'pipe',
14
+ stderr: 'inherit',
15
+ })
16
+
17
+ const [stdout, exitCode] = await Promise.all([
18
+ new Response(proc.stdout).text(),
19
+ proc.exited,
20
+ ])
21
+
22
+ if (exitCode !== 0) {
23
+ throw new Error(`Swift function "${functionName}" failed with exit code ${exitCode}`)
24
+ }
25
+
26
+ const trimmed = stdout.trim()
27
+ if (!trimmed) {
28
+ return undefined
29
+ }
30
+
31
+ try {
32
+ return JSON.parse(trimmed)
33
+ } catch {
34
+ throw new Error(`Swift function "${functionName}" returned invalid JSON: ${trimmed}`)
35
+ }
36
+ }
@@ -8,6 +8,67 @@ export interface FileSystemItem {
8
8
  isDirectory: boolean
9
9
  }
10
10
 
11
+ /**
12
+ * Recursively list all files in a directory (up to maxDepth levels)
13
+ */
14
+ export async function listAllFiles({
15
+ basePath = '.',
16
+ maxDepth = 3,
17
+ maxFiles = 1000,
18
+ includeDirectories = true,
19
+ }: {
20
+ basePath?: string
21
+ maxDepth?: number
22
+ maxFiles?: number
23
+ includeDirectories?: boolean
24
+ } = {}): Promise<string[]> {
25
+ const results: string[] = []
26
+
27
+ // Resolve ~ to home directory
28
+ if (basePath.startsWith('~')) {
29
+ basePath = basePath.replace('~', os.homedir())
30
+ }
31
+
32
+ const resolvedBase = path.isAbsolute(basePath)
33
+ ? basePath
34
+ : path.resolve(process.cwd(), basePath)
35
+
36
+ async function walk(dir: string, depth: number) {
37
+ if (depth > maxDepth || results.length >= maxFiles) return
38
+
39
+ try {
40
+ const entries = await fs.readdir(dir, { withFileTypes: true })
41
+
42
+ for (const entry of entries) {
43
+ if (results.length >= maxFiles) break
44
+
45
+ // Skip hidden files and common ignored directories
46
+ if (entry.name.startsWith('.')) continue
47
+ if (entry.name === 'node_modules') continue
48
+ if (entry.name === 'dist') continue
49
+ if (entry.name === 'build') continue
50
+
51
+ const fullPath = path.join(dir, entry.name)
52
+ const relativePath = path.relative(resolvedBase, fullPath)
53
+
54
+ if (entry.isDirectory()) {
55
+ if (includeDirectories) {
56
+ results.push(relativePath + '/')
57
+ }
58
+ await walk(fullPath, depth + 1)
59
+ } else {
60
+ results.push(relativePath)
61
+ }
62
+ }
63
+ } catch {
64
+ // Ignore permission errors etc
65
+ }
66
+ }
67
+
68
+ await walk(resolvedBase, 0)
69
+ return results.sort()
70
+ }
71
+
11
72
  export async function searchFiles(
12
73
  searchPath: string,
13
74
  prefix: string,