@astryxdesign/cli 0.1.7 → 0.1.8-canary.0c4d8d7

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (134) hide show
  1. package/CHANGELOG.md +40 -0
  2. package/README.md +115 -19
  3. package/docs/cli-integrations.doc.mjs +150 -0
  4. package/docs/elevation.doc.mjs +79 -1
  5. package/docs/getting-started.doc.mjs +9 -9
  6. package/docs/migration.doc.mjs +18 -18
  7. package/docs/principles.doc.dense.mjs +1 -1
  8. package/docs/principles.doc.mjs +6 -6
  9. package/docs/principles.doc.zh.mjs +1 -1
  10. package/docs/styling-libraries.doc.mjs +3 -3
  11. package/docs/styling.doc.mjs +4 -4
  12. package/docs/theme.doc.dense.mjs +2 -2
  13. package/docs/theme.doc.mjs +7 -7
  14. package/docs/theme.doc.zh.mjs +1 -1
  15. package/docs/tokens.doc.mjs +1 -1
  16. package/docs/working-with-ai.doc.mjs +19 -19
  17. package/package.json +13 -11
  18. package/scripts/postinstall.mjs +74 -0
  19. package/src/api/doctor.mjs +3 -3
  20. package/src/api/error.mjs +2 -2
  21. package/src/codemods/__tests__/registry.test.mjs +1 -0
  22. package/src/codemods/ensure-jscodeshift.mjs +11 -27
  23. package/src/codemods/registry.mjs +1 -0
  24. package/src/codemods/run-codemod.mjs +1 -1
  25. package/src/codemods/runner.mjs +2 -2
  26. package/src/codemods/transforms/v0.1.0/__tests__/drop-xds-prefix-imports.test.mjs +42 -7
  27. package/src/codemods/transforms/v0.1.0/__tests__/migrate-xds-module-specifiers.test.mjs +43 -0
  28. package/src/codemods/transforms/v0.1.0/drop-xds-prefix-imports.mjs +102 -3
  29. package/src/codemods/transforms/v0.1.0/migrate-xds-module-specifiers.mjs +72 -6
  30. package/src/codemods/transforms/v0.1.8/__tests__/rename-avatar-size-scale.test.mjs +161 -0
  31. package/src/codemods/transforms/v0.1.8/index.mjs +19 -0
  32. package/src/codemods/transforms/v0.1.8/rename-avatar-size-scale.mjs +234 -0
  33. package/src/commands/agent-docs.mjs +136 -20
  34. package/src/commands/agent-docs.test.mjs +123 -10
  35. package/src/commands/build-theme.mjs +10 -71
  36. package/src/commands/build.mjs +15 -15
  37. package/src/commands/cli-postinstall.test.mjs +42 -0
  38. package/src/commands/component/index.mjs +4 -4
  39. package/src/commands/discover.mjs +7 -5
  40. package/src/commands/docs.mjs +4 -4
  41. package/src/commands/hook/index.mjs +4 -4
  42. package/src/commands/init.mjs +48 -152
  43. package/src/commands/init.next-steps.test.mjs +1 -1
  44. package/src/commands/interactive-guard.test.mjs +19 -22
  45. package/src/commands/json-contract.test.mjs +2 -2
  46. package/src/commands/layout.mjs +1 -1
  47. package/src/commands/search.mjs +4 -4
  48. package/src/commands/setup-nudge.test.mjs +108 -0
  49. package/src/commands/swizzle.mjs +12 -35
  50. package/src/commands/template.mjs +11 -31
  51. package/src/commands/upgrade.mjs +138 -32
  52. package/src/commands/upgrade.test.mjs +155 -1
  53. package/src/index.mjs +40 -6
  54. package/src/lib/cli-error.mjs +2 -2
  55. package/src/lib/component-format.mjs +2 -1
  56. package/src/lib/json-shim.mjs +1 -1
  57. package/src/lib/json.mjs +3 -3
  58. package/src/lib/term-log.mjs +48 -0
  59. package/src/types/api.d.ts +4 -9
  60. package/src/types/base.d.ts +24 -3
  61. package/src/types/build.d.ts +23 -0
  62. package/src/types/index.d.ts +1 -0
  63. package/src/types/swizzle.d.ts +4 -0
  64. package/src/types/template-api.d.ts +4 -1
  65. package/src/types/upgrade.d.ts +39 -2
  66. package/src/utils/package-manager.mjs +108 -4
  67. package/src/utils/package-manager.test.mjs +108 -1
  68. package/src/utils/path-safety.mjs +0 -18
  69. package/src/utils/update-check.mjs +5 -4
  70. package/src/utils/update-check.test.mjs +3 -3
  71. package/templates/blocks/components/Avatar/AvatarFallbackChain.tsx +4 -4
  72. package/templates/blocks/components/Avatar/AvatarGroup.tsx +2 -2
  73. package/templates/blocks/components/Avatar/AvatarInitialsFallback.tsx +1 -1
  74. package/templates/blocks/components/Avatar/AvatarShowcase.tsx +4 -4
  75. package/templates/blocks/components/Avatar/AvatarUserCard.tsx +1 -1
  76. package/templates/blocks/components/Avatar/AvatarWithImage.tsx +4 -4
  77. package/templates/blocks/components/Avatar/AvatarWithStatus.tsx +3 -3
  78. package/templates/blocks/components/AvatarGroup/AvatarGroupShowcase.tsx +2 -2
  79. package/templates/blocks/components/AvatarGroupOverflow/AvatarGroupOverflowCustomText.tsx +1 -1
  80. package/templates/blocks/components/AvatarGroupOverflow/AvatarGroupOverflowDefault.tsx +1 -1
  81. package/templates/blocks/components/AvatarGroupOverflow/AvatarGroupOverflowShowcase.tsx +2 -2
  82. package/templates/blocks/components/AvatarStatusDot/AvatarStatusDotShowcase.tsx +3 -3
  83. package/templates/blocks/components/AvatarStatusDot/AvatarStatusDotVariants.tsx +3 -3
  84. package/templates/blocks/components/Banner/BannerFloating.doc.mjs +14 -0
  85. package/templates/blocks/components/Banner/BannerFloating.tsx +16 -0
  86. package/templates/blocks/components/Button/ButtonFloating.doc.mjs +14 -0
  87. package/templates/blocks/components/Button/ButtonFloating.tsx +37 -0
  88. package/templates/blocks/components/ButtonGroup/ButtonGroupFloating.doc.mjs +14 -0
  89. package/templates/blocks/components/ButtonGroup/ButtonGroupFloating.tsx +23 -0
  90. package/templates/blocks/components/Card/CardElevations.doc.mjs +14 -0
  91. package/templates/blocks/components/Card/CardElevations.tsx +32 -0
  92. package/templates/blocks/components/Card/ClickableCardElevated.doc.mjs +14 -0
  93. package/templates/blocks/components/Card/ClickableCardElevated.tsx +21 -0
  94. package/templates/blocks/components/Card/SelectableCardElevated.doc.mjs +14 -0
  95. package/templates/blocks/components/Card/SelectableCardElevated.tsx +37 -0
  96. package/templates/blocks/components/Carousel/CarouselSnap.tsx +1 -1
  97. package/templates/blocks/components/ChatComposer/ChatComposerFlat.doc.mjs +14 -0
  98. package/templates/blocks/components/ChatComposer/ChatComposerFlat.tsx +75 -0
  99. package/templates/blocks/components/ChatMessage/ChatMessageAvatarName.tsx +2 -2
  100. package/templates/blocks/components/ChatMessage/ChatMessageMultiBubble.tsx +1 -1
  101. package/templates/blocks/components/ChatMessageBubble/ChatMessageBubbleGrouping.tsx +1 -1
  102. package/templates/blocks/components/ChatMessageBubble/ChatMessageBubbleMetadata.tsx +1 -1
  103. package/templates/blocks/components/ChatMessageList/ChatMessageListDensity.tsx +5 -9
  104. package/templates/blocks/components/ChatMessageList/ChatMessageListFullFeatured.tsx +1 -1
  105. package/templates/blocks/components/CodeBlock/CodeBlockTerminal.tsx +1 -1
  106. package/templates/blocks/components/HoverCard/HoverCardShowcase.tsx +1 -1
  107. package/templates/blocks/components/IconButton/IconButtonFloating.doc.mjs +14 -0
  108. package/templates/blocks/components/IconButton/IconButtonFloating.tsx +37 -0
  109. package/templates/blocks/components/Item/ItemWithMedia.tsx +2 -2
  110. package/templates/blocks/components/ListItem/ListItemWithMedia.tsx +2 -2
  111. package/templates/blocks/components/OverflowList/OverflowListCappedToolbar.doc.mjs +14 -0
  112. package/templates/blocks/components/OverflowList/OverflowListCappedToolbar.tsx +39 -0
  113. package/templates/blocks/components/OverflowList/OverflowListMultiRowTags.doc.mjs +14 -0
  114. package/templates/blocks/components/OverflowList/OverflowListMultiRowTags.tsx +44 -0
  115. package/templates/blocks/components/Pagination/PaginationDotsCarousel.tsx +2 -6
  116. package/templates/blocks/components/Stack/StackFillItem.tsx +2 -6
  117. package/templates/blocks/components/TabList/TabListTabsWithActions.doc.mjs +1 -1
  118. package/templates/blocks/components/TabList/TabListTabsWithActions.tsx +2 -7
  119. package/templates/blocks/components/Table/TableRowStatusTable.doc.mjs +14 -0
  120. package/templates/blocks/components/Table/TableRowStatusTable.tsx +59 -0
  121. package/templates/blocks/components/Thumbnail/ThumbnailElevated.doc.mjs +14 -0
  122. package/templates/blocks/components/Thumbnail/ThumbnailElevated.tsx +31 -0
  123. package/templates/blocks/components/TypeaheadItem/TypeaheadItemShowcase.tsx +1 -1
  124. package/templates/pages/ai-chat/page.tsx +4 -4
  125. package/templates/pages/dashboard-portfolio/page.tsx +3 -11
  126. package/templates/pages/detail-page/page.tsx +5 -12
  127. package/templates/pages/documentation-design/page.tsx +1 -1
  128. package/templates/pages/messaging-shell/page.tsx +6 -8
  129. package/templates/pages/table-grouped/page.tsx +9 -14
  130. package/templates/pages/table-page/page.tsx +7 -9
  131. package/templates/pages/table-page-heatmap-status/page.tsx +5 -13
  132. package/docs/integration-authoring.md +0 -105
  133. package/src/utils/interactive.mjs +0 -76
  134. package/src/utils/interactive.test.mjs +0 -70
@@ -9,6 +9,6 @@ export const docsZh = {
9
9
  { section: 'Rules', title: '规则', content: [{ type: 'list', items: ['所有支持的场景都使用 XDS 组件', '布局采用框架优先:先选定外壳并规划区域尺寸,再编写内容(见 astryx docs layout)', '密集数据使用行(Table、List/Item)通栏渲染;Card 用于小部件、画廊和设置分组', '使用 StyleX 或 Tailwind 进行样式设置', '使用语义化令牌,不使用硬编码值', '使用 CSS 变量设置颜色,不使用十六进制值', '表单输入为受控组件(value + onChange)', '使用 useLinkComponent() 进行导航'] }] },
10
10
  { section: 'Styling Approach', title: '样式方法', content: [{ type: 'prose', text: '组件覆盖使用 xstyle 属性。布局使用 StyleX 或 Tailwind。详见 astryx docs styling。' }] },
11
11
  { section: 'Anti-Patterns', title: '反模式', content: [{ type: 'list', items: ['不要在原始元素上使用内联样式', '不要硬编码颜色 — 使用令牌或 Tailwind 语义类', '不要硬编码间距', '不要硬编码 <a> 元素 — 使用 useLinkComponent()', '不要把每个列表项都包在 Card 里 — 先定框架,密集数据用行渲染(见 astryx docs layout)', '不要把 Badge 当装饰 — 状态请使用 StatusDot 或 Token', '不要自创属性。先阅读组件文档'] }] },
12
- { section: 'Design Tokens', title: '设计令牌', content: [{ type: 'prose', text: '运行 npx astryx docs tokens 查看完整参考' }] },
12
+ { section: 'Design Tokens', title: '设计令牌', content: [{ type: 'prose', text: '运行 astryx docs tokens 查看完整参考' }] },
13
13
  ],
14
14
  };
@@ -24,7 +24,7 @@ export const docs = {
24
24
  },
25
25
  {
26
26
  type: 'prose',
27
- text: 'For available token names and values, run `npx astryx docs tokens`. Focused references are also available with `npx astryx docs color`, `npx astryx docs spacing`, `npx astryx docs shape`, `npx astryx docs typography`, `npx astryx docs elevation`, and `npx astryx docs motion`.',
27
+ text: 'For available token names and values, run `astryx docs tokens`. Focused references are also available with `astryx docs color`, `astryx docs spacing`, `astryx docs shape`, `astryx docs typography`, `astryx docs elevation`, and `astryx docs motion`.',
28
28
  },
29
29
  ],
30
30
  },
@@ -452,10 +452,10 @@ function RevenueChart({data}: {data: Array<{x: string; y: number}>}) {
452
452
  type: 'list',
453
453
  style: 'ordered',
454
454
  items: [
455
- 'Import the reset/base CSS and a theme CSS file early enough for first paint. For production SSR, prefer built themes from `npx astryx theme build` or published `/built` theme imports plus `theme.css`.',
455
+ 'Import the reset/base CSS and a theme CSS file early enough for first paint. For production SSR, prefer built themes from `astryx theme build` or published `/built` theme imports plus `theme.css`.',
456
456
  'Choose one owner for color mode. Theme uses `data-theme="light|dark"` and `color-scheme` to resolve `light-dark()` tokens.',
457
457
  'Map the external library\'s semantic layer to system variables by intent, not by exact naming. For example, MUI `background.paper` maps to `--color-background-surface`.',
458
- 'Use `npx astryx docs tokens` and focused token docs when building mappings. Keep mappings small at first: text, surface/body/card/popover, border, accent, status, spacing, radius, typography, shadow.',
458
+ 'Use `astryx docs tokens` and focused token docs when building mappings. Keep mappings small at first: text, surface/body/card/popover, border, accent, status, spacing, radius, typography, shadow.',
459
459
  'Use token resolver APIs only for non-CSS APIs that need resolved values.',
460
460
  ],
461
461
  },
@@ -30,7 +30,7 @@ export const docs = {
30
30
  },
31
31
  {
32
32
  type: 'prose',
33
- text: 'All approaches resolve to the same design tokens, so theming and dark mode work regardless of which you choose. For external styling libraries, run `npx astryx docs styling-libraries`; it covers Tailwind, StyleX, Panda, Chakra, MUI, CSS-in-JS, CSS Modules, Sass, and `useTheme()` for non-CSS processing.',
33
+ text: 'All approaches resolve to the same design tokens, so theming and dark mode work regardless of which you choose. For external styling libraries, run `astryx docs styling-libraries`; it covers Tailwind, StyleX, Panda, Chakra, MUI, CSS-in-JS, CSS Modules, Sass, and `useTheme()` for non-CSS processing.',
34
34
  },
35
35
  ],
36
36
  },
@@ -118,7 +118,7 @@ const overrides = stylex.create({
118
118
  },
119
119
  {
120
120
  type: 'prose',
121
- text: 'The bridge is pure CSS with zero JS. Theme changes (dark mode, custom themes) apply automatically because the utilities reference the same CSS custom properties that components use. This is the paved Tailwind path; for other styling libraries that follow the same aliasing pattern, run `npx astryx docs styling-libraries`.',
121
+ text: 'The bridge is pure CSS with zero JS. Theme changes (dark mode, custom themes) apply automatically because the utilities reference the same CSS custom properties that components use. This is the paved Tailwind path; for other styling libraries that follow the same aliasing pattern, run `astryx docs styling-libraries`.',
122
122
  },
123
123
  ],
124
124
  },
@@ -263,7 +263,7 @@ const overrides = stylex.create({
263
263
  },
264
264
  {
265
265
  type: 'prose',
266
- text: 'For systematic theming, use defineTheme component overrides instead of raw CSS selectors. defineTheme keeps the higher-level `prop:value` API (`variant:primary`, `size:sm`) and handles selector generation for you. Run `npx astryx docs theme` for the full theming guide.',
266
+ text: 'For systematic theming, use defineTheme component overrides instead of raw CSS selectors. defineTheme keeps the higher-level `prop:value` API (`variant:primary`, `size:sm`) and handles selector generation for you. Run `astryx docs theme` for the full theming guide.',
267
267
  },
268
268
  ],
269
269
  },
@@ -334,7 +334,7 @@ const styles = stylex.create({
334
334
  },
335
335
  {
336
336
  type: 'prose',
337
- text: 'See `npx astryx docs tokens` for the full token reference (all spacing, color, radius, shadow, and typography tokens with values). See `npx astryx docs theme` for how to override tokens via defineTheme.',
337
+ text: 'See `astryx docs tokens` for the full token reference (all spacing, color, radius, shadow, and typography tokens with values). See `astryx docs theme` for how to override tokens via defineTheme.',
338
338
  },
339
339
  ],
340
340
  },
@@ -10,9 +10,9 @@ export const docsDense = {
10
10
  { section: 'Theme Props', title: 'Props', content: [null] },
11
11
  { section: 'Creating a Custom Theme', title: 'Custom Theme', content: [{ type: 'prose', text: 'CLI wizard or manual defineTheme. only override tokens that differ.' }, null] },
12
12
  { section: 'defineTheme', title: 'defineTheme', content: [{ type: 'prose', text: 'scale configs (color, typography, radius, motion) + explicit token overrides + component overrides. color derives full palette from accent hex via HCT.' }, null, null] },
13
- { section: 'Component Style Overrides', title: 'Component Overrides', content: [{ type: 'prose', text: 'components field uses semantic component keys + style keys (base, variant:value, stateName), not raw selectors. for external CSS, prefer data-* selectors from `astryx docs styling`. write standard CSS (borderRadius, padding) — pipeline expands to internal vars. public vars (--button-press-scale etc) set directly. private vars (--_*) cannot be set — use CSS properties. run `npx astryx component <Name>` for details.' }, null, null, null, null] },
13
+ { section: 'Component Style Overrides', title: 'Component Overrides', content: [{ type: 'prose', text: 'components field uses semantic component keys + style keys (base, variant:value, stateName), not raw selectors. for external CSS, prefer data-* selectors from `astryx docs styling`. write standard CSS (borderRadius, padding) — pipeline expands to internal vars. public vars (--button-press-scale etc) set directly. private vars (--_*) cannot be set — use CSS properties. run `astryx component <Name>` for details.' }, null, null, null, null] },
14
14
  { section: 'Custom Variants', title: 'Custom Variants', content: [{ type: 'prose', text: 'any unknown prop:value in components becomes a new variant. astryx theme build generates TS augmentations. works on any extensible prop axis (variant, status, etc).' }, null, null, null, null] },
15
- { section: 'Building Themes for Production', title: 'Build for Production', content: [{ type: 'prose', text: 'npx astryx theme build compiles defineTheme to static CSS. outputs .css + .js (__built:true) + .d.ts.' }, null, null, null, null] },
15
+ { section: 'Building Themes for Production', title: 'Build for Production', content: [{ type: 'prose', text: 'astryx theme build compiles defineTheme to static CSS. outputs .css + .js (__built:true) + .d.ts.' }, null, null, null, null] },
16
16
  { section: 'Runtime vs Built Themes', title: 'Runtime vs Built', content: [{ type: 'prose', text: 'runtime: useInsertionEffect injects styles client-side. built: static CSS on first paint. USE /built + theme.css FOR SSR.' }, null, null, null] },
17
17
  { section: 'Light/Dark Mode', title: 'Light/Dark', content: [{ type: 'prose', text: 'light-dark() in token values via [light, dark] tuples. mode=system follows OS.' }, null, null] },
18
18
  { section: 'Nesting Themes', title: 'Nesting', content: [{ type: 'prose', text: 'wrap sections in separate <Theme> providers' }, null] },
@@ -148,7 +148,7 @@ function App() {
148
148
  type: 'code',
149
149
  lang: 'bash',
150
150
  label: 'Scaffold with CLI',
151
- code: 'npx astryx theme',
151
+ code: 'astryx theme',
152
152
  },
153
153
  ],
154
154
  },
@@ -287,7 +287,7 @@ const brandTheme = defineTheme({
287
287
  },
288
288
  {
289
289
  type: 'prose',
290
- text: 'Run `npx astryx component <Name>` to see a component\'s theming targets, public CSS variables, and which standard CSS properties are supported.',
290
+ text: 'Run `astryx component <Name>` to see a component\'s theming targets, public CSS variables, and which standard CSS properties are supported.',
291
291
  },
292
292
  {
293
293
  type: 'list',
@@ -361,13 +361,13 @@ const brandTheme = defineTheme({
361
361
  content: [
362
362
  {
363
363
  type: 'prose',
364
- text: '`npx astryx theme build` compiles a defineTheme file into production-ready artifacts. Recommended for SSR apps (Next.js, Remix) where styles must be present on first paint.',
364
+ text: '`astryx theme build` compiles a defineTheme file into production-ready artifacts. Recommended for SSR apps (Next.js, Remix) where styles must be present on first paint.',
365
365
  },
366
366
  {
367
367
  type: 'code',
368
368
  lang: 'bash',
369
369
  label: 'Build a theme',
370
- code: 'npx astryx theme build ./src/themes/ocean.ts',
370
+ code: 'astryx theme build ./src/themes/ocean.ts',
371
371
  },
372
372
  {
373
373
  type: 'prose',
@@ -432,7 +432,7 @@ import './themes/ocean.css';
432
432
  [
433
433
  'Import (custom theme)',
434
434
  'defineTheme() directly',
435
- "Built .js + .css from `npx astryx theme build`",
435
+ "Built .js + .css from `astryx theme build`",
436
436
  ],
437
437
  [
438
438
  'How it works',
@@ -462,7 +462,7 @@ import './themes/ocean.css';
462
462
  items: [
463
463
  'Use the /built subpath + theme.css for production SSR apps.',
464
464
  'Use runtime themes during development for fast iteration.',
465
- 'Run `npx astryx theme build` for custom themes to get the built artifacts.',
465
+ 'Run `astryx theme build` for custom themes to get the built artifacts.',
466
466
  ],
467
467
  },
468
468
  {
@@ -610,7 +610,7 @@ function ChartConfig() {
610
610
  },
611
611
  {
612
612
  type: 'prose',
613
- text: 'See `npx astryx docs styling-libraries` for styling-library interop and `npx astryx docs tokens` for the full token reference.',
613
+ text: 'See `astryx docs styling-libraries` for styling-library interop and `astryx docs tokens` for the full token reference.',
614
614
  },
615
615
  ],
616
616
  },
@@ -10,7 +10,7 @@ export const docsZh = {
10
10
  { section: 'Theme Props', title: 'Theme 属性', content: [null] },
11
11
  { section: 'Creating a Custom Theme', title: '创建自定义主题', content: [{ type: 'prose', text: '使用 CLI 向导(推荐)或手动 defineTheme。只覆盖与默认值不同的令牌。' }, null] },
12
12
  { section: 'defineTheme', title: 'defineTheme', content: [{ type: 'prose', text: '支持比例配置(typography、radius、motion)+ 显式令牌覆盖 + 组件覆盖。' }, null, null] },
13
- { section: 'Building Themes for Production', title: '生产构建', content: [{ type: 'prose', text: 'npx astryx theme build 将 defineTheme 编译为静态 CSS。输出 .css + .js(__built:true)+ .d.ts。' }, null, null, null, null] },
13
+ { section: 'Building Themes for Production', title: '生产构建', content: [{ type: 'prose', text: 'astryx theme build 将 defineTheme 编译为静态 CSS。输出 .css + .js(__built:true)+ .d.ts。' }, null, null, null, null] },
14
14
  { section: 'Runtime vs Built Themes', title: '运行时 vs 构建', content: [{ type: 'prose', text: '运行时:useInsertionEffect 在客户端注入样式。构建:静态 CSS 在首次渲染时就存在。SSR 应用请使用 /built + theme.css。' }, null, null, null] },
15
15
  { section: 'Light/Dark Mode', title: '亮/暗模式', content: [{ type: 'prose', text: "令牌值使用 [light, dark] 元组实现自动模式切换。Theme 上 mode='system'(默认)跟随系统偏好。" }, null, null] },
16
16
  { section: 'Nesting Themes', title: '嵌套主题', content: [{ type: 'prose', text: '将不同部分包裹在独立的 <Theme> 提供者中。' }, null] },
@@ -1068,7 +1068,7 @@ export const docs = {
1068
1068
  },
1069
1069
  {
1070
1070
  "type": "prose",
1071
- "text": "See `npx astryx docs styling` for how to apply tokens via xstyle, className, and compound component patterns. See `npx astryx docs theme` for overriding tokens with defineTheme."
1071
+ "text": "See `astryx docs styling` for how to apply tokens via xstyle, className, and compound component patterns. See `astryx docs theme` for overriding tokens with defineTheme."
1072
1072
  }
1073
1073
  ]
1074
1074
  }
@@ -34,7 +34,7 @@ export const docs = {
34
34
  type: 'code',
35
35
  lang: 'text',
36
36
  label: 'Paste this into your AI',
37
- code: 'Install @astryxdesign/cli and run `npx astryx init --features agents` to set up your Astryx context. Read the generated file.',
37
+ code: 'Install @astryxdesign/cli and run `npx @astryxdesign/cli init --features agents` to set up your Astryx context. Read the generated file.',
38
38
  },
39
39
  {
40
40
  type: 'prose',
@@ -42,15 +42,15 @@ export const docs = {
42
42
  },
43
43
  {
44
44
  type: 'prose',
45
- text: 'If you prefer to target a specific file format:',
45
+ text: 'By default this creates `AGENTS.md` (the tool-agnostic standard most agents read). To target a specific tool\'s file instead:',
46
46
  },
47
47
  {
48
48
  type: 'code',
49
49
  lang: 'bash',
50
50
  label: 'Manual options',
51
- code: `npx astryx init --features agents --agent claude # CLAUDE.md
52
- npx astryx init --features agents --agent cursor # .cursorrules
53
- npx astryx init --features agents --agent codex # AGENTS.md (Copilot, Codex, etc.)`,
51
+ code: `npx @astryxdesign/cli init --features agents --agent claude # .claude/CLAUDE.md
52
+ npx @astryxdesign/cli init --features agents --agent cursor # .cursorrules
53
+ npx @astryxdesign/cli init --features agents --agent codex # AGENTS.md (Copilot, Codex, etc.)`,
54
54
  },
55
55
  ],
56
56
  },
@@ -65,9 +65,9 @@ npx astryx init --features agents --agent codex # AGENTS.md (Copilot, Codex,
65
65
  type: 'list',
66
66
  style: 'ordered',
67
67
  items: [
68
- '`npx astryx template --list`: find a related page pattern to use as reference',
69
- '`npx astryx template <name> --skeleton`: study the layout structure',
70
- '`npx astryx component <Name>`: read props and examples for every component used',
68
+ '`astryx template --list`: find a related page pattern to use as reference',
69
+ '`astryx template <name> --skeleton`: study the layout structure',
70
+ '`astryx component <Name>`: read props and examples for every component used',
71
71
  ],
72
72
  },
73
73
  {
@@ -88,7 +88,7 @@ npx astryx init --features agents --agent codex # AGENTS.md (Copilot, Codex,
88
88
  lang: 'bash',
89
89
  label: 'Install as a Cursor user rule',
90
90
  code: `mkdir -p ~/.cursor/rules
91
- npx astryx init --features agents --agent-docs-path ~/.cursor/rules/xds.mdc`,
91
+ npx @astryxdesign/cli init --features agents --agent-docs-path ~/.cursor/rules/xds.mdc`,
92
92
  },
93
93
  ],
94
94
  },
@@ -109,12 +109,12 @@ npx astryx init --features agents --agent-docs-path ~/.cursor/rules/xds.mdc`,
109
109
  2. How do you make an Dialog non-dismissible?
110
110
  3. What prop does Selector use for its items?
111
111
 
112
- If you don't know all three, run \`npx astryx init --features agents\` to generate agent docs, then read the generated file.`,
112
+ If you don't know all three, run \`npx @astryxdesign/cli init --features agents\` to generate agent docs, then read the generated file.`,
113
113
  },
114
114
  ],
115
115
  },
116
116
  {
117
- title: 'The npx astryx Pattern',
117
+ title: 'The astryx Pattern',
118
118
  content: [
119
119
  {
120
120
  type: 'prose',
@@ -130,16 +130,16 @@ If you don't know all three, run \`npx astryx init --features agents\` to genera
130
130
  },
131
131
  {
132
132
  type: 'prose',
133
- text: 'With this alias, agents use `npx astryx component --list` instead of guessing the binary path. The `--` separator is standard npm convention for passing flags to scripts.',
133
+ text: 'With this alias, agents use `astryx component --list` instead of guessing the binary path. The `--` separator is standard npm convention for passing flags to scripts.',
134
134
  },
135
135
  {
136
136
  type: 'code',
137
137
  lang: 'bash',
138
138
  label: 'Reliable CLI invocation',
139
- code: `npx astryx component --list
140
- npx astryx component Dialog --dense
141
- npx astryx docs styling --dense
142
- npx astryx docs tokens --dense`,
139
+ code: `astryx component --list
140
+ astryx component Dialog --dense
141
+ astryx docs styling --dense
142
+ astryx docs tokens --dense`,
143
143
  },
144
144
  ],
145
145
  },
@@ -154,9 +154,9 @@ npx astryx docs tokens --dense`,
154
154
  type: 'code',
155
155
  lang: 'bash',
156
156
  label: 'Dense output for pasting into AI conversations',
157
- code: `npx astryx component Dialog --dense
158
- npx astryx docs styling --dense
159
- npx astryx docs tokens --dense`,
157
+ code: `astryx component Dialog --dense
158
+ astryx docs styling --dense
159
+ astryx docs tokens --dense`,
160
160
  },
161
161
  ],
162
162
  },
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@astryxdesign/cli",
3
- "version": "0.1.7",
3
+ "version": "0.1.8-canary.0c4d8d7",
4
4
  "displayName": "CLI",
5
5
  "description": "Scaffold projects, browse templates, generate themes, and get agent-ready docs from the command line.",
6
6
  "author": "Meta Open Source",
@@ -67,22 +67,22 @@
67
67
  "files": [
68
68
  "bin",
69
69
  "src",
70
+ "scripts/postinstall.mjs",
70
71
  "templates",
71
72
  "docs",
72
73
  "CHANGELOG.md"
73
74
  ],
74
75
  "dependencies": {
75
- "@clack/prompts": "^1.7.0",
76
76
  "commander": "^12.1.0",
77
77
  "jiti": "^2.7.0",
78
78
  "jscodeshift": "^17.3.0",
79
79
  "zod": "^4.4.3"
80
80
  },
81
81
  "peerDependencies": {
82
- "@astryxdesign/charts": "*",
83
- "@astryxdesign/core": "*",
84
- "@astryxdesign/lab": "*",
85
- "@astryxdesign/theme-neutral": "*",
82
+ "@astryxdesign/charts": "0.1.8-canary.0c4d8d7",
83
+ "@astryxdesign/core": "0.1.8-canary.0c4d8d7",
84
+ "@astryxdesign/lab": "0.1.8-canary.0c4d8d7",
85
+ "@astryxdesign/theme-neutral": "0.1.8-canary.0c4d8d7",
86
86
  "gpt-tokenizer": "^3.4.0"
87
87
  },
88
88
  "peerDependenciesMeta": {
@@ -100,15 +100,17 @@
100
100
  }
101
101
  },
102
102
  "devDependencies": {
103
- "@astryxdesign/charts": "*",
104
- "@astryxdesign/core": "*",
105
- "@astryxdesign/lab": "*",
106
- "@astryxdesign/theme-neutral": "*",
103
+ "@astryxdesign/charts": "0.1.8-canary.0c4d8d7",
104
+ "@astryxdesign/core": "0.1.8-canary.0c4d8d7",
105
+ "@astryxdesign/lab": "0.1.8-canary.0c4d8d7",
106
+ "@astryxdesign/theme-neutral": "0.1.8-canary.0c4d8d7",
107
107
  "gpt-tokenizer": "^3.4.0"
108
108
  },
109
109
  "scripts": {
110
110
  "astryx": "node bin/astryx.mjs",
111
+ "postinstall": "node scripts/postinstall.mjs",
111
112
  "typecheck:template-docs": "tsc --project tsconfig.template-docs.json",
112
- "typecheck:json-api": "tsc --project tsconfig.json-api.json && tsc --project tsconfig.api-contract.json"
113
+ "typecheck:json-api": "tsc --project tsconfig.json-api.json && tsc --project tsconfig.api-contract.json",
114
+ "typecheck:strict": "tsc --project tsconfig.strict.json"
113
115
  }
114
116
  }
@@ -0,0 +1,74 @@
1
+ // Copyright (c) Meta Platforms, Inc. and affiliates.
2
+
3
+ /**
4
+ * @file @astryxdesign/cli postinstall — nudge to run `astryx init`.
5
+ *
6
+ * Enforcement layer 2 of making `astryx init` foolproof: when the CLI is
7
+ * installed as a project dependency and the project hasn't run init yet, print a
8
+ * one-line next-step so agents/humans discover it.
9
+ *
10
+ * Reuses the ONE setup check (isAstryxInitialized from agent-docs.mjs — a
11
+ * dep-free import chain, safe at install time). Non-interactive, never fails the
12
+ * install, and stays quiet:
13
+ * - in the monorepo/source build (not under node_modules),
14
+ * - during npx's transient fetch (npx runs the bin — likely `init` — right
15
+ * after, so nudging here would double up), and
16
+ * - once setup is already done.
17
+ */
18
+
19
+ import {fileURLToPath, pathToFileURL} from 'node:url';
20
+
21
+ const HERE = fileURLToPath(import.meta.url);
22
+
23
+ /**
24
+ * Pure decision: should the postinstall print the setup nudge? Split out so the
25
+ * matrix is unit-testable without an actual npm install.
26
+ *
27
+ * @param {object} opts
28
+ * @param {string} opts.scriptPath - Absolute path of this script (location tells
29
+ * us dependency vs monorepo vs npx-cache).
30
+ * @param {string} [opts.npmCommand] - process.env.npm_command ('install', 'exec', …).
31
+ * @param {boolean} opts.isSetUp - Whether the project already ran init.
32
+ * @returns {boolean}
33
+ */
34
+ export function shouldNudge({scriptPath, npmCommand, isSetUp} = {}) {
35
+ if (!scriptPath || !scriptPath.includes('node_modules')) return false; // monorepo/source build
36
+ if (scriptPath.includes('_npx') || npmCommand === 'exec') return false; // npx transient — init runs next
37
+ if (isSetUp) return false; // already set up — stay quiet
38
+ return true;
39
+ }
40
+
41
+ /** @param {string} root @returns {Promise<boolean>} */
42
+ async function projectIsSetUp(root) {
43
+ try {
44
+ const {isAstryxInitialized} = await import('../src/commands/agent-docs.mjs');
45
+ return isAstryxInitialized(root);
46
+ } catch {
47
+ return false; // best-effort — if the check can't load, fall through and nudge
48
+ }
49
+ }
50
+
51
+ async function main() {
52
+ const root = process.env.INIT_CWD || process.cwd();
53
+ const nudge = shouldNudge({
54
+ scriptPath: HERE,
55
+ npmCommand: process.env.npm_command,
56
+ isSetUp: await projectIsSetUp(root),
57
+ });
58
+ if (nudge) {
59
+ // Scoped package form (`@astryxdesign/cli`) — always resolves to us. Bare
60
+ // `npx astryx` would fetch an unrelated look-alike package (see PR #4151).
61
+ // After that lands, switch this to its getCliInvocation() single source of truth.
62
+ process.stdout.write(
63
+ '\nNext step: run `npx @astryxdesign/cli init` to finish setup and install the Astryx agent prompt.\n\n',
64
+ );
65
+ }
66
+ }
67
+
68
+ // Run only when executed directly (`node scripts/postinstall.mjs`), never when
69
+ // imported by tests.
70
+ if (import.meta.url === pathToFileURL(process.argv[1] || '').href) {
71
+ main()
72
+ .catch(() => {})
73
+ .finally(() => process.exit(0));
74
+ }
@@ -26,7 +26,7 @@ import {createRequire} from 'node:module';
26
26
 
27
27
  import {MIN_NODE_VERSION, isNodeVersionSupported} from '../lib/node-version.mjs';
28
28
  import {CLI_ROOT, findCoreDir} from '../utils/paths.mjs';
29
- import {detectPackageManager} from '../utils/package-manager.mjs';
29
+ import {detectPackageManager, getCliInvocation} from '../utils/package-manager.mjs';
30
30
  import {findConfigPath, Project} from '../lib/project.mjs';
31
31
  import {semverCompare} from '../utils/semver.mjs';
32
32
 
@@ -343,7 +343,7 @@ export function checkAgentDocs(ctx) {
343
343
  label: 'AI agent docs',
344
344
  status: 'info',
345
345
  message: 'No agent docs (CLAUDE.md / AGENTS.md / .cursorrules) found.',
346
- fix: 'Generate agent docs with `astryx init --features agents`.',
346
+ fix: `Generate agent docs with \`${getCliInvocation(ctx.cwd)} init --features agents\`.`,
347
347
  };
348
348
  }
349
349
 
@@ -365,7 +365,7 @@ export function checkAgentDocs(ctx) {
365
365
  label: 'AI agent docs',
366
366
  status: 'warn',
367
367
  message: `Agent docs present (${present.join(', ')}) but no Astryx section markers found.`,
368
- fix: 'Add the Astryx section to your agent docs with `astryx init --features agents`.',
368
+ fix: `Add the Astryx section to your agent docs with \`${getCliInvocation(ctx.cwd)} init --features agents\`.`,
369
369
  };
370
370
  }
371
371
 
package/src/api/error.mjs CHANGED
@@ -14,7 +14,7 @@
14
14
  import {ERROR_CODES} from '../lib/error-codes.mjs';
15
15
 
16
16
  export class AstryxError extends Error {
17
- /** @type {Array<{name: string, reason: string}> | undefined} */
17
+ /** @type {import('../types/base').Suggestion[] | undefined} */
18
18
  suggestions;
19
19
 
20
20
  /**
@@ -26,7 +26,7 @@ export class AstryxError extends Error {
26
26
 
27
27
  /**
28
28
  * @param {string} message
29
- * @param {Array<{name: string, reason: string}>} [suggestions]
29
+ * @param {import('../types/base').Suggestion[]} [suggestions]
30
30
  * @param {string} [code] - Stable error code. Defaults to ERR_UNKNOWN.
31
31
  */
32
32
  constructor(message, suggestions, code) {
@@ -21,6 +21,7 @@ describe('registry', () => {
21
21
  '0.1.3',
22
22
  '0.1.5',
23
23
  '0.1.7',
24
+ '0.1.8',
24
25
  ]);
25
26
  });
26
27
  });
@@ -3,16 +3,15 @@
3
3
  /**
4
4
  * @file Lazy jscodeshift installer
5
5
  *
6
- * Checks if jscodeshift is available and offers to install it on-demand.
6
+ * Checks if jscodeshift is available and installs it on-demand.
7
7
  * Keeps the CLI lean — jscodeshift is only needed for codemods.
8
8
  *
9
- * In non-interactive environments (CI, LLM agents), the interactive prompt
10
- * is skipped. Pass `installDeps: true` to auto-install without prompting,
11
- * or the command will fail with a helpful error message.
9
+ * Non-interactive by default (no prompts): pass `installDeps: true` to
10
+ * auto-install, otherwise the command fails fast with a helpful error.
12
11
  */
13
12
 
14
13
  import {execSync} from 'node:child_process';
15
- import * as p from '@clack/prompts';
14
+ import * as p from '../lib/term-log.mjs';
16
15
  import {detectPackageManager} from '../utils/package-manager.mjs';
17
16
 
18
17
  /**
@@ -30,32 +29,17 @@ export async function ensureJscodeshift({installDeps = false, silent = false} =
30
29
  } catch {
31
30
  log.warn('jscodeshift is required for codemods but not installed.');
32
31
 
33
- const isInteractive = process.stdout.isTTY && !process.env.CI;
34
-
35
32
  if (installDeps) {
36
- // Explicit opt-in — install without prompting
33
+ // Explicit opt-in — install without prompting.
37
34
  return installJscodeshift(silent);
38
35
  }
39
36
 
40
- if (!isInteractive || silent) {
41
- // Non-interactive environment (or --json) fail fast with a helpful message
42
- log.error(
43
- 'Cannot run codemods without jscodeshift. ' +
44
- 'Use --install-deps to auto-install in non-interactive environments.',
45
- );
46
- return false;
47
- }
48
-
49
- // Interactive TTY — prompt as before
50
- const shouldInstall = await p.confirm({
51
- message: 'Install jscodeshift now?',
52
- initialValue: true,
53
- });
54
- if (p.isCancel(shouldInstall) || !shouldInstall) {
55
- p.log.error('Cannot run codemods without jscodeshift.');
56
- return false;
57
- }
58
- return installJscodeshift(silent);
37
+ // Non-interactive by default: fail fast with a helpful message instead of
38
+ // prompting (the CLI never blocks on a TTY).
39
+ log.error(
40
+ 'Cannot run codemods without jscodeshift. Use --install-deps to auto-install.',
41
+ );
42
+ return false;
59
43
  }
60
44
  }
61
45
 
@@ -22,6 +22,7 @@ const registry = new Map([
22
22
  ['0.1.3', () => import('./transforms/v0.1.3/index.mjs')],
23
23
  ['0.1.5', () => import('./transforms/v0.1.5/index.mjs')],
24
24
  ['0.1.7', () => import('./transforms/v0.1.7/index.mjs')],
25
+ ['0.1.8', () => import('./transforms/v0.1.8/index.mjs')],
25
26
  ]);
26
27
 
27
28
  // Re-export from the shared utility so registry callers and other consumers
@@ -29,7 +29,7 @@
29
29
 
30
30
  import * as fs from 'node:fs';
31
31
  import * as path from 'node:path';
32
- import * as p from '@clack/prompts';
32
+ import * as p from '../lib/term-log.mjs';
33
33
  import {findConfigPath} from '../lib/project.mjs';
34
34
  import {fixDirectiveCorruption, validateOutput} from './runner.mjs';
35
35
 
@@ -10,7 +10,7 @@
10
10
 
11
11
  import * as fs from 'node:fs';
12
12
  import * as path from 'node:path';
13
- import * as p from '@clack/prompts';
13
+ import * as p from '../lib/term-log.mjs';
14
14
  import {humanLog} from '../lib/json.mjs';
15
15
  import {runConfigCodemod} from './run-codemod.mjs';
16
16
 
@@ -184,7 +184,7 @@ export async function runCodemods(
184
184
  versionManifests,
185
185
  {apply, path: srcPath, codemod, skipCodemods, silent = false},
186
186
  ) {
187
- // No-op stub object so silent mode skips clack stdout entirely without
187
+ // No-op stub object so silent mode skips log output entirely without
188
188
  // littering the body with `if (!silent)` guards.
189
189
  const log = silent
190
190
  ? {step() {}, info() {}, success() {}, warn() {}, error() {}, message() {}}
@@ -166,15 +166,50 @@ describe('drop-xds-prefix-imports', () => {
166
166
  expect(output).toContain('export const other = Card;');
167
167
  });
168
168
 
169
- it('un-prefixes normally when there is NO local collision', async () => {
169
+ it('un-prefixes override keys inside an @xds/core mock factory', async () => {
170
170
  const input = [
171
- `import {XDSCodeBlock} from '@xds/core/CodeBlock';`,
172
- `export const App = () => <XDSCodeBlock code="x" />;`,
171
+ `vi.mock('@xds/core/Text', async orig => ({`,
172
+ ` ...(await orig<typeof import('@xds/core/Text')>()),`,
173
+ ` useXDSTruncation: () => ({ref: vi.fn(), isTruncated: true, fullText: ''}),`,
174
+ `}));`,
173
175
  ].join('\n');
174
176
  const output = await applyTransform(input);
175
- expect(output).toContain('{CodeBlock}');
176
- expect(output).toContain('@xds/core/CodeBlock');
177
- expect(output).toContain('<CodeBlock code="x" />');
178
- expect(output).not.toContain('AstryxCodeBlock');
177
+ expect(output).toContain('useTruncation:');
178
+ expect(output).not.toContain('useXDSTruncation');
179
+ // This codemod does not touch the module-path string (that is the
180
+ // module-specifiers codemod's job); the @xds/core/Text path stays here.
181
+ expect(output).toContain(`vi.mock('@xds/core/Text'`);
182
+ });
183
+
184
+ it('un-prefixes component override keys inside a bare @xds/core jest.mock factory', async () => {
185
+ const input = [
186
+ `jest.mock('@xds/core', () => ({`,
187
+ ` XDSButton: () => null,`,
188
+ ` useXDSToast: () => ({}),`,
189
+ `}));`,
190
+ ].join('\n');
191
+ const output = await applyTransform(input);
192
+ expect(output).toContain('Button:');
193
+ expect(output).toContain('useToast:');
194
+ expect(output).not.toContain('XDSButton');
195
+ expect(output).not.toContain('useXDSToast');
196
+ });
197
+
198
+ it('does NOT touch mock factory keys for a non-@xds package', async () => {
199
+ const input = [
200
+ `vi.mock('some-other-pkg', () => ({`,
201
+ ` useXDSFoo: () => 1,`,
202
+ `}));`,
203
+ ].join('\n');
204
+ const output = await applyTransform(input);
205
+ expect(output).toBe(input);
206
+ });
207
+
208
+ it('does NOT touch useXDS keys in an unrelated object literal (not a mock factory)', async () => {
209
+ const input = [`const config = {`, ` useXDSWhatever: true,`, `};`].join(
210
+ '\n',
211
+ );
212
+ const output = await applyTransform(input);
213
+ expect(output).toBe(input);
179
214
  });
180
215
  });