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,82 @@
1
+ import { rnxExit } from '../run-rnx'
2
+ import { callInBridge, createBridgeFromParsed, parseBridgeCliArgs } from '../ws-bridge'
3
+
4
+ export type ShellBooleanModeAction = 'on' | 'off' | 'toggle'
5
+
6
+ export interface ShellBooleanModeOptions {
7
+ port?: number
8
+ verbose?: boolean
9
+ }
10
+
11
+ export interface ShellBooleanModeConfig {
12
+ modeKey: string
13
+ displayName: string
14
+ actionId: string
15
+ }
16
+
17
+ export interface ShellBooleanModeResult {
18
+ current: boolean
19
+ target: boolean
20
+ changed: boolean
21
+ }
22
+
23
+ export function parseShellBooleanModeAction(args: string[]): ShellBooleanModeAction {
24
+ const action = args[0]?.toLowerCase() ?? 'toggle'
25
+ if (action !== 'on' && action !== 'off' && action !== 'toggle') {
26
+ console.error(` unknown argument: "${action}" (expected on | off | toggle)`)
27
+ rnxExit(1)
28
+ }
29
+ return action
30
+ }
31
+
32
+ export async function runShellBooleanMode(
33
+ args: string[],
34
+ opts: ShellBooleanModeOptions,
35
+ config: ShellBooleanModeConfig,
36
+ ): Promise<ShellBooleanModeResult> {
37
+ const parsed = parseBridgeCliArgs(args, { port: opts.port })
38
+ const action = parseShellBooleanModeAction(parsed.positional)
39
+ const bridge = createBridgeFromParsed({ ...parsed, commandTimeoutMs: 5000 })
40
+ try {
41
+ const settings = await callInBridge<Record<string, unknown>>(
42
+ bridge,
43
+ 'SootSim.bridges.settings.get',
44
+ )
45
+ const current = Boolean(settings?.[config.modeKey])
46
+ const target = action === 'on' ? true : action === 'off' ? false : !current
47
+ if (current === target) {
48
+ console.log(` ${config.displayName}: already ${target ? 'on' : 'off'}`)
49
+ return { current, target, changed: false }
50
+ }
51
+ const changed = await bridge.send({
52
+ type: 'evaluate',
53
+ acquireLock: true,
54
+ code: `(async () => {
55
+ const modeKey = ${JSON.stringify(config.modeKey)}
56
+ const target = ${JSON.stringify(target)}
57
+ window.dispatchEvent(new CustomEvent('sootsim:shell-command', {
58
+ detail: { type: 'fire-action', id: ${JSON.stringify(config.actionId)} },
59
+ }))
60
+ const deadline = Date.now() + 1200
61
+ while (Date.now() < deadline) {
62
+ if (Boolean(window.SootSim?.bridges?.settings?.get?.()?.[modeKey]) === target) {
63
+ return true
64
+ }
65
+ await new Promise((resolve) => setTimeout(resolve, 40))
66
+ }
67
+ return false
68
+ })()`,
69
+ })
70
+ if (!changed) {
71
+ throw new Error(
72
+ `${config.displayName} did not change to ${target ? 'on' : 'off'}; the live shell may not support this mode`,
73
+ )
74
+ }
75
+ console.log(
76
+ ` ${config.displayName}: ${current ? 'on' : 'off'} -> ${target ? 'on' : 'off'}`,
77
+ )
78
+ return { current, target, changed: true }
79
+ } finally {
80
+ bridge.close()
81
+ }
82
+ }
@@ -0,0 +1,468 @@
1
+ // rnx skill - install and inspect agent skill files
2
+ //
3
+ // usage:
4
+ // rnx skill install install all skills to supported agents
5
+ // rnx skill install codex debug install specific skills to Codex only
6
+ // rnx skill show [name] list skills or print one skill
7
+
8
+ import * as fs from 'fs'
9
+ import * as os from 'os'
10
+ import * as path from 'path'
11
+ import { fileURLToPath } from 'url'
12
+ import { rnxPublicBrand } from '../../src/public-brand'
13
+ import { rnxExit } from '../run-rnx'
14
+
15
+ function getSkillsDir(): string {
16
+ try {
17
+ const pkgPath = fileURLToPath(
18
+ import.meta.resolve(`${rnxPublicBrand.packageName}/package.json`),
19
+ )
20
+ return path.join(path.dirname(pkgPath), 'skills')
21
+ } catch {
22
+ const __dirname = path.dirname(fileURLToPath(import.meta.url))
23
+ return path.resolve(__dirname, '../../skills')
24
+ }
25
+ }
26
+
27
+ const SKILLS_DIR = getSkillsDir()
28
+ const AGENT_TARGET_IDS = new Set(['all', 'codex', 'claude'])
29
+
30
+ interface SkillInfo {
31
+ name: string
32
+ description: string
33
+ }
34
+
35
+ interface InstallTarget {
36
+ label: string
37
+ dir: string
38
+ }
39
+
40
+ function printSkillHelp() {
41
+ console.log(`rnx skill
42
+
43
+ install and inspect bundled rnx agent skills.
44
+
45
+ usage:
46
+ rnx skill install [all|codex|claude] [skill-name...]
47
+ rnx skill install --target <dir> [skill-name...]
48
+ rnx skill show [skill-name]
49
+
50
+ commands:
51
+ install install skills; no target means all supported local agents
52
+ show list bundled skills, or print one skill markdown
53
+
54
+ targets:
55
+ all Codex and Claude Code (default)
56
+ codex $CODEX_HOME/skills, or ~/.codex/skills
57
+ claude $CLAUDE_CONFIG_DIR/skills, or ~/.claude/skills
58
+ `)
59
+ }
60
+
61
+ function printInstallHelp() {
62
+ console.log(`rnx skill install
63
+
64
+ install bundled rnx skills in the agent skill layout: <skill>/SKILL.md.
65
+ each skill directory is symlinked back to its source, so edits stay live
66
+ without a re-install. an existing install from an older CLI is upgraded in
67
+ place; an unrelated skill directory is left alone unless --force is passed.
68
+
69
+ usage:
70
+ rnx skill install [all|codex|claude] [skill-name...]
71
+ rnx skill install --target <dir> [skill-name...]
72
+
73
+ options:
74
+ --force, -f replace a conflicting existing skill with the symlink
75
+
76
+ examples:
77
+ rnx skill install
78
+ rnx skill install codex
79
+ rnx skill install codex rnx-debug
80
+ rnx skill install --force rnx-debug
81
+ `)
82
+ }
83
+
84
+ function targetFromHome(envName: string, fallbackDir: string): string {
85
+ const home = process.env[envName] || path.join(os.homedir(), fallbackDir)
86
+ return path.join(home, 'skills')
87
+ }
88
+
89
+ function defaultAgentTargets(): InstallTarget[] {
90
+ return [
91
+ { label: 'codex', dir: targetFromHome('CODEX_HOME', '.codex') },
92
+ { label: 'claude', dir: targetFromHome('CLAUDE_CONFIG_DIR', '.claude') },
93
+ ]
94
+ }
95
+
96
+ function agentTarget(target: string): InstallTarget[] {
97
+ if (target === 'all') return defaultAgentTargets()
98
+ if (target === 'codex') {
99
+ return [{ label: 'codex', dir: targetFromHome('CODEX_HOME', '.codex') }]
100
+ }
101
+ if (target === 'claude') {
102
+ return [{ label: 'claude', dir: targetFromHome('CLAUDE_CONFIG_DIR', '.claude') }]
103
+ }
104
+ console.error(` unknown skill install target: ${target}`)
105
+ console.error(' expected all, codex, or claude')
106
+ rnxExit(1)
107
+ }
108
+
109
+ function parseSkillFrontmatter(
110
+ content: string,
111
+ ): { name: string; description: string } | null {
112
+ const match = content.match(/^---\n([\s\S]*?)\n---/)
113
+ if (!match) return null
114
+ const frontmatter = match[1]
115
+ const name = readFrontmatterField(frontmatter, 'name')
116
+ if (!name) return null
117
+ return {
118
+ name,
119
+ description: readFrontmatterField(frontmatter, 'description') || '',
120
+ }
121
+ }
122
+
123
+ function readFrontmatterField(frontmatter: string, field: string): string | null {
124
+ const lines = frontmatter.split('\n')
125
+ const prefix = `${field}:`
126
+ for (let index = 0; index < lines.length; index++) {
127
+ const line = lines[index]
128
+ if (!line.startsWith(prefix)) continue
129
+
130
+ const rawValue = line.slice(prefix.length).trim()
131
+ if (rawValue === '>-' || rawValue === '>' || rawValue === '|-' || rawValue === '|') {
132
+ const blockLines: string[] = []
133
+ for (let blockIndex = index + 1; blockIndex < lines.length; blockIndex++) {
134
+ const blockLine = lines[blockIndex]
135
+ if (/^[A-Za-z0-9_-]+:\s*/.test(blockLine)) break
136
+ if (blockLine.trim() === '') {
137
+ blockLines.push('')
138
+ continue
139
+ }
140
+ if (!/^\s+/.test(blockLine)) break
141
+ blockLines.push(blockLine.trim())
142
+ }
143
+ return blockLines.join(rawValue.startsWith('|') ? '\n' : ' ').trim()
144
+ }
145
+
146
+ if (
147
+ (rawValue.startsWith('"') && rawValue.endsWith('"')) ||
148
+ (rawValue.startsWith("'") && rawValue.endsWith("'"))
149
+ ) {
150
+ return rawValue.slice(1, -1)
151
+ }
152
+ return rawValue
153
+ }
154
+ return null
155
+ }
156
+
157
+ // bundled skills live in the standard agent layout: skills/<name>/SKILL.md.
158
+ // the directory name is the skill name; frontmatter is the display metadata.
159
+ function listSkills(): SkillInfo[] {
160
+ if (!fs.existsSync(SKILLS_DIR)) return []
161
+ const skills: SkillInfo[] = []
162
+ for (const entry of fs.readdirSync(SKILLS_DIR)) {
163
+ const skillMd = path.join(SKILLS_DIR, entry, 'SKILL.md')
164
+ if (!fs.existsSync(skillMd)) continue
165
+ const meta = parseSkillFrontmatter(fs.readFileSync(skillMd, 'utf8'))
166
+ if (meta) {
167
+ skills.push({ name: entry, description: meta.description })
168
+ }
169
+ }
170
+ return skills.sort((a, b) => a.name.localeCompare(b.name))
171
+ }
172
+
173
+ function matchSkills(skills: SkillInfo[], filters: string[]): SkillInfo[] {
174
+ if (filters.length === 0) return skills
175
+ const matched = skills.filter((skill) =>
176
+ filters.some((filter) => {
177
+ const needle = filter.replace(/^rnx-/, '')
178
+ return (
179
+ skill.name.includes(filter) || skill.name.replace(/^rnx-/, '').includes(needle)
180
+ )
181
+ }),
182
+ )
183
+ if (matched.length === 0) {
184
+ console.error(` no skills matched: ${filters.join(', ')}`)
185
+ console.error(' available: ' + skills.map((skill) => skill.name).join(', '))
186
+ rnxExit(1)
187
+ }
188
+ return matched
189
+ }
190
+
191
+ // install is a SYMLINK of the whole skill DIRECTORY from the agent store back
192
+ // to the source, not a copy, so editing the source skill is live everywhere
193
+ // without re-installing. the directory (not the SKILL.md file) is linked
194
+ // because Codex's skill discovery follows directory symlinks but silently
195
+ // skips a SKILL.md that is itself a file symlink. conflict-safe: skip if
196
+ // already linked to source, upgrade an install from an older CLI (or anything
197
+ // under --force), otherwise refuse so we never clobber a skill the user owns.
198
+ type LinkResult = 'up-to-date' | 'linked' | 'replaced' | 'conflict'
199
+
200
+ function lstatOrNull(p: string): fs.Stats | null {
201
+ try {
202
+ return fs.lstatSync(p)
203
+ } catch {
204
+ return null
205
+ }
206
+ }
207
+
208
+ function symlinkPointsTo(dest: string, src: string): boolean {
209
+ const stat = lstatOrNull(dest)
210
+ if (!stat || !stat.isSymbolicLink()) return false
211
+ try {
212
+ return fs.realpathSync(dest) === fs.realpathSync(src)
213
+ } catch {
214
+ return false
215
+ }
216
+ }
217
+
218
+ // a store entry left by an older CLI is safe to upgrade: either a real dir
219
+ // whose only content is a SKILL.md symlink into an rnx skills source (the
220
+ // pre-dir-layout install — possibly broken after the source moved), or a dir
221
+ // holding a byte-identical copy of the skill. only our installer creates
222
+ // those shapes, so replacing them can't lose user work.
223
+ function isOwnLegacyInstall(stat: fs.Stats, dest: string, srcDir: string): boolean {
224
+ if (stat.isSymbolicLink() || !stat.isDirectory()) return false
225
+ let entries: string[]
226
+ try {
227
+ entries = fs.readdirSync(dest)
228
+ } catch {
229
+ return false
230
+ }
231
+ if (entries.length !== 1 || entries[0] !== 'SKILL.md') return false
232
+ const skillMd = path.join(dest, 'SKILL.md')
233
+ const mdStat = lstatOrNull(skillMd)
234
+ if (!mdStat) return false
235
+ if (mdStat.isSymbolicLink()) {
236
+ try {
237
+ return fs.readlinkSync(skillMd).includes(`${path.sep}skills${path.sep}`)
238
+ } catch {
239
+ return false
240
+ }
241
+ }
242
+ if (!mdStat.isFile()) return false
243
+ try {
244
+ return fs.readFileSync(skillMd).equals(fs.readFileSync(path.join(srcDir, 'SKILL.md')))
245
+ } catch {
246
+ return false
247
+ }
248
+ }
249
+
250
+ function linkSkill(skill: SkillInfo, targetDir: string, force: boolean): LinkResult {
251
+ const src = path.join(SKILLS_DIR, skill.name)
252
+ const dest = path.join(targetDir, skill.name)
253
+ fs.mkdirSync(targetDir, { recursive: true })
254
+
255
+ const existing = lstatOrNull(dest)
256
+ if (!existing) {
257
+ fs.symlinkSync(src, dest)
258
+ return 'linked'
259
+ }
260
+ if (symlinkPointsTo(dest, src)) {
261
+ return 'up-to-date'
262
+ }
263
+ if (force || isOwnLegacyInstall(existing, dest, src)) {
264
+ fs.rmSync(dest, { recursive: true, force: true })
265
+ fs.symlinkSync(src, dest)
266
+ return 'replaced'
267
+ }
268
+ return 'conflict'
269
+ }
270
+
271
+ // quiet, conflict-safe (re)install of all bundled skills into the agent stores
272
+ // that already exist on this machine. wired into the CLI build so building or
273
+ // updating the CLI re-establishes the symlinks idempotently — never forces, so
274
+ // it can't clobber a skill the user customized, and never creates a store for
275
+ // an agent that isn't set up here. silent on success.
276
+ export function refreshInstalledSkills() {
277
+ const skills = listSkills()
278
+ if (skills.length === 0) return
279
+ for (const target of defaultAgentTargets()) {
280
+ const skillsDir = path.resolve(target.dir)
281
+ // only touch agents already present (~/.claude, ~/.codex) — don't seed a
282
+ // store on a CI runner or a machine that doesn't use that agent.
283
+ if (!fs.existsSync(path.dirname(skillsDir))) continue
284
+ try {
285
+ fs.mkdirSync(skillsDir, { recursive: true })
286
+ } catch {
287
+ continue
288
+ }
289
+ try {
290
+ removeLegacyBundledSkills(skillsDir)
291
+ } catch {}
292
+ for (const skill of skills) {
293
+ try {
294
+ linkSkill(skill, skillsDir, false)
295
+ } catch {
296
+ // best-effort: a single bad store must not fail the build
297
+ }
298
+ }
299
+ }
300
+ }
301
+
302
+ const LEGACY_BUNDLED_SKILL_DIRS = [
303
+ 'sootsim-setup',
304
+ 'sootsim-debug',
305
+ 'sootsim-perf',
306
+ 'sootsim-test',
307
+ 'sootsim-visual',
308
+ ]
309
+
310
+ function removeLegacyBundledSkills(targetDir: string) {
311
+ for (const name of LEGACY_BUNDLED_SKILL_DIRS) {
312
+ const dest = path.join(targetDir, name)
313
+ const stat = lstatOrNull(dest)
314
+ if (!stat) continue
315
+ if (
316
+ stat.isSymbolicLink() ||
317
+ isOwnLegacyInstall(stat, dest, path.join(SKILLS_DIR, name))
318
+ ) {
319
+ fs.rmSync(dest, { recursive: true, force: true })
320
+ }
321
+ }
322
+ }
323
+
324
+ function printSkillList(skills: SkillInfo[]) {
325
+ if (skills.length === 0) {
326
+ console.log(' no skills found')
327
+ return
328
+ }
329
+ console.log('\n available skills:\n')
330
+ for (const skill of skills) {
331
+ console.log(` ${skill.name}`)
332
+ console.log(` ${skill.description}\n`)
333
+ }
334
+ console.log(' usage: rnx skill install [all|codex|claude] [skill-name...]')
335
+ console.log(' usage: rnx skill show [skill-name]\n')
336
+ }
337
+
338
+ function parseInstallArgs(args: string[]): {
339
+ targets: InstallTarget[]
340
+ filters: string[]
341
+ force: boolean
342
+ } {
343
+ let targets: InstallTarget[] | null = null
344
+ let force = false
345
+ const filters: string[] = []
346
+
347
+ for (let index = 0; index < args.length; index++) {
348
+ const arg = args[index]
349
+ if (arg === '--help' || arg === '-h') {
350
+ printInstallHelp()
351
+ rnxExit(0)
352
+ }
353
+ if (arg === '--force' || arg === '-f') {
354
+ force = true
355
+ continue
356
+ }
357
+ if (arg === '--target') {
358
+ const dir = args[index + 1]
359
+ if (!dir || dir.startsWith('-')) {
360
+ console.error(' --target requires a directory')
361
+ rnxExit(1)
362
+ }
363
+ if (targets) {
364
+ console.error(' pass only one install target')
365
+ rnxExit(1)
366
+ }
367
+ targets = [{ label: 'target', dir }]
368
+ index++
369
+ continue
370
+ }
371
+ if (arg.startsWith('-')) {
372
+ console.error(` unknown option: ${arg}`)
373
+ printInstallHelp()
374
+ rnxExit(1)
375
+ }
376
+ if (!targets && AGENT_TARGET_IDS.has(arg)) {
377
+ targets = agentTarget(arg)
378
+ continue
379
+ }
380
+ filters.push(arg)
381
+ }
382
+
383
+ return { targets: targets ?? defaultAgentTargets(), filters, force }
384
+ }
385
+
386
+ async function runInstall(args: string[]) {
387
+ const skills = listSkills()
388
+ if (skills.length === 0) {
389
+ console.error(' no skills found in rnx package')
390
+ rnxExit(1)
391
+ }
392
+
393
+ const parsed = parseInstallArgs(args)
394
+ const toInstall = matchSkills(skills, parsed.filters)
395
+ const resolvedTargets = parsed.targets.map((target) => ({
396
+ ...target,
397
+ dir: path.resolve(target.dir),
398
+ }))
399
+ for (const target of resolvedTargets) {
400
+ fs.mkdirSync(target.dir, { recursive: true })
401
+ if (!fs.statSync(target.dir).isDirectory()) {
402
+ console.error(` target is not a directory: ${target.dir}`)
403
+ rnxExit(1)
404
+ }
405
+ removeLegacyBundledSkills(target.dir)
406
+ }
407
+
408
+ console.log(
409
+ `\n installing ${toInstall.length} skill(s) to ${resolvedTargets.length} target(s)\n`,
410
+ )
411
+ let conflicts = 0
412
+ for (const target of resolvedTargets) {
413
+ console.log(` target ${target.label}: ${target.dir}`)
414
+ for (const skill of toInstall) {
415
+ const dest = path.join(target.dir, skill.name)
416
+ const result = linkSkill(skill, target.dir, parsed.force)
417
+ if (result === 'linked') console.log(` linked ${skill.name} -> ${dest}`)
418
+ else if (result === 'replaced') console.log(` relinked ${skill.name} -> ${dest}`)
419
+ else if (result === 'up-to-date') console.log(` up to date ${skill.name}`)
420
+ else {
421
+ conflicts++
422
+ console.log(
423
+ ` conflict ${skill.name}: ${dest} exists and is not this skill — pass --force to replace`,
424
+ )
425
+ }
426
+ }
427
+ console.log('')
428
+ }
429
+ if (conflicts > 0) {
430
+ console.error(
431
+ ` ${conflicts} conflict(s); rerun with --force only if replacing them is intended.\n`,
432
+ )
433
+ rnxExit(1)
434
+ }
435
+ console.log(' done. restart the agent session to load newly installed skills.\n')
436
+ }
437
+
438
+ function runShow(args: string[]) {
439
+ const skills = listSkills()
440
+ const name = args.find((arg) => !arg.startsWith('-'))
441
+ if (!name) {
442
+ printSkillList(skills)
443
+ return
444
+ }
445
+ const [skill] = matchSkills(skills, [name])
446
+ const content = fs.readFileSync(path.join(SKILLS_DIR, skill.name, 'SKILL.md'), 'utf8')
447
+ console.log(content)
448
+ }
449
+
450
+ export async function runSkill(args: string[]) {
451
+ const [command, ...rest] = args
452
+ if (!command || command === '--help' || command === '-h') {
453
+ printSkillHelp()
454
+ return
455
+ }
456
+ if (command === 'install') {
457
+ await runInstall(rest)
458
+ return
459
+ }
460
+ if (command === 'show' || command === 'list') {
461
+ runShow(rest)
462
+ return
463
+ }
464
+
465
+ console.error(` unknown skill command: ${command}`)
466
+ printSkillHelp()
467
+ rnxExit(1)
468
+ }