admin-ui-starter-kit 0.1.2 → 0.1.3

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,126 @@
1
+ ---
2
+ name: admin-ui-consumer-migration
3
+ description: Use when migrating a React app to consume `admin-ui-starter-kit`, replacing copied local UI folders, auditing exact package imports, deciding what stays app-owned, or wiring framework adapters such as Inertia without recreating local package mirrors.
4
+ ---
5
+
6
+ # Admin UI Consumer Migration
7
+
8
+ Use this skill inside consuming apps, not when editing package internals.
9
+
10
+ Goal: move reusable UI to `admin-ui-starter-kit` imports while keeping
11
+ app-specific routing, APIs, auth, translations, brand assets, and domain logic
12
+ inside the app.
13
+
14
+ ## First reads
15
+
16
+ When available in the installed package or repo, read these before editing:
17
+
18
+ 1. `MIGRATION.md` — migration sequence and deletion rules.
19
+ 2. `COMPONENT_SELECTION.md` — which component/import to use.
20
+ 3. `INTEGRATION.md` — install, provider, exact import, and framework wiring.
21
+ 4. `.agents/skills/component-library-rules/references/components/INDEX.json` —
22
+ searchable component index.
23
+
24
+ Do not bulk-load every component doc. Use the index, then read only the matching
25
+ component file.
26
+
27
+ ## Migration order
28
+
29
+ 1. Install package, stylesheet, and `<UIProvider>`.
30
+ 2. Replace typography, buttons, badges, cards, and simple display components.
31
+ 3. Replace repeated row and metadata patterns with `Item` and `MetadataList`.
32
+ 4. Replace form rows with `FormField` / `ControlledFormField`.
33
+ 5. Replace tables, filters, metrics, comments, overlays, and other features.
34
+ 6. Replace page/header/sidebar layout surfaces last.
35
+ 7. Delete copied local folders only after imports, typecheck, and build are
36
+ clean.
37
+
38
+ ## Exact package imports only
39
+
40
+ Use:
41
+
42
+ ```tsx
43
+ import { Button } from 'admin-ui-starter-kit/base/buttons';
44
+ import { Text } from 'admin-ui-starter-kit/typography';
45
+ import { MetadataList } from 'admin-ui-starter-kit/base/display/metadata';
46
+ import { Comments } from 'admin-ui-starter-kit/features/comments';
47
+ ```
48
+
49
+ Do not use:
50
+
51
+ ```tsx
52
+ import { Button } from '@/components/ui/base/buttons';
53
+ import { Button } from 'admin-ui-starter-kit/base';
54
+ import { MetadataList } from 'admin-ui-starter-kit/base/display/metadata/metadata-list';
55
+ ```
56
+
57
+ If a path is not listed in `package.json` `exports`, treat it as private.
58
+
59
+ ## Adapter rule
60
+
61
+ Adapters are allowed only when they bind app-specific behaviour.
62
+
63
+ Good:
64
+
65
+ ```tsx
66
+ import { Link, router } from '@inertiajs/react';
67
+ import { Button, type ButtonProps } from 'admin-ui-starter-kit/base/buttons';
68
+
69
+ export function InertiaButton({ href, ...props }: ButtonProps & { href: string }) {
70
+ return <Button {...props} onClick={() => router.visit(href)} />;
71
+ }
72
+ ```
73
+
74
+ Bad:
75
+
76
+ ```ts
77
+ export { Button } from 'admin-ui-starter-kit/base/buttons';
78
+ ```
79
+
80
+ Do not create local mirrors, barrels, or aliases that hide package imports.
81
+ Agents should replace call-site imports directly unless a wrapper adds routing,
82
+ auth, API, translation, or domain behaviour.
83
+
84
+ ## Keep app-owned
85
+
86
+ Do not migrate these into package imports or generic wrappers:
87
+
88
+ - Brand/logo assets and product marks.
89
+ - Error boundaries.
90
+ - Auth, permission gates, tenant/workspace selectors.
91
+ - Route-aware global search content and API-backed search.
92
+ - Domain-specific cards/features that encode business rules.
93
+ - Inertia/router/query clients, Wayfinder actions, API calls, app i18n.
94
+ - Page-level data loading and layout orchestration.
95
+
96
+ ## Useful commands
97
+
98
+ Run in the consuming app:
99
+
100
+ ```bash
101
+ npx admin-ui-starter-kit-audit
102
+ npx admin-ui-starter-kit-audit --fix
103
+ npm run typecheck
104
+ npm run build
105
+ ```
106
+
107
+ Use `--fix` only for conservative import rewrites from known copied paths.
108
+ Review the diff before deleting local folders.
109
+
110
+ ## Component selection shortcuts
111
+
112
+ | Need | Import |
113
+ | --- | --- |
114
+ | Text/headings | `admin-ui-starter-kit/typography` |
115
+ | Buttons | `admin-ui-starter-kit/base/buttons` |
116
+ | Badges | `admin-ui-starter-kit/base/badge` |
117
+ | Card shell | `admin-ui-starter-kit/base/cards` |
118
+ | Icon/avatar/title row | `admin-ui-starter-kit/base/item` |
119
+ | Label/value metadata | `admin-ui-starter-kit/base/display/metadata` |
120
+ | Form rows | `admin-ui-starter-kit/base/forms` |
121
+ | Tables | `admin-ui-starter-kit/base/table` |
122
+ | Filters | `admin-ui-starter-kit/features/filters` |
123
+ | Comments | `admin-ui-starter-kit/features/comments` |
124
+ | Dialogs/drawers | `admin-ui-starter-kit/features/overlays` |
125
+ | Metrics/charts | `admin-ui-starter-kit/composed/analytics` |
126
+ | Page/header/sidebar | `admin-ui-starter-kit/layout/page`, `/header`, `/sidebar` |
@@ -56,6 +56,11 @@ These deeper guides live alongside this file under `references/`. The skill stay
56
56
 
57
57
  Don't read all of them — find the matching guide for the immediate task, follow it, return.
58
58
 
59
+ Consumer-app migration guidance lives outside this maintainer skill:
60
+ [`../admin-ui-consumer-migration/SKILL.md`](../admin-ui-consumer-migration/SKILL.md),
61
+ `MIGRATION.md`, and `COMPONENT_SELECTION.md`. Use those when replacing copied
62
+ local UI in downstream apps.
63
+
59
64
  ## Component reference index
60
65
 
61
66
  Every component documented in the showcase has a generated markdown
@@ -99,9 +104,14 @@ to see how the library uses it before writing fresh code.
99
104
  The references are generated from the MDX showcase by
100
105
  `npm run docs:sync-skill`; CI runs the freshness check via `npm run verify`.
101
106
 
102
- ## This is the only skill in this repo
107
+ ## Maintainer skill scope
103
108
 
104
- There are no other skills shipped with this repo. The harness may surface generic skills (`frontend-design`, `shadcn`, `tailwind-v4-shadcn`, etc.) from the user's plugin set, but **this skill is the source of truth for anything inside `src/components/**`**. Where they conflict, this skill wins. The relevant material from those generic skills is inlined here:
109
+ This skill is the source of truth for anything inside `src/components/**`.
110
+ The separate `admin-ui-consumer-migration` skill is for downstream app
111
+ migrations. The harness may surface generic skills (`frontend-design`,
112
+ `shadcn`, `tailwind-v4-shadcn`, etc.) from the user's plugin set, but for this
113
+ package's component internals, this skill wins. The relevant material from
114
+ those generic skills is inlined here:
105
115
 
106
116
  - **Visual taste / "escape generic AI aesthetics"** → see rule 16 (visual evaluation) and the "Tone for visual work" section in `AGENTS.md`. The library voice is calm, neutral, dense without being cramped — admin density, not marketing flash.
107
117
  - **Adding a shadcn primitive** → step 0 of [`references/base-wrapper.md`](references/base-wrapper.md): `npx shadcn@latest add <primitive>` lands the file in `src/components/ui/<primitive>.tsx`. Then write the wrapper.
package/AGENTS.md CHANGED
@@ -43,7 +43,7 @@ Read it once for context; it is not a live roadmap.
43
43
 
44
44
  ## Mandatory reading before any code change
45
45
 
46
- The **only** mandatory skill in this repo is
46
+ The **only** mandatory maintainer skill in this repo is
47
47
  [`component-library-rules`](.agents/skills/component-library-rules/SKILL.md).
48
48
  It encodes 24 rules covering layer order, typography, density, tokens,
49
49
  strings/i18n, framework-agnostic contracts, slot/render-prop
@@ -59,9 +59,11 @@ pages, ui-provider, consumer wiring, import paths, visual evaluation,
59
59
  testing, layout, composed-domains. Pull the matching one when the work
60
60
  fits; don't read all of them.
61
61
 
62
- This skill is **self-sufficient by design** — there are no other skills
63
- shipped with this repo. If a topic isn't covered, ask before inventing
64
- a pattern.
62
+ The repo also ships
63
+ [`admin-ui-consumer-migration`](.agents/skills/admin-ui-consumer-migration/SKILL.md)
64
+ for downstream apps migrating copied UI into the package. Use that skill in
65
+ consumer repos; use `component-library-rules` when changing this package.
66
+ If a topic isn't covered, ask before inventing a pattern.
65
67
 
66
68
  ## The architectural layers
67
69
 
@@ -0,0 +1,115 @@
1
+ # Component Selection Guide
2
+
3
+ Use this when deciding which package entrypoint belongs at a call site.
4
+
5
+ Public imports are exact. If a path is not listed in `package.json` `exports`
6
+ or documented here, treat it as private.
7
+
8
+ ## Common decisions
9
+
10
+ | Need | Use | Import |
11
+ | --- | --- | --- |
12
+ | Text, headings, labels, links | `Text`, `Heading`, `Label`, `TextLink` | `admin-ui-starter-kit/typography` |
13
+ | Button or icon button | `Button`, `TextButton`, button variants | `admin-ui-starter-kit/base/buttons` |
14
+ | Status pill or count badge | `Badge` | `admin-ui-starter-kit/base/badge` |
15
+ | Card shell | `SmartCard` | `admin-ui-starter-kit/base/cards` |
16
+ | Row with icon/avatar/title/meta/actions | `Item` family | `admin-ui-starter-kit/base/item` |
17
+ | Label/value metadata | `MetadataList` | `admin-ui-starter-kit/base/display/metadata` |
18
+ | Tooltip, avatar, separator, inline stat | display family | `admin-ui-starter-kit/base/display` |
19
+ | Form label/control/error row | `FormField`, `ControlledFormField` | `admin-ui-starter-kit/base/forms` |
20
+ | Inputs, selects, textareas, QR fields | form field exports | `admin-ui-starter-kit/base/forms` |
21
+ | Searchable select | `Combobox` | `admin-ui-starter-kit/base/combobox` |
22
+ | Popover menu or command menu | `PopoverMenu`, `Command` | `admin-ui-starter-kit/base/popover-menu`, `admin-ui-starter-kit/base/command` |
23
+ | Dropdown navigation menus | navigation family | `admin-ui-starter-kit/base/navigation` |
24
+ | Data table | `DataTable` | `admin-ui-starter-kit/base/table` |
25
+ | Date picker or date range picker | date picker family | `admin-ui-starter-kit/base/date-pickers` |
26
+ | Map or place autocomplete | map family | `admin-ui-starter-kit/base/map` |
27
+ | KPI cards and charts | analytics surfaces | `admin-ui-starter-kit/composed/analytics` |
28
+ | Commerce/order/product surfaces | commerce surfaces | `admin-ui-starter-kit/composed/commerce` |
29
+ | Timeline | timeline surfaces | `admin-ui-starter-kit/composed/timelines` |
30
+ | Comments panel | `Comments` | `admin-ui-starter-kit/features/comments` |
31
+ | Filter bar/facets | filters feature | `admin-ui-starter-kit/features/filters` |
32
+ | Dialog, drawer, alert dialog | overlays feature | `admin-ui-starter-kit/features/overlays` |
33
+ | Global search shell | global search feature | `admin-ui-starter-kit/features/global-search` |
34
+ | Mentions/typeahead | mentions or suggestions feature | `admin-ui-starter-kit/features/mentions`, `admin-ui-starter-kit/features/suggestions` |
35
+ | Rich text editor | TipTap-backed editor | `admin-ui-starter-kit/features/rich-text-editor` |
36
+ | Page title/actions/breadcrumb chrome | page layout | `admin-ui-starter-kit/layout/page` |
37
+ | Header shell/search/action slots | header layout | `admin-ui-starter-kit/layout/header` |
38
+ | Sidebar shell | sidebar layout | `admin-ui-starter-kit/layout/sidebar` |
39
+
40
+ ## Decision tree
41
+
42
+ 1. Is it plain user-facing text?
43
+ Use `typography`. Do not hand-roll `text-xs text-muted-foreground` spans.
44
+
45
+ 2. Is it a primitive UI control or presentational wrapper?
46
+ Use `base`. Buttons, badges, cards, form fields, popovers, tables, maps,
47
+ display helpers, and item rows live there.
48
+
49
+ 3. Is it a reusable domain-shaped surface built from base components?
50
+ Use `composed`. Metrics, commerce cards, admin rows, timeline surfaces, and
51
+ dense data-display blocks belong there.
52
+
53
+ 4. Does it include feature state, slots, callbacks, or app-level behaviour?
54
+ Use `features`. Comments, filters, search, overlays, mentions, sync, and
55
+ rich text editor live there.
56
+
57
+ 5. Is it page chrome?
58
+ Use `layout`. Keep routing and active-link state in the app.
59
+
60
+ 6. Does it hardcode backend, auth, routing, app translations, or product
61
+ branding?
62
+ Keep it in the app. Wrap package components only where the wrapper binds
63
+ app-specific behaviour.
64
+
65
+ ## Adapter examples
66
+
67
+ Use an app adapter when you bind framework behaviour:
68
+
69
+ ```tsx
70
+ import { router } from '@inertiajs/react';
71
+ import { Button, type ButtonProps } from 'admin-ui-starter-kit/base/buttons';
72
+
73
+ export function InertiaActionButton({
74
+ href,
75
+ method = 'get',
76
+ ...props
77
+ }: ButtonProps & { href: string; method?: 'get' | 'post' | 'put' | 'delete' }) {
78
+ return <Button {...props} onClick={() => router.visit(href, { method })} />;
79
+ }
80
+ ```
81
+
82
+ Do not create app wrappers that only rename or re-export package components:
83
+
84
+ ```ts
85
+ export { Button } from 'admin-ui-starter-kit/base/buttons';
86
+ ```
87
+
88
+ ## Exact import rules
89
+
90
+ Use:
91
+
92
+ ```tsx
93
+ import { Button } from 'admin-ui-starter-kit/base/buttons';
94
+ import { Text } from 'admin-ui-starter-kit/typography';
95
+ import { MetadataList } from 'admin-ui-starter-kit/base/display/metadata';
96
+ ```
97
+
98
+ Do not use:
99
+
100
+ ```tsx
101
+ import { Button } from 'admin-ui-starter-kit/base';
102
+ import { MetadataList } from 'admin-ui-starter-kit/base/display/metadata/metadata-list';
103
+ import { Button } from '@/components/ui/base/buttons';
104
+ ```
105
+
106
+ ## Where agents should look
107
+
108
+ 1. `AGENTS.md`
109
+ 2. `.agents/skills/component-library-rules/SKILL.md`
110
+ 3. `.agents/skills/admin-ui-consumer-migration/SKILL.md`
111
+ 4. `.agents/skills/component-library-rules/references/components/INDEX.json`
112
+ 5. The specific component doc under `references/components/*.md`
113
+ 6. `INTEGRATION.md`
114
+ 7. `MIGRATION.md`
115
+ 8. `package.json` `exports`
package/INTEGRATION.md CHANGED
@@ -2,6 +2,11 @@
2
2
 
3
3
  Use this when installing `admin-ui-starter-kit` into a React app.
4
4
 
5
+ If the app already has a copied local design system, follow
6
+ [`MIGRATION.md`](MIGRATION.md) first. Use
7
+ [`COMPONENT_SELECTION.md`](COMPONENT_SELECTION.md) when deciding which exact
8
+ entrypoint replaces a local component.
9
+
5
10
  ## 1. Install the package and required peers
6
11
 
7
12
  ```bash
@@ -140,6 +145,7 @@ For a quick smoke test:
140
145
  ```bash
141
146
  npm run typecheck
142
147
  npm run build
148
+ npx admin-ui-starter-kit-audit
143
149
  ```
144
150
 
145
151
  If a build reports a missing optional peer, either install the peer listed
package/MIGRATION.md ADDED
@@ -0,0 +1,235 @@
1
+ # Migration Guide
2
+
3
+ Use this when replacing a copied local design system with the published
4
+ `admin-ui-starter-kit` package.
5
+
6
+ The goal is not to move every UI file into npm. The package owns reusable UI.
7
+ The consuming app owns routing, API calls, auth context, translations, brand
8
+ assets, and domain-specific behaviour.
9
+
10
+ ## 1. Install and mount
11
+
12
+ ```bash
13
+ npm install admin-ui-starter-kit react react-dom tailwindcss lucide-react
14
+ ```
15
+
16
+ If the app still uses `lucide-react` v0, upgrade it as part of the migration.
17
+ This package has a lucide v1 peer.
18
+
19
+ Mount the stylesheet and provider once:
20
+
21
+ ```tsx
22
+ import 'admin-ui-starter-kit/style.css';
23
+ import { UIProvider } from 'admin-ui-starter-kit/ui-provider';
24
+
25
+ <UIProvider
26
+ config={{
27
+ money: { defaultCurrency: 'USD', locale: 'en-US' },
28
+ dates: { weekStartsOn: 1 },
29
+ badge: { defaultSize: 'xs' },
30
+ button: { defaultSize: 'sm' },
31
+ }}
32
+ >
33
+ <App />
34
+ </UIProvider>;
35
+ ```
36
+
37
+ ## 2. Replace local primitives first
38
+
39
+ Start with the low-level imports used everywhere. Keep the changes mechanical
40
+ and verify after each group.
41
+
42
+ ```tsx
43
+ import { Button } from 'admin-ui-starter-kit/base/buttons';
44
+ import { Badge } from 'admin-ui-starter-kit/base/badge';
45
+ import { SmartCard } from 'admin-ui-starter-kit/base/cards';
46
+ import { Text, Heading } from 'admin-ui-starter-kit/typography';
47
+ ```
48
+
49
+ Do not create local mirror files that re-export package primitives. Replace the
50
+ call-site imports directly.
51
+
52
+ ## 3. Replace repeated display patterns
53
+
54
+ Use package components for shapes that repeat across the app:
55
+
56
+ ```tsx
57
+ import { MetadataList } from 'admin-ui-starter-kit/base/display/metadata';
58
+ import { Item, ItemContent, ItemMedia, ItemTitle } from 'admin-ui-starter-kit/base/item';
59
+ ```
60
+
61
+ Use `MetadataList` for label/value data, compact summaries, and entity details.
62
+ Use `Item` for rows with media, title, description, metadata, or actions.
63
+
64
+ ## 4. Replace form rows
65
+
66
+ Use the package form shell before moving larger forms:
67
+
68
+ ```tsx
69
+ import { ControlledFormField, Input, Select } from 'admin-ui-starter-kit/base/forms';
70
+ ```
71
+
72
+ Keep form submission, validation schemas, and API mutation wiring in the app.
73
+ The package should own the field row and control presentation, not the backend
74
+ contract.
75
+
76
+ ## 5. Replace tables, filters, metrics, and overlays
77
+
78
+ Move shared app surfaces after primitives are stable:
79
+
80
+ ```tsx
81
+ import { DataTable } from 'admin-ui-starter-kit/base/table';
82
+ import { FilterProvider } from 'admin-ui-starter-kit/features/filters';
83
+ import { MetricGrid } from 'admin-ui-starter-kit/composed/analytics';
84
+ import { Dialog, Drawer } from 'admin-ui-starter-kit/features/overlays';
85
+ ```
86
+
87
+ Install optional peers only for the entrypoints you import. For example,
88
+ `base/table` needs `@tanstack/react-table`, `composed/analytics` needs
89
+ `recharts`, and `features/rich-text-editor` needs TipTap.
90
+
91
+ ## 6. Replace layouts last
92
+
93
+ Migrate page chrome after shared atoms and features are clean:
94
+
95
+ ```tsx
96
+ import { PageHeader, PageActions } from 'admin-ui-starter-kit/layout/page';
97
+ import { Header, HeaderSearch } from 'admin-ui-starter-kit/layout/header';
98
+ import { AppSidebar, SidebarProvider } from 'admin-ui-starter-kit/layout/sidebar';
99
+ ```
100
+
101
+ Route-aware layout state, active navigation logic, auth menus, and application
102
+ search content stay in the app. The package provides the shell and slots.
103
+
104
+ ## 7. Keep app-owned adapters, not local mirrors
105
+
106
+ A consuming app may create adapters for framework wiring, but not local mirrors.
107
+
108
+ Good:
109
+
110
+ ```tsx
111
+ import { Link } from '@inertiajs/react';
112
+ import { PageHeader, type PageHeaderProps } from 'admin-ui-starter-kit/layout/page';
113
+
114
+ export function AppPageHeader(props: PageHeaderProps) {
115
+ return (
116
+ <PageHeader
117
+ {...props}
118
+ renderLink={(href, children) => <Link href={href}>{children}</Link>}
119
+ />
120
+ );
121
+ }
122
+ ```
123
+
124
+ Bad:
125
+
126
+ ```ts
127
+ export { Button } from 'admin-ui-starter-kit/base/buttons';
128
+ export { Text } from 'admin-ui-starter-kit/typography';
129
+ ```
130
+
131
+ The package owns UI. The app owns routing, API calls, auth context, and
132
+ translations.
133
+
134
+ ## 8. Inertia recipe
135
+
136
+ Keep Inertia imports in the consuming app:
137
+
138
+ ```tsx
139
+ import { Link, router } from '@inertiajs/react';
140
+ import { Button } from 'admin-ui-starter-kit/base/buttons';
141
+ import { Comments } from 'admin-ui-starter-kit/features/comments';
142
+ import { PageHeader } from 'admin-ui-starter-kit/layout/page';
143
+
144
+ <PageHeader
145
+ title="Customers"
146
+ actions={<Button onClick={() => router.visit('/customers/create')}>Create</Button>}
147
+ renderLink={(href, children) => <Link href={href}>{children}</Link>}
148
+ />;
149
+
150
+ <Comments
151
+ comments={comments}
152
+ onSubmit={(values) => router.post(route('comments.store'), values)}
153
+ onDelete={(id) => router.delete(route('comments.destroy', id))}
154
+ strings={{
155
+ empty: t('comments.empty'),
156
+ composerPlaceholder: t('comments.placeholder'),
157
+ }}
158
+ />;
159
+ ```
160
+
161
+ Do not add Inertia adapters to the package. Document app adapters locally when
162
+ they bind routes, Wayfinder actions, translations, or app APIs.
163
+
164
+ ## 9. Do not migrate these
165
+
166
+ Keep these in the consuming app:
167
+
168
+ - Brand assets, logos, app icons, and product-specific marks.
169
+ - Error boundaries and app-level exception handling.
170
+ - Route-aware search content and backend-connected global search.
171
+ - Auth menus, permission gates, and tenant/workspace selectors.
172
+ - Domain-specific cards and features that encode business rules.
173
+ - API clients, query caches, Inertia router calls, and Wayfinder route actions.
174
+ - App-specific layout orchestration and page-level data loading.
175
+
176
+ ## 10. Delete copied folders after imports are clean
177
+
178
+ Only delete local copied folders after typecheck and build pass:
179
+
180
+ ```text
181
+ components/ui/base
182
+ components/ui/typography
183
+ components/ui/navigation
184
+ components/layout
185
+ components/features/comments
186
+ components/features/filters
187
+ components/features/metrics
188
+ ```
189
+
190
+ The exact folders vary by app. Delete only copied package equivalents; keep
191
+ app-owned components.
192
+
193
+ ## 11. Hard import rules
194
+
195
+ Use exact public exports:
196
+
197
+ ```tsx
198
+ import { Button } from 'admin-ui-starter-kit/base/buttons';
199
+ import { Text } from 'admin-ui-starter-kit/typography';
200
+ import { MetadataList } from 'admin-ui-starter-kit/base/display/metadata';
201
+ ```
202
+
203
+ Do not use:
204
+
205
+ ```tsx
206
+ import { Button } from '@/components/ui/base/buttons';
207
+ import { Button } from 'admin-ui-starter-kit/base';
208
+ import { MetadataList } from 'admin-ui-starter-kit/base/display/metadata/metadata-list';
209
+ ```
210
+
211
+ Run the package audit in a consumer app:
212
+
213
+ ```bash
214
+ npx admin-ui-starter-kit-audit
215
+ ```
216
+
217
+ Use `--fix` for conservative import rewrites from known copied paths:
218
+
219
+ ```bash
220
+ npx admin-ui-starter-kit-audit --fix
221
+ ```
222
+
223
+ ## 12. Verification
224
+
225
+ After each migration chunk:
226
+
227
+ ```bash
228
+ npm run typecheck
229
+ npm run build
230
+ npx admin-ui-starter-kit-audit
231
+ ```
232
+
233
+ If a package import fails, check `package.json` `exports` in
234
+ `admin-ui-starter-kit`. Public imports are exact keys. Undocumented nested
235
+ paths are private even when declaration files exist under `dist/`.
package/PUBLISHING.md CHANGED
@@ -54,7 +54,12 @@ Read the file list. **Confirm:**
54
54
  - `dist/files/` and `dist/images/` are included for font and map CSS assets.
55
55
  - `style.css.d.ts` is included for strict TypeScript side-effect CSS imports.
56
56
  - `dist/showcase/` is included.
57
- - `README.md`, `INTEGRATION.md`, `LICENSE`, `CHANGELOG.md` are included.
57
+ - `README.md`, `INTEGRATION.md`, `MIGRATION.md`,
58
+ `COMPONENT_SELECTION.md`, `LICENSE`, `CHANGELOG.md` are included.
59
+ - `.agents/skills/component-library-rules/` and
60
+ `.agents/skills/admin-ui-consumer-migration/` are included.
61
+ - `scripts/audit-consumer.mjs`, `scripts/install-skill.mjs`, and
62
+ `scripts/serve-showcase.mjs` are included.
58
63
  - `src/App.css` is included so consumers can copy the token source when
59
64
  building their own Tailwind output.
60
65
  - `node_modules/`, `coverage/`, `.git/`, `dist-ssr/`, the preview app
package/README.md CHANGED
@@ -99,6 +99,11 @@ exactly your tool.**
99
99
  For a full framework and bundler setup walkthrough, see
100
100
  [INTEGRATION.md](INTEGRATION.md).
101
101
 
102
+ Migrating an app that already copied an older local UI tree? Start with
103
+ [MIGRATION.md](MIGRATION.md), then use
104
+ [COMPONENT_SELECTION.md](COMPONENT_SELECTION.md) to choose exact package
105
+ entrypoints.
106
+
102
107
  ### 1. Add the package
103
108
 
104
109
  ```bash
@@ -164,23 +169,37 @@ import { UIProvider } from 'admin-ui-starter-kit/ui-provider';
164
169
 
165
170
  The provider is locked at first mount; pass everything once at the root.
166
171
 
167
- ### 6. Install the AI agent skill (optional)
172
+ ### 6. Install the AI agent skills (optional)
173
+
174
+ This package ships AI skills for Claude Code and other agents:
168
175
 
169
- This package ships a `component-library-rules` skill for Claude Code and other
170
- AI agents. After installing the package:
176
+ - `component-library-rules` maintainer rules for editing this library.
177
+ - `admin-ui-consumer-migration` consumer-app migration rules and guardrails.
178
+
179
+ After installing the package:
171
180
 
172
181
  ```bash
173
182
  npx admin-ui-starter-kit-install-skill
174
183
  ```
175
184
 
176
- Copies the skill into your project's `.claude/skills/component-library-rules/`
177
- and `.agents/skills/component-library-rules/`. Run again to update after a
178
- package upgrade.
185
+ Copies both skills into your project's `.claude/skills/` and
186
+ `.agents/skills/`. Run again to update after a package upgrade.
179
187
 
180
188
  Flags:
189
+ - `--skill=all|component-library-rules|admin-ui-consumer-migration` (default `all`)
181
190
  - `--target=claude|agents|both` (default `both`) — pick which directory to install into.
182
191
  - `--force` — overwrite without prompting.
183
192
 
193
+ For migration checks in a consuming app:
194
+
195
+ ```bash
196
+ npx admin-ui-starter-kit-audit
197
+ npx admin-ui-starter-kit-audit --fix
198
+ ```
199
+
200
+ The audit rejects stale copied imports, invalid package subpaths, and local
201
+ files that only re-export package components.
202
+
184
203
  ---
185
204
 
186
205
  ## Browse the component showcase
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "admin-ui-starter-kit",
3
- "version": "0.1.2",
3
+ "version": "0.1.3",
4
4
  "description": "Opinionated React component kit for admin panels and SaaS dashboards, built on top of shadcn/ui + Tailwind CSS v4. Callback-driven, framework-agnostic, i18n-ready.",
5
5
  "type": "module",
6
6
  "license": "MIT",
@@ -29,6 +29,8 @@
29
29
  "files": [
30
30
  "dist",
31
31
  "README.md",
32
+ "MIGRATION.md",
33
+ "COMPONENT_SELECTION.md",
32
34
  "INTEGRATION.md",
33
35
  "CHANGELOG.md",
34
36
  "CONTRIBUTING.md",
@@ -39,10 +41,13 @@
39
41
  "CLAUDE.md",
40
42
  "AGENTS.md",
41
43
  ".agents/skills/component-library-rules",
44
+ ".agents/skills/admin-ui-consumer-migration",
45
+ "scripts/audit-consumer.mjs",
42
46
  "scripts/install-skill.mjs",
43
47
  "scripts/serve-showcase.mjs"
44
48
  ],
45
49
  "bin": {
50
+ "admin-ui-starter-kit-audit": "./scripts/audit-consumer.mjs",
46
51
  "admin-ui-starter-kit-install-skill": "./scripts/install-skill.mjs",
47
52
  "admin-ui-starter-kit-showcase": "./scripts/serve-showcase.mjs"
48
53
  },
@@ -0,0 +1,326 @@
1
+ #!/usr/bin/env node
2
+ /**
3
+ * audit-consumer — checks a consuming app for common admin-ui-starter-kit
4
+ * migration mistakes:
5
+ *
6
+ * - stale copied import paths such as @/components/ui/base/buttons
7
+ * - invalid package imports not listed in package.json exports
8
+ * - local files that only re-export package components
9
+ *
10
+ * Run from a consumer app:
11
+ *
12
+ * npx admin-ui-starter-kit-audit
13
+ * npx admin-ui-starter-kit-audit --fix
14
+ */
15
+ import { promises as fs } from 'node:fs';
16
+ import path from 'node:path';
17
+ import { fileURLToPath } from 'node:url';
18
+
19
+ const PACKAGE_NAME = 'admin-ui-starter-kit';
20
+ const __filename = fileURLToPath(import.meta.url);
21
+ const PACKAGE_ROOT = path.resolve(path.dirname(__filename), '..');
22
+ const PACKAGE_JSON = JSON.parse(
23
+ await fs.readFile(path.join(PACKAGE_ROOT, 'package.json'), 'utf8'),
24
+ );
25
+ const PUBLIC_EXPORTS = new Set(Object.keys(PACKAGE_JSON.exports ?? {}));
26
+
27
+ const DEFAULT_PATHS = ['src', 'app', 'resources/js', 'components', 'pages'];
28
+ const CODE_EXTENSIONS = new Set(['.cjs', '.cts', '.js', '.jsx', '.mjs', '.mts', '.ts', '.tsx']);
29
+ const SKIP_DIRS = new Set([
30
+ '.git',
31
+ '.next',
32
+ '.nuxt',
33
+ '.turbo',
34
+ '.vercel',
35
+ 'build',
36
+ 'coverage',
37
+ 'dist',
38
+ 'node_modules',
39
+ 'public',
40
+ 'vendor',
41
+ ]);
42
+
43
+ function parseArgs(argv) {
44
+ const opts = { fix: false, json: false, help: false, paths: [] };
45
+ for (const arg of argv.slice(2)) {
46
+ if (arg === '--fix') {
47
+ opts.fix = true;
48
+ } else if (arg === '--json') {
49
+ opts.json = true;
50
+ } else if (arg === '--help' || arg === '-h') {
51
+ opts.help = true;
52
+ } else if (arg.startsWith('--paths=')) {
53
+ opts.paths.push(...arg.slice('--paths='.length).split(',').filter(Boolean));
54
+ } else if (arg.startsWith('-')) {
55
+ throw new Error(`Unknown option: ${arg}`);
56
+ } else {
57
+ opts.paths.push(arg);
58
+ }
59
+ }
60
+ return opts;
61
+ }
62
+
63
+ function printHelp() {
64
+ console.log(
65
+ `Usage: npx ${PACKAGE_NAME}-audit [options] [paths...]\n\n` +
66
+ `Audits a consuming app for stale local UI imports, invalid package subpaths,\n` +
67
+ `and local files that only mirror ${PACKAGE_NAME} exports.\n\n` +
68
+ `Options:\n` +
69
+ ` --fix Rewrite known copied import paths to package imports\n` +
70
+ ` --paths=a,b Comma-separated paths to scan\n` +
71
+ ` --json Print machine-readable JSON\n` +
72
+ ` --help, -h Show this message\n`,
73
+ );
74
+ }
75
+
76
+ async function exists(p) {
77
+ try {
78
+ await fs.access(p);
79
+ return true;
80
+ } catch {
81
+ return false;
82
+ }
83
+ }
84
+
85
+ async function walk(dir, files = []) {
86
+ const entries = await fs.readdir(dir, { withFileTypes: true });
87
+ for (const entry of entries) {
88
+ if (SKIP_DIRS.has(entry.name)) continue;
89
+ const full = path.join(dir, entry.name);
90
+ if (entry.isDirectory()) {
91
+ await walk(full, files);
92
+ } else if (entry.isFile() && CODE_EXTENSIONS.has(path.extname(entry.name))) {
93
+ files.push(full);
94
+ }
95
+ }
96
+ return files;
97
+ }
98
+
99
+ async function getScanFiles(cwd, requestedPaths) {
100
+ const roots = requestedPaths.length > 0 ? requestedPaths : DEFAULT_PATHS;
101
+ const files = [];
102
+ for (const relative of roots) {
103
+ const full = path.resolve(cwd, relative);
104
+ if (!(await exists(full))) continue;
105
+ const stat = await fs.stat(full);
106
+ if (stat.isDirectory()) {
107
+ await walk(full, files);
108
+ } else if (stat.isFile() && CODE_EXTENSIONS.has(path.extname(full))) {
109
+ files.push(full);
110
+ }
111
+ }
112
+ return [...new Set(files)].sort();
113
+ }
114
+
115
+ function getImportSpecifiers(source) {
116
+ const specifiers = [];
117
+ const patterns = [
118
+ /\bimport\s+(?:type\s+)?(?:[^'"]*?\s+from\s+)?['"]([^'"]+)['"]/g,
119
+ /\bexport\s+(?:type\s+)?[^'"]*?\s+from\s+['"]([^'"]+)['"]/g,
120
+ /\bimport\s*\(\s*['"]([^'"]+)['"]\s*\)/g,
121
+ ];
122
+ for (const pattern of patterns) {
123
+ for (const match of source.matchAll(pattern)) specifiers.push(match[1]);
124
+ }
125
+ return specifiers;
126
+ }
127
+
128
+ function normalizePackageKey(specifier) {
129
+ if (specifier === PACKAGE_NAME) return '.';
130
+ if (!specifier.startsWith(`${PACKAGE_NAME}/`)) return null;
131
+ return `.${specifier.slice(PACKAGE_NAME.length)}`;
132
+ }
133
+
134
+ function packageImportIssue(specifier) {
135
+ const key = normalizePackageKey(specifier);
136
+ if (key === null) return null;
137
+ if (PUBLIC_EXPORTS.has(key)) return null;
138
+ return {
139
+ kind: 'invalid-package-import',
140
+ specifier,
141
+ message: `${specifier} is not a public ${PACKAGE_NAME} export`,
142
+ };
143
+ }
144
+
145
+ function mapOldSpecifier(specifier) {
146
+ const typographyPrefixes = [
147
+ '@/components/ui/base/typography',
148
+ '@/components/ui/typography',
149
+ '@/components/typography',
150
+ ];
151
+ if (typographyPrefixes.some((prefix) => specifier === prefix || specifier.startsWith(`${prefix}/`))) {
152
+ return `${PACKAGE_NAME}/typography`;
153
+ }
154
+
155
+ const layerMappings = [
156
+ { prefix: '@/components/ui/base/', target: 'base' },
157
+ { prefix: '@/components/base/', target: 'base' },
158
+ { prefix: '@/components/composed/', target: 'composed' },
159
+ { prefix: '@/components/features/', target: 'features' },
160
+ { prefix: '@/components/layout/', target: 'layout' },
161
+ ];
162
+
163
+ for (const mapping of layerMappings) {
164
+ if (!specifier.startsWith(mapping.prefix)) continue;
165
+ const rest = specifier.slice(mapping.prefix.length);
166
+ const parts = rest.split('/').filter(Boolean);
167
+ if (parts.length === 0) continue;
168
+
169
+ if (mapping.target === 'base' && parts[0] === 'display' && parts[1] === 'metadata') {
170
+ return `${PACKAGE_NAME}/base/display/metadata`;
171
+ }
172
+
173
+ const candidate = `${PACKAGE_NAME}/${mapping.target}/${parts[0]}`;
174
+ if (PUBLIC_EXPORTS.has(normalizePackageKey(candidate))) return candidate;
175
+ }
176
+
177
+ if (specifier === '@/components/layout') return `${PACKAGE_NAME}/layout`;
178
+
179
+ return null;
180
+ }
181
+
182
+ function isKnownCopiedImport(specifier) {
183
+ return mapOldSpecifier(specifier) !== null;
184
+ }
185
+
186
+ function isMirrorFile(source) {
187
+ const withoutComments = source
188
+ .replace(/\/\*[\s\S]*?\*\//g, '')
189
+ .replace(/^\s*\/\/.*$/gm, '')
190
+ .trim();
191
+ if (!withoutComments) return false;
192
+
193
+ const statements = withoutComments
194
+ .split(';')
195
+ .map((statement) => statement.trim())
196
+ .filter(Boolean);
197
+ if (statements.length === 0) return false;
198
+
199
+ return statements.every((statement) =>
200
+ /^export\s+(?:type\s+)?(?:\{[\s\S]*\}|\*)\s+from\s+['"]admin-ui-starter-kit(?:\/[^'"]+)?['"]$/m.test(
201
+ statement,
202
+ ),
203
+ );
204
+ }
205
+
206
+ function replaceSpecifiers(source) {
207
+ let changed = false;
208
+ const next = source.replace(
209
+ /(['"])(@\/components\/(?:ui\/base|ui\/typography|base|typography|composed|features|layout)(?:\/[^'"]*)?)\1/g,
210
+ (match, quote, specifier) => {
211
+ const replacement = mapOldSpecifier(specifier);
212
+ if (!replacement) return match;
213
+ changed = true;
214
+ return `${quote}${replacement}${quote}`;
215
+ },
216
+ );
217
+ return { changed, source: next };
218
+ }
219
+
220
+ async function audit(opts) {
221
+ const cwd = process.cwd();
222
+ const files = await getScanFiles(cwd, opts.paths);
223
+ const issues = [];
224
+ const fixedFiles = [];
225
+
226
+ for (const file of files) {
227
+ const source = await fs.readFile(file, 'utf8');
228
+ const relative = path.relative(cwd, file);
229
+ const specifiers = getImportSpecifiers(source);
230
+
231
+ for (const specifier of specifiers) {
232
+ if (isKnownCopiedImport(specifier)) {
233
+ issues.push({
234
+ kind: 'stale-local-import',
235
+ file: relative,
236
+ specifier,
237
+ suggested: mapOldSpecifier(specifier),
238
+ message: `${specifier} should import directly from ${PACKAGE_NAME}`,
239
+ });
240
+ }
241
+
242
+ const invalidPackageImport = packageImportIssue(specifier);
243
+ if (invalidPackageImport) {
244
+ issues.push({ ...invalidPackageImport, file: relative });
245
+ }
246
+ }
247
+
248
+ if (isMirrorFile(source)) {
249
+ issues.push({
250
+ kind: 'local-package-mirror',
251
+ file: relative,
252
+ message: 'local file only re-exports admin-ui-starter-kit; replace call-site imports directly',
253
+ });
254
+ }
255
+
256
+ if (opts.fix) {
257
+ const replacement = replaceSpecifiers(source);
258
+ if (replacement.changed) {
259
+ await fs.writeFile(file, replacement.source);
260
+ fixedFiles.push(relative);
261
+ }
262
+ }
263
+ }
264
+
265
+ return { filesScanned: files.length, issues, fixedFiles: [...new Set(fixedFiles)].sort() };
266
+ }
267
+
268
+ function printReport(result, opts) {
269
+ if (opts.json) {
270
+ console.log(JSON.stringify(result, null, 2));
271
+ return;
272
+ }
273
+
274
+ if (result.fixedFiles.length > 0) {
275
+ console.log(`Rewrote imports in ${result.fixedFiles.length} file(s):`);
276
+ for (const file of result.fixedFiles) console.log(` - ${file}`);
277
+ console.log('');
278
+ }
279
+
280
+ if (result.issues.length === 0) {
281
+ console.log(`Admin UI audit passed (${result.filesScanned} files scanned).`);
282
+ return;
283
+ }
284
+
285
+ console.error(`Admin UI audit found ${result.issues.length} issue(s):`);
286
+ for (const issue of result.issues) {
287
+ const suggestion = issue.suggested ? ` -> ${issue.suggested}` : '';
288
+ console.error(`- ${issue.file}: ${issue.message}${suggestion}`);
289
+ }
290
+
291
+ if (!opts.fix) {
292
+ console.error('\nRun with --fix to rewrite known copied import paths.');
293
+ }
294
+ }
295
+
296
+ async function main() {
297
+ const opts = parseArgs(process.argv);
298
+ if (opts.help) {
299
+ printHelp();
300
+ return;
301
+ }
302
+
303
+ const first = await audit(opts);
304
+ if (!opts.fix || first.fixedFiles.length === 0) {
305
+ printReport(first, opts);
306
+ process.exit(first.issues.length > 0 ? 1 : 0);
307
+ }
308
+
309
+ const second = await audit({ ...opts, fix: false });
310
+ const merged = {
311
+ filesScanned: second.filesScanned,
312
+ issues: second.issues,
313
+ fixedFiles: first.fixedFiles,
314
+ };
315
+ printReport(merged, opts);
316
+ process.exit(second.issues.length > 0 ? 1 : 0);
317
+ }
318
+
319
+ main().catch((error) => {
320
+ if (error instanceof Error) {
321
+ console.error(`admin-ui-starter-kit-audit: ${error.message}`);
322
+ } else {
323
+ console.error(error);
324
+ }
325
+ process.exit(2);
326
+ });
@@ -1,16 +1,15 @@
1
1
  #!/usr/bin/env node
2
2
  /**
3
- * install-skill — consumer-facing installer for the
4
- * `component-library-rules` skill that ships inside the
5
- * `admin-ui-starter-kit` npm package.
3
+ * install-skill — consumer-facing installer for the AI skills that ship
4
+ * inside the `admin-ui-starter-kit` npm package.
6
5
  *
7
6
  * Run from a consumer project (after `npm install admin-ui-starter-kit`):
8
7
  *
9
- * npx admin-ui-starter-kit-install-skill [--target=claude|agents|both] [--force]
8
+ * npx admin-ui-starter-kit-install-skill [--skill=name|all] [--target=claude|agents|both] [--force]
10
9
  *
11
10
  * Default behaviour: copies the skill into BOTH
12
- * <cwd>/.claude/skills/component-library-rules/
13
- * <cwd>/.agents/skills/component-library-rules/
11
+ * <cwd>/.claude/skills/<skill-name>/
12
+ * <cwd>/.agents/skills/<skill-name>/
14
13
  *
15
14
  * The script is idempotent. It refuses to run inside the library itself
16
15
  * (the maintainer has `scripts/publish-skill.mjs` for that workflow).
@@ -20,18 +19,16 @@ import path from 'node:path';
20
19
  import readline from 'node:readline';
21
20
  import { fileURLToPath } from 'node:url';
22
21
 
23
- const SKILL_NAME = 'component-library-rules';
22
+ const SKILL_NAMES = ['component-library-rules', 'admin-ui-consumer-migration'];
24
23
  const PACKAGE_NAME = 'admin-ui-starter-kit';
25
24
 
26
25
  const __filename = fileURLToPath(import.meta.url);
27
26
  // scripts/install-skill.mjs lives at <pkg>/scripts/, so package root is one up.
28
27
  const PACKAGE_ROOT = path.resolve(path.dirname(__filename), '..');
29
- const SOURCE = path.join(PACKAGE_ROOT, '.agents', 'skills', SKILL_NAME);
30
-
31
28
  const CWD = process.cwd();
32
29
 
33
30
  function parseArgs(argv) {
34
- const opts = { target: 'both', force: false, help: false };
31
+ const opts = { target: 'both', skill: 'all', force: false, help: false };
35
32
  for (const arg of argv.slice(2)) {
36
33
  if (arg === '--force' || arg === '-f') {
37
34
  opts.force = true;
@@ -44,6 +41,15 @@ function parseArgs(argv) {
44
41
  process.exit(2);
45
42
  }
46
43
  opts.target = value;
44
+ } else if (arg.startsWith('--skill=')) {
45
+ const value = arg.slice('--skill='.length);
46
+ if (value !== 'all' && !SKILL_NAMES.includes(value)) {
47
+ console.error(
48
+ `✖ Invalid --skill=${value}. Expected one of: all, ${SKILL_NAMES.join(', ')}.`,
49
+ );
50
+ process.exit(2);
51
+ }
52
+ opts.skill = value;
47
53
  } else {
48
54
  console.error(`✖ Unknown argument: ${arg}`);
49
55
  opts.help = true;
@@ -55,8 +61,9 @@ function parseArgs(argv) {
55
61
  function printHelp() {
56
62
  console.log(
57
63
  `Usage: npx ${PACKAGE_NAME}-install-skill [options]\n\n` +
58
- `Installs the "${SKILL_NAME}" skill into the current project.\n\n` +
64
+ `Installs ${PACKAGE_NAME} AI skills into the current project.\n\n` +
59
65
  `Options:\n` +
66
+ ` --skill=all|${SKILL_NAMES.join('|')} Which skill to install (default: all)\n` +
60
67
  ` --target=claude|agents|both Where to install (default: both)\n` +
61
68
  ` --force, -f Overwrite existing skill dir without prompting\n` +
62
69
  ` --help, -h Show this message\n`,
@@ -107,7 +114,7 @@ function confirm(question) {
107
114
  });
108
115
  }
109
116
 
110
- async function installInto(targetDir, force) {
117
+ async function installInto(skillName, sourceDir, targetDir, force) {
111
118
  // Safety: the only thing we ever delete is the existing skill dir at the
112
119
  // exact target path. We never touch the parent directory or siblings.
113
120
  if (await exists(targetDir)) {
@@ -120,8 +127,8 @@ async function installInto(targetDir, force) {
120
127
  }
121
128
  await fs.rm(targetDir, { recursive: true, force: true });
122
129
  }
123
- const count = await copyDir(SOURCE, targetDir);
124
- console.log(` ✓ Installed ${SKILL_NAME} → ${targetDir} (${count} files)`);
130
+ const count = await copyDir(sourceDir, targetDir);
131
+ console.log(` ✓ Installed ${skillName} → ${targetDir} (${count} files)`);
125
132
  return count;
126
133
  }
127
134
 
@@ -143,31 +150,38 @@ async function main() {
143
150
  process.exit(2);
144
151
  }
145
152
 
146
- if (!(await exists(SOURCE))) {
147
- console.error(
148
- `✖ Source skill not found at ${SOURCE}\n` +
149
- ` This usually means the ${PACKAGE_NAME} package is missing the skill files.\n` +
150
- ` Try reinstalling: npm install ${PACKAGE_NAME}@latest`,
151
- );
152
- process.exit(1);
153
- }
153
+ const skillNames = opts.skill === 'all' ? SKILL_NAMES : [opts.skill];
154
154
 
155
- const targets = [];
156
- if (opts.target === 'claude' || opts.target === 'both') {
157
- targets.push(path.join(CWD, '.claude', 'skills', SKILL_NAME));
158
- }
159
- if (opts.target === 'agents' || opts.target === 'both') {
160
- targets.push(path.join(CWD, '.agents', 'skills', SKILL_NAME));
161
- }
155
+ console.log(`Installing ${skillNames.length === 1 ? `"${skillNames[0]}"` : 'AI skills'} from ${PACKAGE_NAME}\n`);
162
156
 
163
- console.log(`Installing "${SKILL_NAME}" from ${PACKAGE_NAME}\n`);
164
- for (const target of targets) {
165
- try {
166
- await installInto(target, opts.force);
167
- } catch (err) {
168
- console.error(` ${target}`);
169
- console.error(` ${err instanceof Error ? err.message : String(err)}`);
157
+ for (const skillName of skillNames) {
158
+ const source = path.join(PACKAGE_ROOT, '.agents', 'skills', skillName);
159
+ if (!(await exists(source))) {
160
+ console.error(
161
+ `✖ Source skill not found at ${source}\n` +
162
+ ` This usually means the ${PACKAGE_NAME} package is missing the skill files.\n` +
163
+ ` Try reinstalling: npm install ${PACKAGE_NAME}@latest`,
164
+ );
170
165
  process.exitCode = 1;
166
+ continue;
167
+ }
168
+
169
+ const targets = [];
170
+ if (opts.target === 'claude' || opts.target === 'both') {
171
+ targets.push(path.join(CWD, '.claude', 'skills', skillName));
172
+ }
173
+ if (opts.target === 'agents' || opts.target === 'both') {
174
+ targets.push(path.join(CWD, '.agents', 'skills', skillName));
175
+ }
176
+
177
+ for (const target of targets) {
178
+ try {
179
+ await installInto(skillName, source, target, opts.force);
180
+ } catch (err) {
181
+ console.error(` ✖ ${target}`);
182
+ console.error(` ${err instanceof Error ? err.message : String(err)}`);
183
+ process.exitCode = 1;
184
+ }
171
185
  }
172
186
  }
173
187