diff --git a/.agents/skills/rozenite b/.agents/skills/rozenite new file mode 120000 index 00000000..c1c002a0 --- /dev/null +++ b/.agents/skills/rozenite @@ -0,0 +1 @@ +../../packages/cli/skills/rozenite \ No newline at end of file diff --git a/.agents/skills/rozenite-agent b/.agents/skills/rozenite-agent deleted file mode 120000 index 9178d46e..00000000 --- a/.agents/skills/rozenite-agent +++ /dev/null @@ -1 +0,0 @@ -../../packages/cli/skills/rozenite-agent \ No newline at end of file diff --git a/.changeset/skills-cli-restructure.md b/.changeset/skills-cli-restructure.md new file mode 100644 index 00000000..f5ac662e --- /dev/null +++ b/.changeset/skills-cli-restructure.md @@ -0,0 +1,5 @@ +--- +'rozenite': minor +--- + +Restructure Rozenite for Agents skills so the CLI bundles all content and the installable skill becomes a thin router. Add `rozenite skills list` and `rozenite skills show ` to list and read the bundled docs (ground truths, CLI workflow, SDK workflow, SDK code patterns, and one doc per agent-enabled domain). The `rozenite-agent` and `rozenite-agent-sdk` skills are replaced by a single `rozenite` skill that discovers docs through `rozenite skills` instead of hardcoding them, so the skill can no longer go stale. diff --git a/docs/react-agent-features/01-get-tree.md b/docs/react-agent-features/01-get-tree.md index 8ee43cef..f065e6f1 100644 --- a/docs/react-agent-features/01-get-tree.md +++ b/docs/react-agent-features/01-get-tree.md @@ -111,7 +111,7 @@ Use `items` instead of `nodes` to match existing paginated Rozenite tool style. 7. Expose `getTree` from `createReactTreeStore`. 8. Register the tool in `createReactDomainService` in `packages/middleware/src/agent/local-domains.ts`. 9. Add `getTree` to `STATIC_DOMAIN_TOOL_NAMES.react` in `packages/agent-sdk/src/constants.ts`. -10. Update `packages/cli/skills/rozenite-agent/domains/react.md`. +10. Update `packages/cli/docs/react.md`. 11. If required by the repository structure, mirror runtime changes under `packages/cli/src/commands/agent/runtime/react/*`. ## Test Plan diff --git a/docs/react-agent-features/README.md b/docs/react-agent-features/README.md index ef349143..0f709408 100644 --- a/docs/react-agent-features/README.md +++ b/docs/react-agent-features/README.md @@ -12,7 +12,7 @@ The goal is not to copy its WebSocket transport. Rozenite already receives React - Rozenite React types: `packages/middleware/src/agent/runtime/react/types.ts` - Rozenite React domain registration: `packages/middleware/src/agent/local-domains.ts` - Static SDK domain tool list: `packages/agent-sdk/src/constants.ts` -- Current CLI skill reference: `packages/cli/skills/rozenite-agent/domains/react.md` +- Current CLI skill doc: `packages/cli/docs/react.md` - Mirrored CLI runtime copy: `packages/cli/src/commands/agent/runtime/react/*` When implementing, treat `packages/middleware` as the canonical runtime location. If the CLI copy is still intentionally maintained, mirror equivalent runtime/type changes there after changing middleware. diff --git a/packages/cli/skills/rozenite-agent/SKILL.md b/packages/cli/docs/cli.md similarity index 52% rename from packages/cli/skills/rozenite-agent/SKILL.md rename to packages/cli/docs/cli.md index b30cf3ce..9a868c42 100644 --- a/packages/cli/skills/rozenite-agent/SKILL.md +++ b/packages/cli/docs/cli.md @@ -1,12 +1,55 @@ --- -name: rozenite-agent -description: Use Rozenite for Agents through CLI-driven `rozenite agent` commands to inspect React Native DevTools data and Rozenite plugins on a live app target. Trigger this skill for shell-based debugging and live session work. For Node.js or TypeScript scripts, wrappers, automations, or other programmatic SDK usage, use `rozenite-agent-sdk` instead. +name: cli +description: Shell-driven `rozenite agent ...` workflow — command syntax, session lifecycle, the listing/pagination output contract, calls, and the end-to-end flow. --- -## CLI +# CLI + +Use this doc for shell-driven `rozenite agent ...` workflows and live +debugging from the terminal. Read the `core` doc first for ground truths +shared with the SDK workflow (built-in domains, plugin domain tokens, and +when live session data beats source code). + +If the task instead needs a Node.js or TypeScript script, wrapper, +automation, or agent runtime built on top of Rozenite, use the `sdk` doc +instead. - Use `npx rozenite` for Rozenite commands. -- Run `npx rozenite` from the app root where Metro is started for the target app. In monorepos, this is usually the app package root, not the repository root. +- Run `npx rozenite` from the app root where Metro is started for the target + app. In monorepos, this is usually the app package root, not the + repository root. + +## Session lifecycle + +- Start with `npx rozenite agent session create`. It creates or reuses the + device session and returns when ready. Stop the session when done with + `npx rozenite agent session stop `. +- If `session create` fails because multiple devices are connected, run + `npx rozenite agent targets`, choose the right `id`, then retry with + `--deviceId `. +- Treat `npx rozenite agent targets` as the source of truth for available + targets. If the expected target is missing, ask the user to run the app on + a device. +- Pass `--session ` on every domain command. +- Treat the Rozenite session ID as a public runtime identifier, not a + secret, credential, or token. + +## Discovering domains and tools + +- Discover domains from the live session with + `npx rozenite agent domains --session ` only if a domain call fails, + the expected domain is unclear, or you need to confirm what is currently + registered. +- Do not call `npx rozenite agent tools` or fetch a tool schema + when the expected tool name and arguments are already known (for example + from a domain doc — run `npx rozenite skills list` to find the doc id for + a domain, then `npx rozenite skills show `). +- Check `npx rozenite agent tools --session ` or + `npx rozenite agent schema --tool --session ` only + when no matching domain doc exists, a domain doc doesn't answer the + question, a call fails, or the live domain exposes behavior that differs + from the docs. +- Skip confirmation or discovery steps that do not add new information. ## Listing output contract @@ -53,43 +96,12 @@ retain their existing shapes. by default. Pass `-f, --fields ` to pick specific columns, or `-v, --verbose` to include every field the tool declares. -## Handoff - -- Keep this skill for shell-driven `rozenite agent ...` workflows. -- If the user wants code instead of shell commands, or asks for a Node.js or TypeScript script, wrapper, benchmark, automation, or agent runtime built on top of Rozenite, switch to `rozenite-agent-sdk`. - -## Rules - -- Agent work is session-scoped. Reuse one session across related commands. -- Always run Rozenite commands in serial. Never issue Rozenite agent commands in parallel. -- Start with `npx rozenite agent session create`. It creates or reuses the device session and returns when ready. Stop the session when done with `npx rozenite agent session stop `. -- If `session create` fails because multiple devices are connected, run `npx rozenite agent targets`, choose the right `id`, then retry with `--deviceId `. -- Treat `npx rozenite agent targets` as the source of truth for available targets. If the expected target is missing, ask the user to run the app on a device. -- Pass `--session ` on every domain command. -- Treat the Rozenite session ID as a public runtime identifier, not a secret, credential, or token. -- Use this skill and its `domains/*.md` references as the source of truth for workflow, tool choice, and tool arguments. -- If this skill or a domain reference already identifies the expected domain, try it directly. If a reference already lists the exact tool and arguments you need, call it directly. -- Discover domains from the live session with `npx rozenite agent domains --session ` only if a domain call fails, the expected domain is unclear, or you need to confirm what is currently registered. -- Do not call `npx rozenite agent tools` or fetch tool schema when this skill or its references already provide the needed tool name and arguments. -- Check `npx rozenite agent tools --session ` or `npx rozenite agent schema --tool --session ` only when no matching reference exists, the references do not answer the question, a call fails, or the live domain exposes behavior that differs from the references. -- Skip confirmation or discovery steps that do not add new information. -- For live app inspection, Rozenite session data is the source of truth. Use the relevant live domain before exploring source code. -- Trust that Rozenite is correctly installed. Do not explore the codebase for setup unless the Rozenite CLI fails. -- Do not explore the codebase to infer live runtime state when Rozenite can answer directly. -- Explore source code only when the user asks about implementation or setup, when no relevant domain is available, or when Rozenite shows the required plugin or domain is not registered and the task becomes setup or debugging. -- If the expected plugin domain is missing from the live session, tell the user that the corresponding plugin must be installed and registered in the app. -- When referring to plugin domains in user-facing output, use the plugin's `pluginId` instead of the domain token. -- When making Rozenite calls against a discovered plugin domain, use the live domain token returned by Rozenite. -- Built-in domains are `console`, `network`, `react`, `performance`, and `memory`. -- Additional domains can appear at runtime from the app or installed plugins. Plugin domain tokens are short, derived names, not the npm package name: `@rozenite/mmkv-plugin` becomes `mmkv`, `@avasapp/rozenite-plugin-ably` becomes `avasapp/ably`. -- Domain token shape tells you provenance: a bare word (`mmkv`) is a built-in or an official `@rozenite/*` plugin; `scope/name` (`avasapp/ably`) is a third-party scoped plugin; a verbatim `rozenite-*` name is a third-party unscoped plugin. `evil/mmkv` and `mmkv` are never the same plugin. - ## Calls - Do not pass domain tool names as direct CLI subcommands. - Always invoke domain tools with `npx rozenite agent call --tool --args '' --session `. - Continue a paginated domain tool call by passing its returned cursor inside `--args`; `--cursor` is only for CLI-owned domain and tool listings. -- If a domain reference lists only tool names, treat them as tool names, not CLI actions. +- If a domain doc lists only tool names, treat them as tool names, not CLI actions. - Example: `npx rozenite agent mmkv call --tool list-storages --args '{}' --session `. - If a command fails with `Unknown domain action`, check the CLI syntax and retry with `call --tool --session `. @@ -98,10 +110,10 @@ retain their existing shapes. 1. Run Rozenite commands one at a time. 2. Use `npx rozenite agent targets` as the source of truth for available targets when device selection matters. If the expected target is missing, ask the user to run the app on a device. 3. Run `npx rozenite agent session create`. -4. If a matching file exists under `domains/*.md`, read it. -5. If the reference already lists the needed tool and arguments for the expected domain, call it directly. +4. Run `npx rozenite skills list` to find a matching domain doc id, then `npx rozenite skills show ` to read it, if one exists for the expected domain. +5. If the doc already lists the needed tool and arguments for the expected domain, call it directly. 6. Run `npx rozenite agent domains --session ` only if the call fails, the expected domain is unclear, or you need to confirm what is currently registered. 7. If the expected plugin domain is missing, tell the user to install and register the corresponding plugin in the app. -8. Check `npx rozenite agent tools --session ` or `npx rozenite agent schema --tool --session ` only if the reference is insufficient, the call fails, or you need to confirm a live mismatch. +8. Check `npx rozenite agent tools --session ` or `npx rozenite agent schema --tool --session ` only if the doc is insufficient, the call fails, or you need to confirm a live mismatch. 9. Fall back to source-code exploration only if no relevant domain exists or the task is about implementation or setup. 10. When no further Rozenite calls are needed, stop the session with `npx rozenite agent session stop `. diff --git a/packages/cli/skills/rozenite-agent/domains/console.md b/packages/cli/docs/console.md similarity index 82% rename from packages/cli/skills/rozenite-agent/domains/console.md rename to packages/cli/docs/console.md index 6b949d58..e2e1da10 100644 --- a/packages/cli/skills/rozenite-agent/domains/console.md +++ b/packages/cli/docs/console.md @@ -1,3 +1,9 @@ +--- +name: console +description: Read, filter, and paginate React Native console messages from the app, and clear the log buffer when needed. +domain: console +--- + Read, filter, and paginate React Native console messages from the app, and clear the log buffer when needed. ## Tools diff --git a/packages/cli/skills/rozenite-agent/domains/controls.md b/packages/cli/docs/controls.md similarity index 88% rename from packages/cli/skills/rozenite-agent/domains/controls.md rename to packages/cli/docs/controls.md index c82f0104..fa32379e 100644 --- a/packages/cli/skills/rozenite-agent/domains/controls.md +++ b/packages/cli/docs/controls.md @@ -1,3 +1,9 @@ +--- +name: controls +description: Read and mutate app-defined debug controls (text, toggle, select, input, button) exposed via the Controls plugin. +domain: controls +--- + # Controls Plugin Domain A Rozenite plugin for exposing app-defined controls in React Native DevTools. You get a custom control panel: read runtime values, flip toggles, switch options, submit text input, and trigger actions (e.g. reset, refetch) without building extra debug screens. Use this domain to list sections and items, read values, and mutate them via `set-value` or `press-button`. diff --git a/packages/cli/docs/core.md b/packages/cli/docs/core.md new file mode 100644 index 00000000..89039a5b --- /dev/null +++ b/packages/cli/docs/core.md @@ -0,0 +1,74 @@ +--- +name: core +description: Ground truths for Rozenite for Agents shared by every workflow — what it is, session and call discipline, built-in and plugin domains, and when live data beats source code. +--- + +# Core + +Rozenite for Agents is the agent-facing way to interact with React Native +DevTools and Rozenite plugins on a running React Native app. It works through +either the `rozenite agent` CLI or the `@rozenite/agent-sdk` package. Read +this doc first, then run `npx rozenite skills show cli` for shell-driven +workflows or `npx rozenite skills show sdk` for programmatic +Node.js/TypeScript workflows. + +Rozenite for Agents requires a project with Rozenite already installed and +configured, Metro running, and at least one React Native target connected. It +does not replace that setup. + +## Session and call discipline + +- Agent work is session-scoped. Create or reuse one session and reuse it + across related commands or calls. +- Always make Rozenite calls in serial. Never issue Rozenite agent commands + or SDK tool calls in parallel. +- Stop the session when the work is done. + +## Built-in domains + +Built-in domains are always available on a live session: `console`, +`network`, `react`, `performance`, and `memory`. + +## Plugin domains + +Additional domains can appear at runtime from the app or from installed +Rozenite plugins. Treat the live session's domain list as the source of +truth for which domains actually exist on the connected target. + +Plugin domain tokens are short, derived names, not the npm package name: +`@rozenite/mmkv-plugin` becomes `mmkv`, `@avasapp/rozenite-plugin-ably` +becomes `avasapp/ably`. + +Domain token shape tells you provenance: + +- A bare word (`mmkv`) is a built-in domain or an official `@rozenite/*` + plugin. +- `scope/name` (`avasapp/ably`) is a third-party scoped plugin. +- A verbatim `rozenite-*` name is a third-party unscoped plugin. + +`evil/mmkv` and `mmkv` are never the same plugin — do not treat a +similarly-named token as equivalent to a known-good one. + +When referring to plugin domains in user-facing output, use the plugin's +`pluginId` (for example `@rozenite/storage-plugin`) instead of the derived +domain token. + +If the expected plugin domain is missing from the live session, tell the +user that the corresponding plugin must be installed and registered in the +app. Do not guess at a substitute domain. + +## Live data is the source of truth + +For live app inspection, Rozenite session data is the source of truth. Use +the relevant live domain before exploring source code to infer runtime +state — a component tree, a storage entry, or a navigation state read from +source code can be stale or simply wrong compared to what's actually +running. + +Explore source code only when the user asks about implementation or setup, +when no relevant domain is available, or when Rozenite shows that the +required plugin or domain is not registered and the task becomes setup or +debugging rather than inspection. + +Trust that Rozenite is correctly installed. Do not explore the codebase for +setup unless the Rozenite CLI or SDK call actually fails. diff --git a/packages/cli/skills/rozenite-agent/domains/file-system.md b/packages/cli/docs/file-system.md similarity index 85% rename from packages/cli/skills/rozenite-agent/domains/file-system.md rename to packages/cli/docs/file-system.md index b634c4e5..c86ec781 100644 --- a/packages/cli/skills/rozenite-agent/domains/file-system.md +++ b/packages/cli/docs/file-system.md @@ -1,3 +1,9 @@ +--- +name: file-system +description: Browse app files and preview file contents read-only via Expo FileSystem or an RNFS-compatible provider. +domain: file-system +--- + # File System Plugin Domain A Rozenite plugin for browsing app files and previewing file contents in React Native DevTools. It provides read-only filesystem inspection through either Expo FileSystem or an RNFS-compatible provider. Use it to discover roots, inspect directories, and preview files without mutating app data. diff --git a/packages/cli/skills/rozenite-agent/domains/memory.md b/packages/cli/docs/memory.md similarity index 76% rename from packages/cli/skills/rozenite-agent/domains/memory.md rename to packages/cli/docs/memory.md index 5ccb4886..4fc57249 100644 --- a/packages/cli/skills/rozenite-agent/domains/memory.md +++ b/packages/cli/docs/memory.md @@ -1,3 +1,9 @@ +--- +name: memory +description: Capture heap snapshots or run allocation sampling over a reproduction, with artifacts written by Metro for offline analysis. +domain: memory +--- + Capture one-off heap snapshots or run allocation sampling over a reproduction. Metro writes artifacts under `.rozenite/agent/sessions//memory` and `.rozenite/agent/sessions//profiles` for offline analysis. ## Tools diff --git a/packages/cli/skills/rozenite-agent/domains/mmkv.md b/packages/cli/docs/mmkv.md similarity index 89% rename from packages/cli/skills/rozenite-agent/domains/mmkv.md rename to packages/cli/docs/mmkv.md index 1ef0b108..078a4d6b 100644 --- a/packages/cli/skills/rozenite-agent/domains/mmkv.md +++ b/packages/cli/docs/mmkv.md @@ -1,3 +1,9 @@ +--- +name: mmkv +description: Inspect and manage react-native-mmkv storages and entries in real time via the MMKV plugin. +domain: mmkv +--- + # MMKV Plugin Domain A Rozenite plugin for MMKV storage inspection in React Native DevTools. It provides real-time inspection of MMKV instances, data visualization with type detection (string, number, boolean, buffer), and management: list storages and entries, read/create/edit/remove entries. Use it when the app uses `react-native-mmkv` and exposes instances via `useMMKVDevTools`. diff --git a/packages/cli/skills/rozenite-agent/domains/network-activity.md b/packages/cli/docs/network-activity.md similarity index 90% rename from packages/cli/skills/rozenite-agent/domains/network-activity.md rename to packages/cli/docs/network-activity.md index db3afa6a..9a4f29ec 100644 --- a/packages/cli/skills/rozenite-agent/domains/network-activity.md +++ b/packages/cli/docs/network-activity.md @@ -1,3 +1,9 @@ +--- +name: network-activity +description: Fallback HTTP, WebSocket, and SSE network inspection via the Network Activity plugin when the built-in network domain is unavailable. +domain: network-activity +--- + # Network Activity Plugin Domain A Rozenite plugin for fallback network inspection when the built-in `network` domain is unavailable, such as on older React Native versions. It mirrors the built-in `network` API for HTTP request recording and inspection, and adds plugin-specific tools for WebSocket and SSE traffic. diff --git a/packages/cli/skills/rozenite-agent/domains/network.md b/packages/cli/docs/network.md similarity index 89% rename from packages/cli/skills/rozenite-agent/domains/network.md rename to packages/cli/docs/network.md index 0a766d02..47148888 100644 --- a/packages/cli/skills/rozenite-agent/domains/network.md +++ b/packages/cli/docs/network.md @@ -1,3 +1,9 @@ +--- +name: network +description: Record HTTP/HTTPS traffic, then list requests, inspect request/response details and bodies, and analyze timing. +domain: network +--- + Record HTTP/HTTPS traffic, then list requests, inspect request and response details and bodies, and analyze timing, similar to the browser DevTools Network panel. ## Precedence diff --git a/packages/cli/skills/rozenite-agent/domains/performance.md b/packages/cli/docs/performance.md similarity index 74% rename from packages/cli/skills/rozenite-agent/domains/performance.md rename to packages/cli/docs/performance.md index a434ad33..1ec8d6e1 100644 --- a/packages/cli/skills/rozenite-agent/domains/performance.md +++ b/packages/cli/docs/performance.md @@ -1,3 +1,9 @@ +--- +name: performance +description: Start and stop a performance trace on the session target, exporting a Metro-managed trace artifact for offline analysis. +domain: performance +--- + Start a performance trace on the session target, reproduce the issue while recording, then stop and export the trace to a Metro-managed artifact under `.rozenite/agent/sessions//traces`. Calls return only artifact metadata. ## Tools diff --git a/packages/cli/skills/rozenite-agent/domains/react-navigation.md b/packages/cli/docs/react-navigation.md similarity index 84% rename from packages/cli/skills/rozenite-agent/domains/react-navigation.md rename to packages/cli/docs/react-navigation.md index 4f5c21fe..68405c40 100644 --- a/packages/cli/skills/rozenite-agent/domains/react-navigation.md +++ b/packages/cli/docs/react-navigation.md @@ -1,3 +1,9 @@ +--- +name: react-navigation +description: Inspect React Navigation state, action history, and dispatch navigation actions or deep links via the React Navigation plugin. +domain: react-navigation +--- + # React Navigation Plugin Domain A Rozenite plugin for React Navigation debugging and inspection in React Native DevTools. It provides real-time navigation state monitoring, action timeline inspection, and deep-link testing (React Navigation v7). Use this domain to read focused route and root state, navigate by route name, go back, open deep links, or dispatch low-level actions when needed. diff --git a/packages/cli/skills/rozenite-agent/domains/react.md b/packages/cli/docs/react.md similarity index 93% rename from packages/cli/skills/rozenite-agent/domains/react.md rename to packages/cli/docs/react.md index 148884b0..6a137a80 100644 --- a/packages/cli/skills/rozenite-agent/domains/react.md +++ b/packages/cli/docs/react.md @@ -1,3 +1,9 @@ +--- +name: react +description: Search and traverse the React component tree, read props/state/hooks, and record render timelines via profiling. +domain: react +--- + Search and traverse the React component tree, read props, state, and hooks for any node, and record render timelines for performance analysis by starting and stopping profiling, then fetching commit data. ## Tools diff --git a/packages/cli/skills/rozenite-agent/domains/redux-devtools.md b/packages/cli/docs/redux-devtools.md similarity index 91% rename from packages/cli/skills/rozenite-agent/domains/redux-devtools.md rename to packages/cli/docs/redux-devtools.md index db24974c..88e44159 100644 --- a/packages/cli/skills/rozenite-agent/domains/redux-devtools.md +++ b/packages/cli/docs/redux-devtools.md @@ -1,3 +1,9 @@ +--- +name: redux-devtools +description: Inspect Redux stores and action history, dispatch actions, and control curated DevTools history operations. +domain: redux-devtools +--- + # Redux DevTools Plugin Domain A Rozenite plugin for Redux state inspection and curated history control in React Native DevTools. It exposes store discovery, current state reads, action-history inspection, normal Redux action dispatch, and safe Redux DevTools history operations such as jump, toggle, reset, rollback, commit, sweep, pause, and lock. diff --git a/packages/cli/skills/rozenite-agent-sdk/references/code-patterns.md b/packages/cli/docs/sdk-patterns.md similarity index 93% rename from packages/cli/skills/rozenite-agent-sdk/references/code-patterns.md rename to packages/cli/docs/sdk-patterns.md index ceca90e2..678b04f3 100644 --- a/packages/cli/skills/rozenite-agent-sdk/references/code-patterns.md +++ b/packages/cli/docs/sdk-patterns.md @@ -1,4 +1,9 @@ -# Code Patterns +--- +name: sdk-patterns +description: Copy-pastable @rozenite/agent-sdk code patterns for session lifecycle, typed and dynamic tool calls, pagination, target selection, and manual session control. +--- + +# SDK Patterns Use these patterns as starting points for SDK-based Rozenite agent work. @@ -199,7 +204,10 @@ const result = await client.withSession( ); ``` -When more than one simulator, emulator, or device may be connected, use the `rozenite-agent` skill to enumerate and choose the live target first. Then pass the chosen `deviceId` into the SDK flow instead of duplicating target-discovery logic here. +When more than one simulator, emulator, or device may be connected, run +`npx rozenite agent targets` to enumerate and choose the live target first +(see the `cli` doc). Then pass the chosen `deviceId` into the SDK flow +instead of duplicating target-discovery logic here. ## Advanced: Manual Session Lifecycle diff --git a/packages/cli/skills/rozenite-agent-sdk/SKILL.md b/packages/cli/docs/sdk.md similarity index 69% rename from packages/cli/skills/rozenite-agent-sdk/SKILL.md rename to packages/cli/docs/sdk.md index 4d9d27a1..99ba0753 100644 --- a/packages/cli/skills/rozenite-agent-sdk/SKILL.md +++ b/packages/cli/docs/sdk.md @@ -1,13 +1,21 @@ --- -name: rozenite-agent-sdk -description: Use Rozenite for Agents through `@rozenite/agent-sdk` in Node.js or TypeScript code. Trigger this skill when Codex needs to write or run scripts, wrappers, automations, benchmarks, or agent runtimes that call Rozenite programmatically instead of driving the `rozenite agent` CLI directly. +name: sdk +description: Programmatic `@rozenite/agent-sdk` usage in Node.js/TypeScript — session lifecycle, tool discovery, typed plugin calls, and pagination. --- -# Rozenite Agent SDK +# SDK -Use this skill when the user wants code-first access to Rozenite for Agents. +Use this doc for programmatic access to Rozenite for Agents through +`@rozenite/agent-sdk` in Node.js or TypeScript code. Read the `core` doc +first for ground truths shared with the CLI workflow (built-in domains, +plugin domain tokens, and when live session data beats source code). -Read `references/code-patterns.md` for copy-pastable examples. +If the task is shell-driven, needs a reusable CLI session, operates directly +through `rozenite agent ...`, or requires target enumeration before choosing +a `deviceId`, use the `cli` doc instead. + +Read `sdk-patterns` for copy-pastable code examples covering everything +below. ## Rules @@ -21,9 +29,5 @@ Read `references/code-patterns.md` for copy-pastable examples. - Prefer stable SDK domain identifiers such as built-in domain IDs (`network`, `react`, `memory`) and plugin IDs (`@rozenite/storage-plugin`, `@rozenite/tanstack-query-plugin`) over the derived, CLI-facing domain token like `storage`. - For paged tools, make one call at a time and pass the returned `page.nextCursor` explicitly in the next call. The plugin owns its page envelope and cursor. - If a plugin only mounts after navigation, navigate first, then refresh the live view with `session.domains.list()` or `session.tools.list(...)` before calling the plugin tool. -- For advanced session control with `client.openSession()` or `client.attachSession(sessionId)`, see the reference patterns. +- For advanced session control with `client.openSession()` or `client.attachSession(sessionId)`, see `sdk-patterns`. - If a script encounters an unexpected runtime error, let the script fail clearly. Do not hide the failure by printing placeholder JSON. - -## Handoff - -- Use `rozenite-agent` instead when the task is shell-driven, needs a reusable CLI session, operates directly through `rozenite agent ...`, or requires target enumeration before choosing a `deviceId`. diff --git a/packages/cli/skills/rozenite-agent/domains/storage.md b/packages/cli/docs/storage.md similarity index 88% rename from packages/cli/skills/rozenite-agent/domains/storage.md rename to packages/cli/docs/storage.md index 757cebf6..172ed5ff 100644 --- a/packages/cli/skills/rozenite-agent/domains/storage.md +++ b/packages/cli/docs/storage.md @@ -1,3 +1,9 @@ +--- +name: storage +description: Inspect and mutate entries across multiple storage backends (MMKV, AsyncStorage, Expo SecureStore) via adapters. +domain: storage +--- + # Storage Plugin Domain A Rozenite plugin for inspecting multiple storage backends in React Native DevTools. It provides a single inspector for sync and async storages, including MMKV, AsyncStorage, and Expo SecureStore via adapters. Use it for storage inspection and entry mutation across supported adapters. diff --git a/packages/cli/skills/rozenite-agent/domains/tanstack-query.md b/packages/cli/docs/tanstack-query.md similarity index 90% rename from packages/cli/skills/rozenite-agent/domains/tanstack-query.md rename to packages/cli/docs/tanstack-query.md index 07c96a43..6cb25090 100644 --- a/packages/cli/skills/rozenite-agent/domains/tanstack-query.md +++ b/packages/cli/docs/tanstack-query.md @@ -1,3 +1,9 @@ +--- +name: tanstack-query +description: Inspect and manage TanStack Query caches — queries, mutations, refetch, invalidate, and cache clearing. +domain: tanstack-query +--- + # TanStack Query Plugin Domain A Rozenite plugin for inspecting and managing TanStack Query caches in React Native DevTools. Use it to inspect query and mutation state, paginate large caches, refetch or invalidate specific queries, and clear caches when needed. diff --git a/packages/cli/package.json b/packages/cli/package.json index ad58a5ce..7da885f5 100644 --- a/packages/cli/package.json +++ b/packages/cli/package.json @@ -24,6 +24,7 @@ }, "files": [ "dist", + "docs", "skills", "template", "bin.js" diff --git a/packages/cli/skills/rozenite/SKILL.md b/packages/cli/skills/rozenite/SKILL.md new file mode 100644 index 00000000..00cbb320 --- /dev/null +++ b/packages/cli/skills/rozenite/SKILL.md @@ -0,0 +1,18 @@ +--- +name: rozenite +description: Use Rozenite for Agents to inspect React Native DevTools data and Rozenite plugins on a live app target, either through shell-driven `rozenite agent` commands or programmatic `@rozenite/agent-sdk` usage. Trigger this skill for any task that needs live app inspection, debugging, or plugin interaction through Rozenite. +--- + +## Rozenite + +Rozenite for Agents ships its real content as CLI-bundled docs instead of +inside this skill, so it can never go stale here. + +- Run `npx rozenite skills list` to discover the available docs (id and + one-line description for each). +- Run `npx rozenite skills show ` to read one. +- Start with `npx rozenite skills show core` — it has the ground truths every + workflow shares. +- Run these commands, and any other `npx rozenite` command, from the app + root where Metro is started for the target app. In a monorepo, that is the + app package root, not the repository root. diff --git a/packages/cli/src/__tests__/skills-registry.test.ts b/packages/cli/src/__tests__/skills-registry.test.ts new file mode 100644 index 00000000..a56d805f --- /dev/null +++ b/packages/cli/src/__tests__/skills-registry.test.ts @@ -0,0 +1,132 @@ +import { describe, expect, it } from 'vitest'; +import { SkillsRegistry } from '../skills/registry.js'; + +const NON_DOMAIN_IDS = ['core', 'cli', 'sdk', 'sdk-patterns']; + +const DOMAIN_IDS = [ + 'storage', + 'mmkv', + 'console', + 'network', + 'network-activity', + 'react', + 'react-navigation', + 'redux-devtools', + 'performance', + 'memory', + 'file-system', + 'controls', + 'tanstack-query', +]; + +describe('SkillsRegistry', () => { + it('lists every expected doc id', () => { + const registry = new SkillsRegistry(); + const ids = registry.list().map((doc) => doc.id); + + for (const id of [...NON_DOMAIN_IDS, ...DOMAIN_IDS]) { + expect(ids).toContain(id); + } + expect(ids).toHaveLength(NON_DOMAIN_IDS.length + DOMAIN_IDS.length); + }); + + it('gives every doc a non-empty description', () => { + const registry = new SkillsRegistry(); + + for (const doc of registry.list()) { + expect(doc.description.length).toBeGreaterThan(0); + } + }); + + it('sets the domain field for every domain doc, and only those', () => { + const registry = new SkillsRegistry(); + + for (const id of DOMAIN_IDS) { + const doc = registry.get(id); + expect(doc?.domain).toBe(id); + } + + for (const id of NON_DOMAIN_IDS) { + const doc = registry.get(id); + expect(doc?.domain).toBeUndefined(); + } + }); + + it('resolves domain docs by domain token', () => { + const registry = new SkillsRegistry(); + + for (const id of DOMAIN_IDS) { + expect(registry.getByDomain(id)?.id).toBe(id); + } + }); + + it('returns undefined for an unknown id or domain', () => { + const registry = new SkillsRegistry(); + + expect(registry.get('does-not-exist')).toBeUndefined(); + expect(registry.getByDomain('does-not-exist')).toBeUndefined(); + }); + + // Structural markers that make a slash-adjacent mention a location + // reference rather than prose. Bare `./` or `../` are deliberately + // excluded: they show up constantly in legitimate, non-doc-referencing + // content here, such as the `sdk` doc's plugin `./sdk` export subpath. + // `core` also legitimately uses bare `scope/name`-shaped examples (e.g. + // "evil/mmkv", "avasapp/ably") to illustrate plugin domain token + // provenance; those aren't location references either. A `docs/`, + // `domains/`, or `references/` directory marker in front of (or a ".md" + // right after) a known doc id is what actually distinguishes a real + // location reference like "domains/storage" or "./docs/core" from that + // legitimate prose. + const PATH_MARKERS = ['docs/', 'domains/', 'references/']; + + const isPathReference = (body: string, id: string): boolean => { + if (new RegExp(`${id}\\.md`, 'i').test(body)) { + return true; + } + + return PATH_MARKERS.some((marker) => { + const escapedMarker = marker.replace(/[.*+?^${}()|[\]\\]/g, '\\$&'); + return new RegExp(`${escapedMarker}${id}(?:[^a-z0-9-]|$)`, 'i').test(body); + }); + }; + + it('flags marker-adjacent doc ids but not bare scope/name prose', () => { + // Guards the `isPathReference` helper itself against the exact shapes + // called out for this invariant, independent of current doc content. + expect(isPathReference('see domains/storage.md for tools', 'storage')).toBe(true); + expect(isPathReference('read ./docs/core first', 'core')).toBe(true); + expect(isPathReference('read references/core.md', 'core')).toBe(true); + expect(isPathReference('mmkv.md has the tools', 'mmkv')).toBe(true); + + expect(isPathReference('`evil/mmkv` and `mmkv` are never the same plugin', 'mmkv')).toBe(false); + expect(isPathReference('`avasapp/ably` is a third-party scoped plugin', 'ably')).toBe(false); + expect(isPathReference('run `npx rozenite skills show core`', 'core')).toBe(false); + expect(isPathReference('call the plugin `./sdk` export', 'sdk')).toBe(false); + }); + + it('contains no cross-references between docs', () => { + const registry = new SkillsRegistry(); + const ids = new Set(registry.list().map((doc) => doc.id)); + + for (const doc of registry.list()) { + // No doc may point at another doc by location: neither a bundled-repo + // path like "domains/*.md" nor a "references/" directory. + expect(doc.body).not.toMatch(/references\//i); + expect(doc.body).not.toMatch(/domains\//i); + + for (const otherId of ids) { + if (otherId === doc.id) { + continue; + } + + // A doc must not point at another doc by filename (e.g. "mmkv.md") + // or by path (e.g. "domains/mmkv", "./docs/core"). Referring to the + // id in prose with no path marker (e.g. "run `npx rozenite skills + // show mmkv`", or the `core` doc's "evil/mmkv" provenance example) + // is fine. + expect(isPathReference(doc.body, otherId)).toBe(false); + } + } + }); +}); diff --git a/packages/cli/src/commands/register-skills-command.ts b/packages/cli/src/commands/register-skills-command.ts new file mode 100644 index 00000000..d91c5999 --- /dev/null +++ b/packages/cli/src/commands/register-skills-command.ts @@ -0,0 +1,48 @@ +import { Command } from 'commander'; +import { SkillsRegistry } from '../skills/registry.js'; +import { logger } from '../utils/logger.js'; + +export const registerSkillsCommand = (program: Command): void => { + const skillsCommand = program + .command('skills') + .description('Inspect bundled Rozenite for Agents skill docs'); + + skillsCommand + .command('list') + .alias('ls') + .description('List bundled skill docs (id and description)') + .option('--pretty', 'Pretty-print JSON output') + .action((options: { pretty?: boolean }) => { + const registry = new SkillsRegistry(); + const docs = registry.list(); + const payload = docs.map((doc) => ({ + id: doc.id, + description: doc.description, + ...(doc.domain ? { domain: doc.domain } : {}), + })); + + const json = options.pretty ? JSON.stringify(payload, null, 2) : JSON.stringify(payload); + process.stdout.write(`${json}\n`); + }); + + skillsCommand + .command('show') + .description('Print a bundled skill doc to stdout') + .argument('', 'Doc id, e.g. core, cli, sdk, or a domain token like storage') + .action((id: string) => { + const registry = new SkillsRegistry(); + const doc = registry.get(id); + + if (!doc) { + const validIds = registry + .list() + .map((entry) => entry.id) + .join(', '); + logger.error(`Unknown skill doc "${id}". Valid ids: ${validIds}`); + process.exitCode = 1; + return; + } + + process.stdout.write(`${doc.body}\n`); + }); +}; diff --git a/packages/cli/src/constants.ts b/packages/cli/src/constants.ts index b52c6ff6..1a8fd2a2 100644 --- a/packages/cli/src/constants.ts +++ b/packages/cli/src/constants.ts @@ -1,3 +1,5 @@ import { fileURLToPath } from 'node:url'; export const TEMPLATE_DIR = fileURLToPath(new URL('../template', import.meta.url)); + +export const DOCS_DIR = fileURLToPath(new URL('../docs', import.meta.url)); diff --git a/packages/cli/src/index.ts b/packages/cli/src/index.ts index 28f4c1ce..f49a43f5 100644 --- a/packages/cli/src/index.ts +++ b/packages/cli/src/index.ts @@ -8,6 +8,7 @@ import { buildCommand } from './commands/build-command.js'; import { devCommand } from './commands/dev-command.js'; import { initCommand } from './commands/init-command.js'; import { registerAgentCommand } from './commands/agent/register-agent-command.js'; +import { registerSkillsCommand } from './commands/register-skills-command.js'; import { getErrorMessage } from './commands/agent/error-message.js'; const packageJSON = getPackageJSON(); @@ -84,6 +85,7 @@ const main = async () => { }); registerAgentCommand(program); + registerSkillsCommand(program); await program.parseAsync(process.argv); }; diff --git a/packages/cli/src/skills/registry.ts b/packages/cli/src/skills/registry.ts new file mode 100644 index 00000000..b0c9dcff --- /dev/null +++ b/packages/cli/src/skills/registry.ts @@ -0,0 +1,101 @@ +import fs from 'node:fs'; +import path from 'node:path'; +import { DOCS_DIR } from '../constants.js'; + +export { DOCS_DIR }; + +export type SkillDoc = { + id: string; + description: string; + domain?: string; + body: string; +}; + +type Frontmatter = { + name?: string; + description?: string; + domain?: string; +}; + +const parseFrontmatter = (raw: string): { frontmatter: Frontmatter; body: string } => { + const match = raw.match(/^---\r?\n([\s\S]*?)\r?\n---\r?\n?([\s\S]*)$/); + + if (!match) { + return { frontmatter: {}, body: raw }; + } + + const [, rawFrontmatter, body] = match; + const frontmatter: Frontmatter = {}; + + for (const line of rawFrontmatter.split(/\r?\n/)) { + const lineMatch = line.match(/^([a-zA-Z0-9_-]+):\s*(.*)$/); + if (!lineMatch) { + continue; + } + + const [, key, value] = lineMatch; + const trimmedValue = value.trim().replace(/^['"]|['"]$/g, ''); + + if (key === 'name' || key === 'description' || key === 'domain') { + frontmatter[key] = trimmedValue; + } + } + + return { frontmatter, body: body.replace(/^\r?\n/, '') }; +}; + +const loadDoc = (docsDir: string, fileName: string): SkillDoc => { + const filePath = path.join(docsDir, fileName); + const raw = fs.readFileSync(filePath, 'utf8'); + const { frontmatter, body } = parseFrontmatter(raw); + const id = frontmatter.name ?? path.basename(fileName, '.md'); + + if (!frontmatter.description) { + throw new Error(`Doc "${id}" (${filePath}) is missing a "description" in its frontmatter.`); + } + + return { + id, + description: frontmatter.description, + domain: frontmatter.domain, + body, + }; +}; + +/** + * Loads every bundled doc under `docsDir` from disk. + */ +const loadAllDocs = (docsDir: string): SkillDoc[] => { + const fileNames = fs + .readdirSync(docsDir) + .filter((fileName) => fileName.endsWith('.md')) + .sort(); + + return fileNames.map((fileName) => loadDoc(docsDir, fileName)); +}; + +export class SkillsRegistry { + private readonly docs: SkillDoc[]; + private readonly byId: Map; + private readonly byDomain: Map; + + constructor(docsDir: string = DOCS_DIR) { + this.docs = loadAllDocs(docsDir); + this.byId = new Map(this.docs.map((doc) => [doc.id, doc])); + this.byDomain = new Map( + this.docs.filter((doc) => doc.domain).map((doc) => [doc.domain as string, doc]), + ); + } + + list(): SkillDoc[] { + return this.docs; + } + + get(id: string): SkillDoc | undefined { + return this.byId.get(id); + } + + getByDomain(domain: string): SkillDoc | undefined { + return this.byDomain.get(domain); + } +} diff --git a/website/src/docs/agent/overview.mdx b/website/src/docs/agent/overview.mdx index e23f944c..d423f57e 100644 --- a/website/src/docs/agent/overview.mdx +++ b/website/src/docs/agent/overview.mdx @@ -63,11 +63,11 @@ Prefer the built-in `network` domain first when it is available. `@rozenite/netw Apps can also expose in-app custom tools. These are runtime tools registered directly by the app instead of by a separate Rozenite plugin package. -If you want to give a coding agent this workflow directly, install the [`rozenite-agent` skill](/docs/agent/skills) for CLI workflows or `rozenite-agent-sdk` for SDK-driven scripts and automations. +If you want to give a coding agent this workflow directly, install the [`rozenite` skill](/docs/agent/skills). It routes the agent to `npx rozenite skills list` and `npx rozenite skills show ` to discover and read the CLI and SDK workflow docs on demand. ## Next steps -- Install the [Agent Skills](/docs/agent/skills) (`rozenite-agent` for CLI workflows, `rozenite-agent-sdk` for code-first SDK workflows) if you want Codex or another coding agent to use Rozenite effectively. +- Install the [Agent Skill](/docs/agent/skills) (`rozenite`) if you want Codex or another coding agent to use Rozenite effectively. - [Adding tools to your application](/docs/agent/adding-tools-to-your-application) – expose custom tools from your app for agents to call. - [Making your plugin agent-enabled](/docs/agent/making-your-plugin-agent-enabled) – expose tools from your Rozenite plugin to agents. - [Agent SDK](/docs/agent/sdk) – build custom tooling or automation on top of the agent workflow. diff --git a/website/src/docs/agent/sdk.mdx b/website/src/docs/agent/sdk.mdx index 3097bbb5..916031b5 100644 --- a/website/src/docs/agent/sdk.mdx +++ b/website/src/docs/agent/sdk.mdx @@ -5,7 +5,7 @@ import { PackageManagerTabs } from '@rspress/core/theme'; `@rozenite/agent-sdk` is the programmatic version of `rozenite agent` for Node.js and TypeScript scripts. Use it when you want to write a script, automation, or agent runtime that talks to a running app through Rozenite directly from code. :::tip -For most debugging workflows, start with the `rozenite agent` CLI and the [`rozenite-agent` skill](/docs/agent/skills). Reach for the SDK when you want code instead of shell commands, and install `rozenite-agent-sdk` if you want your coding agent to follow that SDK-first workflow directly. +For most debugging workflows, start with the `rozenite agent` CLI and the [`rozenite` skill](/docs/agent/skills). Reach for the SDK when you want code instead of shell commands — the skill's `npx rozenite skills show sdk` doc covers the SDK-first workflow directly. ::: :::warning Experimental diff --git a/website/src/docs/agent/skills.mdx b/website/src/docs/agent/skills.mdx index 2e7ac1e5..1e941f04 100644 --- a/website/src/docs/agent/skills.mdx +++ b/website/src/docs/agent/skills.mdx @@ -1,43 +1,46 @@ # Agent Skills -If you use Codex or another coding agent that supports the Vercel `skills` CLI, you can install a Rozenite skill so the agent can use Rozenite for Agents efficiently. +If you use Codex or another coding agent that supports the Vercel `skills` CLI, you can install the Rozenite skill so the agent can use Rozenite for Agents efficiently. -Rozenite currently ships two complementary skills: +Rozenite ships a single skill, `rozenite`. It is a thin router: the installed skill contains no workflow content of its own, only a pointer telling the agent to discover and read the real content through the `rozenite skills` CLI command. -- `rozenite-agent` for CLI-driven `rozenite agent ...` workflows and live shell-based debugging -- `rozenite-agent-sdk` for Node.js and TypeScript scripts that use `@rozenite/agent-sdk` +The actual guidance — ground truths, CLI and SDK workflows, and one doc per plugin domain — lives inside the `rozenite` CLI package itself and is versioned together with it. That's why the content moved out of the skill: whatever the agent reads through `rozenite skills` always matches the version of Rozenite actually installed in the project, instead of drifting out of sync with a separately-installed skill. -These skills are the easiest way to make a coding agent effective with Rozenite. They give the agent the expected workflow so it can interact with React Native DevTools and Rozenite plugins without fumbling CLI usage, SDK lifecycle, or plugin domain resolution. +Because of that, installing the skill is a convenience, not a requirement. If your coding agent doesn't use a skills CLI at all, you can point it straight at `npx rozenite skills list` and `npx rozenite skills show ` and it gets the exact same content. -Both skills complement Rozenite; they do not replace it. Your app still needs Rozenite installed and configured because the agent ultimately talks to the running app through the Rozenite agent runtime. +This skill complements Rozenite; it does not replace it. Your app still needs Rozenite installed and configured because the agent ultimately talks to the running app through the Rozenite agent runtime. ## Install with the Vercel `skills` CLI -Install from this repository using the repo URL and the skill name that matches your workflow: - -For CLI-driven debugging: +Install from this repository using the repo URL: ```bash -npx skills add https://github.com/callstackincubator/rozenite --skill rozenite-agent --agent codex +npx skills add https://github.com/callstackincubator/rozenite --skill rozenite --agent codex ``` -For SDK-driven scripts and automations: +By default, the `skills` CLI installs project-local skills. For Codex, that means the skill is linked into `.agents/skills/`. To install it globally for all projects instead, use: ```bash -npx skills add https://github.com/callstackincubator/rozenite --skill rozenite-agent-sdk --agent codex +npx skills add https://github.com/callstackincubator/rozenite --skill rozenite --agent codex --global ``` -By default, the `skills` CLI installs project-local skills. For Codex, that means the skill is linked into `.agents/skills/`. To install it globally for all projects instead, use: +## The `rozenite skills` command + +Once the skill is installed, the agent discovers and reads the actual workflow content through two CLI commands, run from the app root where Metro is started (in a monorepo, that's the app package root, not the repository root): ```bash -npx skills add https://github.com/callstackincubator/rozenite --skill rozenite-agent --agent codex --global +npx rozenite skills list ``` -Replace `rozenite-agent` with `rozenite-agent-sdk` in that command if you want the SDK skill globally instead. +Lists every bundled doc as an id and a one-line description, for example `core`, `cli`, `sdk`, `sdk-patterns`, and one doc per domain such as `storage`, `mmkv`, or `network`. + +```bash +npx rozenite skills show +``` -After installation, you can ask your agent to use `rozenite-agent` whenever it needs shell-driven live debugging, or `rozenite-agent-sdk` whenever it needs to write code against `@rozenite/agent-sdk`. +Prints the raw content of one doc. The agent typically starts with `npx rozenite skills show core` for the ground truths shared by every workflow, then reads `cli` or `sdk` depending on whether it's driving the shell or writing code, and reads a domain doc like `storage` or `react-navigation` only when it needs that domain. -This is the recommended setup. Rozenite for Agents is designed for coding agents first, and these skills make the intended workflow explicit inside the agent. +An unknown id fails with a non-zero exit code and a message listing the valid ids. ## Next steps