From 21c2d13015e594a9c3e5063fe359c01b0003dac7 Mon Sep 17 00:00:00 2001 From: Mikolaj Adamowicz Date: Mon, 7 Sep 2026 12:50:38 +0200 Subject: [PATCH 1/3] feat: add Expo Skills to the curated lookup table --- LICENSE | 26 ++++ README.md | 3 +- scripts/sync-lookup.mjs | 18 ++- src/lookup-table.json | 164 ++++++++++++++++++++- test/fixtures/expo-router-app/package.json | 15 ++ test/index.e2e.test.ts | 67 ++++++++- 6 files changed, 286 insertions(+), 7 deletions(-) create mode 100644 test/fixtures/expo-router-app/package.json diff --git a/LICENSE b/LICENSE index 37ce375..487c78f 100644 --- a/LICENSE +++ b/LICENSE @@ -115,4 +115,30 @@ SOFTWARE. https://github.com/vercel-labs/agent-skills _No LICENSE file found in the repository root._ + +## expo/skills + +https://github.com/expo/skills + +The MIT License (MIT) + +Copyright (c) 2025-present 650 Industries, Inc. (aka Expo) + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. diff --git a/README.md b/README.md index f643f68..a047716 100644 --- a/README.md +++ b/README.md @@ -12,7 +12,7 @@ It scans every `package.json` under the target directory, compares discovered li ## Features - πŸ” **Dependency scan** β€” discovers libraries from every `package.json` under your project root -- πŸ—ΊοΈ **Curated mappings** β€” links React Native packages to skills from Callstack, Software Mansion, Vercel, and more +- πŸ—ΊοΈ **Curated mappings** β€” links React Native and Expo packages to skills from Callstack, Expo, Software Mansion, Vercel, and more - πŸ“‹ **Report** β€” shows recommended, missing, and extra skills without changing anything - πŸ™‹ **Interactive** β€” choose action groups (update, install, remove), then pick skills in each - ⚑ **Auto** β€” updates, installs, and prunes managed skills in one non-interactive pass @@ -219,6 +219,7 @@ This tool uses the following skills repositories: - [Software Mansion's Skills](https://github.com/software-mansion-labs/skills) - [React Native Testing Library Skills](https://github.com/callstack/react-native-testing-library) - [Vercel Agent Skills](https://github.com/vercel-labs/agent-skills) +- [Expo Skills](https://github.com/expo/skills) ## Special Thanks diff --git a/scripts/sync-lookup.mjs b/scripts/sync-lookup.mjs index e62a069..e7aceae 100644 --- a/scripts/sync-lookup.mjs +++ b/scripts/sync-lookup.mjs @@ -36,8 +36,15 @@ const remoteSources = [ repo: 'vercel-labs/agent-skills', displayName: 'Vercel Agent Skills', }, + { + repo: 'expo/skills', + displayName: 'Expo Skills', + skillsPath: 'plugins/expo/skills', + }, ]; +const DEFAULT_SKILLS_PATH = 'skills'; + async function main() { const lookup = JSON.parse(await readFile(lookupPath, 'utf8')); const nextSources = {}; @@ -49,7 +56,10 @@ async function main() { skills: preserveExistingDescriptions( lookup, source.repo, - await fetchRemoteSkills(source.repo) + await fetchRemoteSkills( + source.repo, + source.skillsPath ?? DEFAULT_SKILLS_PATH + ) ), }; } @@ -83,9 +93,9 @@ async function main() { ); } -async function fetchRemoteSkills(repo) { +async function fetchRemoteSkills(repo, skillsPath = DEFAULT_SKILLS_PATH) { const directoryResponse = await fetch( - `https://api.github.com/repos/${repo}/contents/skills`, + `https://api.github.com/repos/${repo}/contents/${skillsPath}`, { headers: { 'Accept': 'application/vnd.github+json', @@ -108,7 +118,7 @@ async function fetchRemoteSkills(repo) { const skills = []; for (const directory of directories) { - const rawSkillUrl = `https://raw.githubusercontent.com/${repo}/main/skills/${directory}/SKILL.md`; + const rawSkillUrl = `https://raw.githubusercontent.com/${repo}/main/${skillsPath}/${directory}/SKILL.md`; const response = await fetch(rawSkillUrl, { headers: { 'User-Agent': 'pkg-skills-sync', diff --git a/src/lookup-table.json b/src/lookup-table.json index 4d48550..62a4f9c 100644 --- a/src/lookup-table.json +++ b/src/lookup-table.json @@ -107,12 +107,121 @@ "description": "Review UI code for Web Interface Guidelines compliance. Use when asked to \"review my UI\", \"check accessibility\", \"audit design\", \"review UX\", or \"check my site against best practices\"." } ] + }, + "expo/skills": { + "repo": "expo/skills", + "displayName": "Expo Skills", + "skills": [ + { + "name": "eas-app-stores", + "description": "EAS service (paid). Deploy Expo apps to the app stores with EAS - build and submit to the iOS App Store, Google Play Store, and TestFlight, configure eas.json build and submit profiles, manage app versions and build numbers, and publish App Store metadata and ASO. Use whenever the user wants to deploy, release, or ship an app to production or the app stores, is preparing a production build, running eas build or eas submit, shipping to TestFlight, bumping version or build numbers, or setting up store listing metadata. For deploying an Expo website or API routes, use the eas-hosting skill." + }, + { + "name": "eas-hosting", + "description": "EAS service (paid). Deploy Expo websites and Expo Router API routes to EAS Hosting - export the web bundle, run eas deploy for production and PR preview URLs, manage environment secrets and custom domains, and work within the Cloudflare Workers runtime. Also covers authoring API routes (+api.ts handlers, HTTP methods, request handling, CORS). Use when deploying an Expo web app or API routes, setting up EAS Hosting, or configuring hosting environments and domains. Not for native builds or store releases - use the eas-app-stores skill for those." + }, + { + "name": "eas-observe", + "description": "EAS service (paid). Use for anything related to EAS Observe - adding `expo-observe` to an Expo project (AppMetricsRoot/ObserveRoot HOC, markInteractive and ObserveInteractiveMarker, the useObserve hook, the Expo Router / React Navigation integrations for per-route metrics, user-defined events via `Observe.logEvent`, error reporting via ObserveErrorBoundary and `Observe.reportError`, and runtime config such as sampleRate and dispatchInDebug), querying via the EAS CLI (`eas observe:metrics-summary`, `observe:metrics`, `observe:routes`, `observe:events`, `observe:session`, `observe:versions`), interpreting the resulting metrics (cold/warm launch, TTR, TTI, navigation cold/warm TTR, update download, and the TTI frameRate/device/network params for triaging slow startups), or shipping an Observe integration inside a third-party package." + }, + { + "name": "eas-simulator", + "description": "\"EAS service (paid). Run and control a user's app on a remote iOS/Android simulator hosted on EAS cloud. Read before running any `eas simulator:*` commands - it has the current syntax for this experimental API. Use whenever the user needs a simulator they can't run locally - 'run my app on a cloud simulator', 'use eas simulator to run/install/screenshot my app', 'I'm on Linux/Cursor and need an iOS device', 'no sim on this box / headless CI', 'let an agent click through my app and screenshot it', 'test my dev build on a remote sim with live reload', 'stream a sim to my browser' - even when they don't say 'EAS Simulator' or 'cloud'. On a host WITHOUT a local simulator (Linux, CI, cloud sandbox) it's the default; on macOS, do NOT auto-trigger for a plain 'run on the simulator' - use it only for a cloud/remote/shareable sim, an iOS version they lack, or an agent-driven session. NOT for local sims (expo run:ios, Xcode, Android Studio), EAS Build/Update, web preview, or physical devices.\"" + }, + { + "name": "eas-update-insights", + "description": "\"EAS service (paid). Check the health of published EAS Update: crash rates, install/launch counts, unique users, payload size, and the split between embedded and OTA users per channel. Use when the user asks how an update is performing, whether a rollout is healthy, how many users are on the embedded build vs OTA, or wants to gate CI on update health.\"" + }, + { + "name": "eas-workflows", + "description": "EAS service (paid). Helps understand and write EAS workflow YAML files for Expo projects. Use this skill when the user asks about CI/CD or workflows in an Expo or EAS context, mentions .eas/workflows/, or wants help with EAS build pipelines or deployment automation." + }, + { + "name": "expo-animation", + "description": "Framework (OSS). Build animations in React Native and Expo, making the decisions in the order that determines whether they feel right β€” should it animate, which thread it runs on, which properties, spring or timing, how the gesture hands off, how it degrades. Writes the implementation with Reanimated, Gesture Handler, Expo Router and expo-haptics. Use when animating anything in an Expo app, adding gestures, sheets, screen transitions, press feedback or haptics, or fixing motion that stutters on device. For web animation use `animate`." + }, + { + "name": "expo-app-clip", + "description": "Framework (OSS). Add an iOS App Clip target to an Expo app. Use when the user mentions App Clip, AASA, apple-app-site-association, appclips, smart app banner, or wants to ship a lightweight iOS Clip invoked from a URL alongside their parent app." + }, + { + "name": "expo-brownfield", + "description": "Framework (OSS). Integrate Expo and React Native into an existing native iOS or Android app. Use when the user mentions brownfield, embedding React Native in a native app, AAR/XCFramework, or adding Expo to an existing Kotlin/Swift project. Covers both the isolated approach and the integrated approach." + }, + { + "name": "expo-data-fetching", + "description": "Framework (OSS). Use when implementing or debugging ANY network request, API call, or data fetching. Covers fetch API, React Query, SWR, error handling, caching, offline support, and Expo Router data loaders (`useLoaderData`)." + }, + { + "name": "expo-design-system", + "description": "Framework (OSS). Build and maintain a design system inside an Expo app - a reusable theme of design tokens (color, spacing, typography, radius, shadow, motion), reusable component structure with variant/size/state prop conventions, and rules for when to extract a repeated view into a shared component. Use when creating or organizing theme files and design tokens (theme.ts / theme/), extending an existing theme or styling library (NativeWind, Tamagui, Restyle, Unistyles) in its own idiom, standardizing styles so screens (including AI-generated ones) look consistent and polished, building an in-app component library, or auditing an app for design-system drift (hardcoded colors, spacing, fonts). For platform styling specifics (semantic colors, HIG rules, native controls) use expo-native-ui; for folder layout of a new app use expo-project-structure." + }, + { + "name": "expo-dev-client", + "description": "Framework (OSS). Build and distribute Expo development clients locally or via TestFlight for internal testing. For production TestFlight releases and store submission, use the eas-app-stores skill." + }, + { + "name": "expo-dom", + "description": "Framework (OSS). Use Expo DOM components to run web code in a webview on native and as-is on web. Migrate web code to native incrementally. For the end-to-end migration of a whole web app, use the expo-web-to-native skill." + }, + { + "name": "expo-examples", + "description": "Framework (OSS). Expo's official example projects - the expo/examples repo of ~70 `with-*` integrations (Stripe, Clerk, Supabase, OpenAI, maps, Reanimated, SQLite, Skia, NativeWind, and more). Use when integrating a third-party library or service into an existing Expo app and you want the canonical, version-matched pattern to adapt, or when scaffolding a new project from one with `npx create-expo --example`." + }, + { + "name": "expo-module", + "description": "Framework (OSS). Guide for creating and writing Expo native modules and views using the Expo Modules API (Swift, Kotlin, TypeScript). Covers module definition DSL, native views, shared objects, config plugins, lifecycle hooks, autolinking, and type system. Use when building or modifying native modules for Expo. Not for migrating an existing Swift module from the definition DSL to the Expo Modules API 2.0 macros; use expo-migrate-module (from the expo-experiments plugin) for that." + }, + { + "name": "expo-native-ui", + "description": "Framework (OSS). Build beautiful, native-feeling Expo screens. Covers Apple HIG styling, semantic colors, native controls, SF Symbols, media, animations, visual effects, gradients, storage, and responsive layout. For routing and navigation, use the expo-router skill." + }, + { + "name": "expo-overview", + "description": "\"Framework (OSS). Entry point and router for every Expo or EAS task. Load this skill first β€” before writing code and before choosing another expo-* / eas-* skill β€” when the request, PRD, or spec mentions Expo, EAS, Expo Go, or an expo-* package, or the project has an `expo` dependency in `package.json`. Within that gate it also covers app specs and designs to implement (tabs, stacks, maps, lists, navigation, building from a screenshot), and phrasings like 'implement a mobile app', 'make my app look native', 'add navigation', 'fetch some data', 'upgrade my SDK', 'add Expo to my existing native app', 'ship to the App Store', or 'I'm new to Expo, where do I start'. A fully specified request (SDK pinned, libraries named, layout given) still routes through here β€” the shared setup rules still apply. Do NOT load it when neither signal is present: a bare React Native project with no `expo` dependency is not Expo work. Detects the real goal, routes to the right expo-* / eas-* skill, and owns the shared setup rules.\"" + }, + { + "name": "expo-project-structure", + "description": "Framework (OSS). Folder structure for a new Expo app. Use when scaffolding or laying out a new Expo project with Expo Router, or deciding where a file should live in one. For new projects only β€” never restructure an existing app to match." + }, + { + "name": "expo-router", + "description": "Framework (OSS). Navigation and routing for Expo Router. Covers file-based routes, groups and dynamic routes, folder organization, Link with previews and context menus, native Stack, page titles, modals and form sheets, NativeTabs, headers and toolbars, and header search bars." + }, + { + "name": "expo-skill-feedback", + "description": "'Submit feedback on an Expo skillβ€”or Expo itselfβ€”and control bundled anonymous usage telemetry (off by default / opt-in). Submit feedback with: npx --yes submit-expo-feedback@latest \"ACTIONABLE_FEEDBACK\". Optionally add either or both: --category \"CATEGORY\" and --subject \"SUBJECT\". Replace the uppercase placeholders before running. Use when a skill was useful, confusing, broken, missing context, or worth improving; when Expo, Expo CLI, EAS CLI, docs, or MCP worked well or fell short; when an AI agent repeatedly failed, got stuck, or needed the user to take over an Expo task (report it as an eval candidate); or when the user explicitly asks to enable or disable telemetry (tracking), check its status, or understand what it collects.'" + }, + { + "name": "expo-ui", + "description": "\"Framework (OSS). Build native UI with the @expo/ui package: real SwiftUI on iOS and Jetpack Compose on Android. Default to @expo/ui for sheets (BottomSheet), pickers, sliders, toggles, menus, and grouped-form sections β€” do NOT reach for Reanimated, @gorhom/bottom-sheet, or RN built-in Picker/Switch; use @expo/ui instead. Fall back to RN built-ins only when @expo/ui is missing the component. NOTE: @expo/ui List renders native grouped rows like an iOS Settings screen β€” it is NOT a virtualized list; use FlatList/FlashList for large datasets. Covers universal components (Host, Column, Row, Button, Text, List, BottomSheet, FieldGroup, Switch, Slider, Picker, Menu), drop-in replacements for RN community libraries, and platform-specific SwiftUI/Jetpack Compose trees. Not for Expo Router navigation, Reanimated, or data fetching.\"" + }, + { + "name": "expo-upgrade", + "description": "Framework (OSS). Guidelines for upgrading Expo SDK versions and fixing dependency issues" + }, + { + "name": "expo-web-to-native", + "description": "Framework (OSS). Migrate an existing web React app to a native iOS/Android app with Expo. Use when the user wants to turn a website into a mobile app, port a Next.js/Vite/CRA React codebase to React Native, reuse web code on native incrementally, or asks how web idioms (the DOM, CSS, React Router, localStorage, window) map to native. This is the end-to-end migration guide; use the `expo-dom` skill for the DOM-component mechanism itself." + } + ] } }, "libraries": { "@callstack/react-native-brownfield": { "skillRefs": [ - "callstackincubator/agent-skills:react-native-brownfield-migration" + "callstackincubator/agent-skills:react-native-brownfield-migration", + "expo/skills:expo-brownfield" + ] + }, + "@expo/server": { + "skillRefs": [ + "expo/skills:eas-hosting" + ] + }, + "@expo/ui": { + "skillRefs": [ + "expo/skills:expo-ui" ] }, "@react-navigation/native": { @@ -136,14 +245,36 @@ "callstack/react-native-testing-library:react-native-testing" ] }, + "eas-cli": { + "skillRefs": [ + "expo/skills:eas-app-stores", + "expo/skills:eas-simulator", + "expo/skills:eas-workflows" + ] + }, "expo": { "skillRefs": [ "callstackincubator/agent-skills:react-native-best-practices", + "expo/skills:expo-animation", + "expo/skills:expo-app-clip", + "expo/skills:expo-data-fetching", + "expo/skills:expo-design-system", + "expo/skills:expo-examples", + "expo/skills:expo-native-ui", + "expo/skills:expo-overview", + "expo/skills:expo-project-structure", + "expo/skills:expo-skill-feedback", + "expo/skills:expo-upgrade", "vercel-labs/agent-skills:deploy-to-vercel", "vercel-labs/agent-skills:vercel-cli-with-tokens", "vercel-labs/agent-skills:vercel-react-native-skills" ] }, + "expo-dev-client": { + "skillRefs": [ + "expo/skills:expo-dev-client" + ] + }, "expo-font": { "skillRefs": [ "vercel-labs/agent-skills:vercel-react-native-skills" @@ -159,6 +290,31 @@ "vercel-labs/agent-skills:vercel-react-native-skills" ] }, + "expo-module-scripts": { + "skillRefs": [ + "expo/skills:expo-module" + ] + }, + "expo-modules-core": { + "skillRefs": [ + "expo/skills:expo-module" + ] + }, + "expo-observe": { + "skillRefs": [ + "expo/skills:eas-observe" + ] + }, + "expo-router": { + "skillRefs": [ + "expo/skills:expo-router" + ] + }, + "expo-updates": { + "skillRefs": [ + "expo/skills:eas-update-insights" + ] + }, "react-native": { "skillRefs": [ "callstackincubator/agent-skills:github", @@ -225,6 +381,12 @@ "software-mansion-labs/skills:react-native-best-practices" ] }, + "react-native-web": { + "skillRefs": [ + "expo/skills:expo-dom", + "expo/skills:expo-web-to-native" + ] + }, "react-native-worklets": { "skillRefs": [ "software-mansion-labs/skills:react-native-best-practices" diff --git a/test/fixtures/expo-router-app/package.json b/test/fixtures/expo-router-app/package.json new file mode 100644 index 0000000..3e88ce2 --- /dev/null +++ b/test/fixtures/expo-router-app/package.json @@ -0,0 +1,15 @@ +{ + "name": "expo-router-app", + "private": true, + "dependencies": { + "@expo/server": "^0.6.0", + "@expo/ui": "^0.2.0", + "expo-dev-client": "^6.0.0", + "expo-router": "^7.0.0", + "expo-updates": "^30.0.0", + "react-native-web": "^0.21.0" + }, + "devDependencies": { + "eas-cli": "^18.0.0" + } +} diff --git a/test/index.e2e.test.ts b/test/index.e2e.test.ts index a071e8a..c149bf9 100644 --- a/test/index.e2e.test.ts +++ b/test/index.e2e.test.ts @@ -74,7 +74,7 @@ describe('pkg-skills e2e', () => { expect(stdout).toContain('react-native-reanimated'); }); - it('adds the expected Callstack, Vercel, and testing skills for expo-app', async () => { + it('adds the expected Callstack, Expo, Vercel, and testing skills for expo-app', async () => { const result = await runAutoWithFixture({ fixtureName: 'expo-app', installedSkills: [], @@ -86,6 +86,16 @@ describe('pkg-skills e2e', () => { ['callstackincubator/agent-skills', 'github-actions'], ['callstackincubator/agent-skills', 'react-native-best-practices'], ['callstackincubator/agent-skills', 'upgrading-react-native'], + ['expo/skills', 'expo-animation'], + ['expo/skills', 'expo-app-clip'], + ['expo/skills', 'expo-data-fetching'], + ['expo/skills', 'expo-design-system'], + ['expo/skills', 'expo-examples'], + ['expo/skills', 'expo-native-ui'], + ['expo/skills', 'expo-overview'], + ['expo/skills', 'expo-project-structure'], + ['expo/skills', 'expo-skill-feedback'], + ['expo/skills', 'expo-upgrade'], ['software-mansion-labs/skills', 'radon-mcp'], ['vercel-labs/agent-skills', 'deploy-to-vercel'], ['vercel-labs/agent-skills', 'vercel-cli-with-tokens'], @@ -102,6 +112,29 @@ describe('pkg-skills e2e', () => { expect(result.exitCode).toBe(0); }); + it('adds the Expo skills for expo-router-app', async () => { + const result = await runAutoWithFixture({ + fixtureName: 'expo-router-app', + installedSkills: [], + expectedAdds: [ + ['expo/skills', 'eas-app-stores'], + ['expo/skills', 'eas-hosting'], + ['expo/skills', 'eas-simulator'], + ['expo/skills', 'eas-update-insights'], + ['expo/skills', 'eas-workflows'], + ['expo/skills', 'expo-dev-client'], + ['expo/skills', 'expo-dom'], + ['expo/skills', 'expo-router'], + ['expo/skills', 'expo-ui'], + ['expo/skills', 'expo-web-to-native'], + ], + expectedRemovals: [], + command: ['auto'], + }); + + expect(result.exitCode).toBe(0); + }); + it('adds the brownfield migration skill for brownfield-app', async () => { const result = await runAutoWithFixture({ fixtureName: 'brownfield-app', @@ -111,6 +144,7 @@ describe('pkg-skills e2e', () => { 'callstackincubator/agent-skills', 'react-native-brownfield-migration', ], + ['expo/skills', 'expo-brownfield'], ], expectedRemovals: [], command: ['auto'], @@ -149,6 +183,7 @@ describe('pkg-skills e2e', () => { 'callstackincubator/agent-skills', 'react-native-brownfield-migration', ], + ['expo/skills', 'expo-brownfield'], ], expectedRemovals: [], command: ['auto'], @@ -179,6 +214,16 @@ describe('pkg-skills e2e', () => { ['callstackincubator/agent-skills', 'github-actions'], ['callstackincubator/agent-skills', 'react-native-best-practices'], ['callstackincubator/agent-skills', 'upgrading-react-native'], + ['expo/skills', 'expo-animation'], + ['expo/skills', 'expo-app-clip'], + ['expo/skills', 'expo-data-fetching'], + ['expo/skills', 'expo-design-system'], + ['expo/skills', 'expo-examples'], + ['expo/skills', 'expo-native-ui'], + ['expo/skills', 'expo-overview'], + ['expo/skills', 'expo-project-structure'], + ['expo/skills', 'expo-skill-feedback'], + ['expo/skills', 'expo-upgrade'], ['software-mansion-labs/skills', 'radon-mcp'], ['vercel-labs/agent-skills', 'deploy-to-vercel'], ['vercel-labs/agent-skills', 'vercel-cli-with-tokens'], @@ -210,6 +255,16 @@ describe('pkg-skills e2e', () => { ['callstackincubator/agent-skills', 'github-actions'], ['callstackincubator/agent-skills', 'react-native-best-practices'], ['callstackincubator/agent-skills', 'upgrading-react-native'], + ['expo/skills', 'expo-animation'], + ['expo/skills', 'expo-app-clip'], + ['expo/skills', 'expo-data-fetching'], + ['expo/skills', 'expo-design-system'], + ['expo/skills', 'expo-examples'], + ['expo/skills', 'expo-native-ui'], + ['expo/skills', 'expo-overview'], + ['expo/skills', 'expo-project-structure'], + ['expo/skills', 'expo-skill-feedback'], + ['expo/skills', 'expo-upgrade'], ['software-mansion-labs/skills', 'radon-mcp'], ['vercel-labs/agent-skills', 'deploy-to-vercel'], ['vercel-labs/agent-skills', 'vercel-cli-with-tokens'], @@ -490,6 +545,16 @@ describe('pkg-skills e2e', () => { ['callstackincubator/agent-skills', 'github-actions'], ['callstackincubator/agent-skills', 'react-native-best-practices'], ['callstackincubator/agent-skills', 'upgrading-react-native'], + ['expo/skills', 'expo-animation'], + ['expo/skills', 'expo-app-clip'], + ['expo/skills', 'expo-data-fetching'], + ['expo/skills', 'expo-design-system'], + ['expo/skills', 'expo-examples'], + ['expo/skills', 'expo-native-ui'], + ['expo/skills', 'expo-overview'], + ['expo/skills', 'expo-project-structure'], + ['expo/skills', 'expo-skill-feedback'], + ['expo/skills', 'expo-upgrade'], ['software-mansion-labs/skills', 'radon-mcp'], ['vercel-labs/agent-skills', 'deploy-to-vercel'], ['vercel-labs/agent-skills', 'vercel-cli-with-tokens'], From bbac05deca4ff10b6ac15f6080e85bb72fc3f739 Mon Sep 17 00:00:00 2001 From: Mikolaj Adamowicz Date: Mon, 7 Sep 2026 13:15:17 +0200 Subject: [PATCH 2/3] feat: skills paths in sync-lookup --- .changeset/tall-spies-invite.md | 5 +++++ README.md | 2 +- scripts/sync-lookup.mjs | 9 ++------- 3 files changed, 8 insertions(+), 8 deletions(-) create mode 100644 .changeset/tall-spies-invite.md diff --git a/.changeset/tall-spies-invite.md b/.changeset/tall-spies-invite.md new file mode 100644 index 0000000..350d2ec --- /dev/null +++ b/.changeset/tall-spies-invite.md @@ -0,0 +1,5 @@ +--- +'pkg-skills': minor +--- + +feat: add Expo Skills (expo/skills) to the curated lookup table diff --git a/README.md b/README.md index a047716..99351c8 100644 --- a/README.md +++ b/README.md @@ -12,7 +12,7 @@ It scans every `package.json` under the target directory, compares discovered li ## Features - πŸ” **Dependency scan** β€” discovers libraries from every `package.json` under your project root -- πŸ—ΊοΈ **Curated mappings** β€” links React Native and Expo packages to skills from Callstack, Expo, Software Mansion, Vercel, and more +- πŸ—ΊοΈ **Curated mappings** β€” links React Native packages to skills from Callstack, Expo, Software Mansion, Vercel, and more - πŸ“‹ **Report** β€” shows recommended, missing, and extra skills without changing anything - πŸ™‹ **Interactive** β€” choose action groups (update, install, remove), then pick skills in each - ⚑ **Auto** β€” updates, installs, and prunes managed skills in one non-interactive pass diff --git a/scripts/sync-lookup.mjs b/scripts/sync-lookup.mjs index e7aceae..46942ad 100644 --- a/scripts/sync-lookup.mjs +++ b/scripts/sync-lookup.mjs @@ -43,8 +43,6 @@ const remoteSources = [ }, ]; -const DEFAULT_SKILLS_PATH = 'skills'; - async function main() { const lookup = JSON.parse(await readFile(lookupPath, 'utf8')); const nextSources = {}; @@ -56,10 +54,7 @@ async function main() { skills: preserveExistingDescriptions( lookup, source.repo, - await fetchRemoteSkills( - source.repo, - source.skillsPath ?? DEFAULT_SKILLS_PATH - ) + await fetchRemoteSkills(source.repo, source.skillsPath) ), }; } @@ -93,7 +88,7 @@ async function main() { ); } -async function fetchRemoteSkills(repo, skillsPath = DEFAULT_SKILLS_PATH) { +async function fetchRemoteSkills(repo, skillsPath = 'skills') { const directoryResponse = await fetch( `https://api.github.com/repos/${repo}/contents/${skillsPath}`, { From 3ad2593b7e1d2ffb54bb87be4a5fb2e46d3b2ee4 Mon Sep 17 00:00:00 2001 From: Mikolaj Adamowicz Date: Tue, 8 Sep 2026 14:06:12 +0200 Subject: [PATCH 3/3] fix: correct Expo Skills paths in sync-lookup and lookup table --- scripts/sync-lookup.mjs | 37 +++++++++++++++++++-- src/lookup-table.json | 38 ++++------------------ test/fixtures/expo-router-app/package.json | 3 +- test/index.e2e.test.ts | 16 +++------ 4 files changed, 47 insertions(+), 47 deletions(-) diff --git a/scripts/sync-lookup.mjs b/scripts/sync-lookup.mjs index 46942ad..d453b9e 100644 --- a/scripts/sync-lookup.mjs +++ b/scripts/sync-lookup.mjs @@ -40,6 +40,17 @@ const remoteSources = [ repo: 'expo/skills', displayName: 'Expo Skills', skillsPath: 'plugins/expo/skills', + // expo-dom and expo-web-to-native are migration guides for code that has + // not adopted React Native yet, so no package.json signals them. + // expo-app-clip covers a feature almost no app ships, and + // expo-skill-feedback is a feedback and telemetry channel rather than + // engineering guidance - both would fire on every Expo project. + excludedSkills: [ + 'expo-app-clip', + 'expo-dom', + 'expo-skill-feedback', + 'expo-web-to-native', + ], }, ]; @@ -54,7 +65,10 @@ async function main() { skills: preserveExistingDescriptions( lookup, source.repo, - await fetchRemoteSkills(source.repo, source.skillsPath) + excludeSkills( + await fetchRemoteSkills(source.repo, source.skillsPath), + source.excludedSkills + ) ), }; } @@ -134,6 +148,14 @@ async function fetchRemoteSkills(repo, skillsPath = 'skills') { return skills; } +function excludeSkills(skills, excludedSkills) { + if (!excludedSkills?.length) { + return skills; + } + + return skills.filter((skill) => !excludedSkills.includes(skill.name)); +} + function preserveExistingDescriptions(lookup, repo, skills) { const existingDescriptionsBySkillName = new Map( (lookup.sources?.[repo]?.skills ?? []).map((skill) => [ @@ -267,7 +289,7 @@ function extractDescription(skillMarkdown) { inlineValue !== '>-' && inlineValue !== '|-' ) { - return inlineValue; + return stripSurroundingQuotes(inlineValue); } const descriptionLines = []; @@ -290,4 +312,15 @@ function extractDescription(skillMarkdown) { return ''; } +// YAML requires quoting a scalar that contains ': ', so some descriptions +// arrive wrapped in delimiters that are not part of the text. +function stripSurroundingQuotes(value) { + const quote = value[0]; + if ((quote !== '"' && quote !== "'") || value.length < 2) { + return value; + } + + return value.endsWith(quote) ? value.slice(1, -1) : value; +} + await main(); diff --git a/src/lookup-table.json b/src/lookup-table.json index 62a4f9c..284e010 100644 --- a/src/lookup-table.json +++ b/src/lookup-table.json @@ -48,7 +48,7 @@ "skills": [ { "name": "expo-horizon", - "description": "\"Software Mansion's guide for migrating Expo SDK apps to Meta Quest using expo-horizon packages. Use when adding Meta Quest or Meta Horizon OS support to an existing Expo or React Native project. Trigger on: Meta Quest, Horizon OS, Quest 2, Quest 3, Quest 3S, VR app, expo-horizon-core, expo-horizon-location, expo-horizon-notifications, build flavors for Quest, panel sizing, VR headtracking, Horizon App ID, quest build variant, isHorizonDevice, isHorizonBuild, migrate expo-location to Quest, migrate expo-notifications to Quest, Meta Horizon Store publishing, or any task involving running an Expo app on Meta Quest hardware.\"" + "description": "Software Mansion's guide for migrating Expo SDK apps to Meta Quest using expo-horizon packages. Use when adding Meta Quest or Meta Horizon OS support to an existing Expo or React Native project. Trigger on: Meta Quest, Horizon OS, Quest 2, Quest 3, Quest 3S, VR app, expo-horizon-core, expo-horizon-location, expo-horizon-notifications, build flavors for Quest, panel sizing, VR headtracking, Horizon App ID, quest build variant, isHorizonDevice, isHorizonBuild, migrate expo-location to Quest, migrate expo-notifications to Quest, Meta Horizon Store publishing, or any task involving running an Expo app on Meta Quest hardware." }, { "name": "radon-mcp", @@ -126,11 +126,11 @@ }, { "name": "eas-simulator", - "description": "\"EAS service (paid). Run and control a user's app on a remote iOS/Android simulator hosted on EAS cloud. Read before running any `eas simulator:*` commands - it has the current syntax for this experimental API. Use whenever the user needs a simulator they can't run locally - 'run my app on a cloud simulator', 'use eas simulator to run/install/screenshot my app', 'I'm on Linux/Cursor and need an iOS device', 'no sim on this box / headless CI', 'let an agent click through my app and screenshot it', 'test my dev build on a remote sim with live reload', 'stream a sim to my browser' - even when they don't say 'EAS Simulator' or 'cloud'. On a host WITHOUT a local simulator (Linux, CI, cloud sandbox) it's the default; on macOS, do NOT auto-trigger for a plain 'run on the simulator' - use it only for a cloud/remote/shareable sim, an iOS version they lack, or an agent-driven session. NOT for local sims (expo run:ios, Xcode, Android Studio), EAS Build/Update, web preview, or physical devices.\"" + "description": "EAS service (paid). Run and control a user's app on a remote iOS/Android simulator hosted on EAS cloud. Read before running any `eas simulator:*` commands - it has the current syntax for this experimental API. Use whenever the user needs a simulator they can't run locally - 'run my app on a cloud simulator', 'use eas simulator to run/install/screenshot my app', 'I'm on Linux/Cursor and need an iOS device', 'no sim on this box / headless CI', 'let an agent click through my app and screenshot it', 'test my dev build on a remote sim with live reload', 'stream a sim to my browser' - even when they don't say 'EAS Simulator' or 'cloud'. On a host WITHOUT a local simulator (Linux, CI, cloud sandbox) it's the default; on macOS, do NOT auto-trigger for a plain 'run on the simulator' - use it only for a cloud/remote/shareable sim, an iOS version they lack, or an agent-driven session. NOT for local sims (expo run:ios, Xcode, Android Studio), EAS Build/Update, web preview, or physical devices." }, { "name": "eas-update-insights", - "description": "\"EAS service (paid). Check the health of published EAS Update: crash rates, install/launch counts, unique users, payload size, and the split between embedded and OTA users per channel. Use when the user asks how an update is performing, whether a rollout is healthy, how many users are on the embedded build vs OTA, or wants to gate CI on update health.\"" + "description": "EAS service (paid). Check the health of published EAS Update: crash rates, install/launch counts, unique users, payload size, and the split between embedded and OTA users per channel. Use when the user asks how an update is performing, whether a rollout is healthy, how many users are on the embedded build vs OTA, or wants to gate CI on update health." }, { "name": "eas-workflows", @@ -140,10 +140,6 @@ "name": "expo-animation", "description": "Framework (OSS). Build animations in React Native and Expo, making the decisions in the order that determines whether they feel right β€” should it animate, which thread it runs on, which properties, spring or timing, how the gesture hands off, how it degrades. Writes the implementation with Reanimated, Gesture Handler, Expo Router and expo-haptics. Use when animating anything in an Expo app, adding gestures, sheets, screen transitions, press feedback or haptics, or fixing motion that stutters on device. For web animation use `animate`." }, - { - "name": "expo-app-clip", - "description": "Framework (OSS). Add an iOS App Clip target to an Expo app. Use when the user mentions App Clip, AASA, apple-app-site-association, appclips, smart app banner, or wants to ship a lightweight iOS Clip invoked from a URL alongside their parent app." - }, { "name": "expo-brownfield", "description": "Framework (OSS). Integrate Expo and React Native into an existing native iOS or Android app. Use when the user mentions brownfield, embedding React Native in a native app, AAR/XCFramework, or adding Expo to an existing Kotlin/Swift project. Covers both the isolated approach and the integrated approach." @@ -160,10 +156,6 @@ "name": "expo-dev-client", "description": "Framework (OSS). Build and distribute Expo development clients locally or via TestFlight for internal testing. For production TestFlight releases and store submission, use the eas-app-stores skill." }, - { - "name": "expo-dom", - "description": "Framework (OSS). Use Expo DOM components to run web code in a webview on native and as-is on web. Migrate web code to native incrementally. For the end-to-end migration of a whole web app, use the expo-web-to-native skill." - }, { "name": "expo-examples", "description": "Framework (OSS). Expo's official example projects - the expo/examples repo of ~70 `with-*` integrations (Stripe, Clerk, Supabase, OpenAI, maps, Reanimated, SQLite, Skia, NativeWind, and more). Use when integrating a third-party library or service into an existing Expo app and you want the canonical, version-matched pattern to adapt, or when scaffolding a new project from one with `npx create-expo --example`." @@ -178,7 +170,7 @@ }, { "name": "expo-overview", - "description": "\"Framework (OSS). Entry point and router for every Expo or EAS task. Load this skill first β€” before writing code and before choosing another expo-* / eas-* skill β€” when the request, PRD, or spec mentions Expo, EAS, Expo Go, or an expo-* package, or the project has an `expo` dependency in `package.json`. Within that gate it also covers app specs and designs to implement (tabs, stacks, maps, lists, navigation, building from a screenshot), and phrasings like 'implement a mobile app', 'make my app look native', 'add navigation', 'fetch some data', 'upgrade my SDK', 'add Expo to my existing native app', 'ship to the App Store', or 'I'm new to Expo, where do I start'. A fully specified request (SDK pinned, libraries named, layout given) still routes through here β€” the shared setup rules still apply. Do NOT load it when neither signal is present: a bare React Native project with no `expo` dependency is not Expo work. Detects the real goal, routes to the right expo-* / eas-* skill, and owns the shared setup rules.\"" + "description": "Framework (OSS). Entry point and router for every Expo or EAS task. Load this skill first β€” before writing code and before choosing another expo-* / eas-* skill β€” when the request, PRD, or spec mentions Expo, EAS, Expo Go, or an expo-* package, or the project has an `expo` dependency in `package.json`. Within that gate it also covers app specs and designs to implement (tabs, stacks, maps, lists, navigation, building from a screenshot), and phrasings like 'implement a mobile app', 'make my app look native', 'add navigation', 'fetch some data', 'upgrade my SDK', 'add Expo to my existing native app', 'ship to the App Store', or 'I'm new to Expo, where do I start'. A fully specified request (SDK pinned, libraries named, layout given) still routes through here β€” the shared setup rules still apply. Do NOT load it when neither signal is present: a bare React Native project with no `expo` dependency is not Expo work. Detects the real goal, routes to the right expo-* / eas-* skill, and owns the shared setup rules." }, { "name": "expo-project-structure", @@ -188,21 +180,13 @@ "name": "expo-router", "description": "Framework (OSS). Navigation and routing for Expo Router. Covers file-based routes, groups and dynamic routes, folder organization, Link with previews and context menus, native Stack, page titles, modals and form sheets, NativeTabs, headers and toolbars, and header search bars." }, - { - "name": "expo-skill-feedback", - "description": "'Submit feedback on an Expo skillβ€”or Expo itselfβ€”and control bundled anonymous usage telemetry (off by default / opt-in). Submit feedback with: npx --yes submit-expo-feedback@latest \"ACTIONABLE_FEEDBACK\". Optionally add either or both: --category \"CATEGORY\" and --subject \"SUBJECT\". Replace the uppercase placeholders before running. Use when a skill was useful, confusing, broken, missing context, or worth improving; when Expo, Expo CLI, EAS CLI, docs, or MCP worked well or fell short; when an AI agent repeatedly failed, got stuck, or needed the user to take over an Expo task (report it as an eval candidate); or when the user explicitly asks to enable or disable telemetry (tracking), check its status, or understand what it collects.'" - }, { "name": "expo-ui", - "description": "\"Framework (OSS). Build native UI with the @expo/ui package: real SwiftUI on iOS and Jetpack Compose on Android. Default to @expo/ui for sheets (BottomSheet), pickers, sliders, toggles, menus, and grouped-form sections β€” do NOT reach for Reanimated, @gorhom/bottom-sheet, or RN built-in Picker/Switch; use @expo/ui instead. Fall back to RN built-ins only when @expo/ui is missing the component. NOTE: @expo/ui List renders native grouped rows like an iOS Settings screen β€” it is NOT a virtualized list; use FlatList/FlashList for large datasets. Covers universal components (Host, Column, Row, Button, Text, List, BottomSheet, FieldGroup, Switch, Slider, Picker, Menu), drop-in replacements for RN community libraries, and platform-specific SwiftUI/Jetpack Compose trees. Not for Expo Router navigation, Reanimated, or data fetching.\"" + "description": "Framework (OSS). Build native UI with the @expo/ui package: real SwiftUI on iOS and Jetpack Compose on Android. Default to @expo/ui for sheets (BottomSheet), pickers, sliders, toggles, menus, and grouped-form sections β€” do NOT reach for Reanimated, @gorhom/bottom-sheet, or RN built-in Picker/Switch; use @expo/ui instead. Fall back to RN built-ins only when @expo/ui is missing the component. NOTE: @expo/ui List renders native grouped rows like an iOS Settings screen β€” it is NOT a virtualized list; use FlatList/FlashList for large datasets. Covers universal components (Host, Column, Row, Button, Text, List, BottomSheet, FieldGroup, Switch, Slider, Picker, Menu), drop-in replacements for RN community libraries, and platform-specific SwiftUI/Jetpack Compose trees. Not for Expo Router navigation, Reanimated, or data fetching." }, { "name": "expo-upgrade", "description": "Framework (OSS). Guidelines for upgrading Expo SDK versions and fixing dependency issues" - }, - { - "name": "expo-web-to-native", - "description": "Framework (OSS). Migrate an existing web React app to a native iOS/Android app with Expo. Use when the user wants to turn a website into a mobile app, port a Next.js/Vite/CRA React codebase to React Native, reuse web code on native incrementally, or asks how web idioms (the DOM, CSS, React Router, localStorage, window) map to native. This is the end-to-end migration guide; use the `expo-dom` skill for the DOM-component mechanism itself." } ] } @@ -210,8 +194,7 @@ "libraries": { "@callstack/react-native-brownfield": { "skillRefs": [ - "callstackincubator/agent-skills:react-native-brownfield-migration", - "expo/skills:expo-brownfield" + "callstackincubator/agent-skills:react-native-brownfield-migration" ] }, "@expo/server": { @@ -256,14 +239,13 @@ "skillRefs": [ "callstackincubator/agent-skills:react-native-best-practices", "expo/skills:expo-animation", - "expo/skills:expo-app-clip", + "expo/skills:expo-brownfield", "expo/skills:expo-data-fetching", "expo/skills:expo-design-system", "expo/skills:expo-examples", "expo/skills:expo-native-ui", "expo/skills:expo-overview", "expo/skills:expo-project-structure", - "expo/skills:expo-skill-feedback", "expo/skills:expo-upgrade", "vercel-labs/agent-skills:deploy-to-vercel", "vercel-labs/agent-skills:vercel-cli-with-tokens", @@ -381,12 +363,6 @@ "software-mansion-labs/skills:react-native-best-practices" ] }, - "react-native-web": { - "skillRefs": [ - "expo/skills:expo-dom", - "expo/skills:expo-web-to-native" - ] - }, "react-native-worklets": { "skillRefs": [ "software-mansion-labs/skills:react-native-best-practices" diff --git a/test/fixtures/expo-router-app/package.json b/test/fixtures/expo-router-app/package.json index 3e88ce2..18764f1 100644 --- a/test/fixtures/expo-router-app/package.json +++ b/test/fixtures/expo-router-app/package.json @@ -6,8 +6,7 @@ "@expo/ui": "^0.2.0", "expo-dev-client": "^6.0.0", "expo-router": "^7.0.0", - "expo-updates": "^30.0.0", - "react-native-web": "^0.21.0" + "expo-updates": "^30.0.0" }, "devDependencies": { "eas-cli": "^18.0.0" diff --git a/test/index.e2e.test.ts b/test/index.e2e.test.ts index c149bf9..6b523d5 100644 --- a/test/index.e2e.test.ts +++ b/test/index.e2e.test.ts @@ -87,14 +87,13 @@ describe('pkg-skills e2e', () => { ['callstackincubator/agent-skills', 'react-native-best-practices'], ['callstackincubator/agent-skills', 'upgrading-react-native'], ['expo/skills', 'expo-animation'], - ['expo/skills', 'expo-app-clip'], + ['expo/skills', 'expo-brownfield'], ['expo/skills', 'expo-data-fetching'], ['expo/skills', 'expo-design-system'], ['expo/skills', 'expo-examples'], ['expo/skills', 'expo-native-ui'], ['expo/skills', 'expo-overview'], ['expo/skills', 'expo-project-structure'], - ['expo/skills', 'expo-skill-feedback'], ['expo/skills', 'expo-upgrade'], ['software-mansion-labs/skills', 'radon-mcp'], ['vercel-labs/agent-skills', 'deploy-to-vercel'], @@ -123,10 +122,8 @@ describe('pkg-skills e2e', () => { ['expo/skills', 'eas-update-insights'], ['expo/skills', 'eas-workflows'], ['expo/skills', 'expo-dev-client'], - ['expo/skills', 'expo-dom'], ['expo/skills', 'expo-router'], ['expo/skills', 'expo-ui'], - ['expo/skills', 'expo-web-to-native'], ], expectedRemovals: [], command: ['auto'], @@ -144,7 +141,6 @@ describe('pkg-skills e2e', () => { 'callstackincubator/agent-skills', 'react-native-brownfield-migration', ], - ['expo/skills', 'expo-brownfield'], ], expectedRemovals: [], command: ['auto'], @@ -183,7 +179,6 @@ describe('pkg-skills e2e', () => { 'callstackincubator/agent-skills', 'react-native-brownfield-migration', ], - ['expo/skills', 'expo-brownfield'], ], expectedRemovals: [], command: ['auto'], @@ -215,14 +210,13 @@ describe('pkg-skills e2e', () => { ['callstackincubator/agent-skills', 'react-native-best-practices'], ['callstackincubator/agent-skills', 'upgrading-react-native'], ['expo/skills', 'expo-animation'], - ['expo/skills', 'expo-app-clip'], + ['expo/skills', 'expo-brownfield'], ['expo/skills', 'expo-data-fetching'], ['expo/skills', 'expo-design-system'], ['expo/skills', 'expo-examples'], ['expo/skills', 'expo-native-ui'], ['expo/skills', 'expo-overview'], ['expo/skills', 'expo-project-structure'], - ['expo/skills', 'expo-skill-feedback'], ['expo/skills', 'expo-upgrade'], ['software-mansion-labs/skills', 'radon-mcp'], ['vercel-labs/agent-skills', 'deploy-to-vercel'], @@ -256,14 +250,13 @@ describe('pkg-skills e2e', () => { ['callstackincubator/agent-skills', 'react-native-best-practices'], ['callstackincubator/agent-skills', 'upgrading-react-native'], ['expo/skills', 'expo-animation'], - ['expo/skills', 'expo-app-clip'], + ['expo/skills', 'expo-brownfield'], ['expo/skills', 'expo-data-fetching'], ['expo/skills', 'expo-design-system'], ['expo/skills', 'expo-examples'], ['expo/skills', 'expo-native-ui'], ['expo/skills', 'expo-overview'], ['expo/skills', 'expo-project-structure'], - ['expo/skills', 'expo-skill-feedback'], ['expo/skills', 'expo-upgrade'], ['software-mansion-labs/skills', 'radon-mcp'], ['vercel-labs/agent-skills', 'deploy-to-vercel'], @@ -546,14 +539,13 @@ describe('pkg-skills e2e', () => { ['callstackincubator/agent-skills', 'react-native-best-practices'], ['callstackincubator/agent-skills', 'upgrading-react-native'], ['expo/skills', 'expo-animation'], - ['expo/skills', 'expo-app-clip'], + ['expo/skills', 'expo-brownfield'], ['expo/skills', 'expo-data-fetching'], ['expo/skills', 'expo-design-system'], ['expo/skills', 'expo-examples'], ['expo/skills', 'expo-native-ui'], ['expo/skills', 'expo-overview'], ['expo/skills', 'expo-project-structure'], - ['expo/skills', 'expo-skill-feedback'], ['expo/skills', 'expo-upgrade'], ['software-mansion-labs/skills', 'radon-mcp'], ['vercel-labs/agent-skills', 'deploy-to-vercel'],