@astryx-vue/cli 0.4.2 → 0.4.4

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,53 @@
1
+ # 维护 `@astryx-vue/cli`
2
+
3
+ 这个文件只写给改这个包的人。使用说明在文档站的 `/guide/cli`,包里没有第二份。
4
+
5
+ ## 文本从哪来
6
+
7
+ CLI 不另存一份组件文档。`scripts/generate-catalog.mjs` 读:
8
+
9
+ - `apps/docs/components/demos/*/meta.ts`:名字、分组和顺序
10
+ - `apps/docs/docs/{en,zh}/components/<slug>/`:用法、props、插槽、事件和示例标题
11
+ - `apps/docs/components/demos/<slug>/*.vue`:那些页面指向的示例源码
12
+ - `apps/docs/components/demo-infra/reference/*.json`:anatomy 和无障碍信息
13
+ - `apps/docs/docs/{en,zh}/guide` 和 `themes`:搜索主题和正文,以及 `theme` 用的主题记录、`init` 用的安装命令和代码片段
14
+ - `apps/docs/docs/{en,zh}/guide/migration.md`:`upgrade` 打印的升级小节,和它去源码里找的旧名字
15
+ - `apps/docs/components/playground/pages/*.vue`:模板源码;`templateManifest.ts`:模板标题和分类
16
+ - 每个 `packages/*/package.json`:判断组件属于哪个包、版本和说明
17
+
18
+ 模板的说明和关键词是手写的,在 `packages/cli/src/generate/template-meta/`,每类模板一个文件,由 `template-meta.ts` 合并。它们属于源数据,不是生成物。
19
+
20
+ 示例的标题和说明按语言各存一份,代码两种语言只存一份。两个页面指向同一个 `.vue` 文件;如果一个页面改了代码、另一个没改,生成时直接报错并指出是哪个组件的第几个示例。
21
+
22
+ 结果提交在 `packages/cli/src/generated/catalog.json`。
23
+
24
+ ## 改完文档或模板之后
25
+
26
+ ```sh
27
+ pnpm --filter @astryx-vue/cli generate
28
+ ```
29
+
30
+ `pnpm check` 会跑 `generate:check`。它在这些情况下失败:`catalog.json` 过期、某个包里有 PascalCase 导出没有任何组件文档引用、某个演练场页面在 `template-meta.ts` 里没有元数据。`./icons`、`./locale`、CSS 和包根不算组件导出。
31
+
32
+ 只要 catalog 变了就要有 CLI 的 changeset,否则 npm 上的用户拿到的还是旧目录;`scripts/check-cli-catalog-release.mjs` 会在 PR 里拦住漏发。
33
+
34
+ ## 在本仓库里直接跑
35
+
36
+ ```sh
37
+ pnpm --filter @astryx-vue/cli build
38
+ node packages/cli/bin/astryx-vue.mjs component Button
39
+ node packages/cli/bin/astryx-vue.mjs composable useClipboard
40
+ node packages/cli/bin/astryx-vue.mjs docs guide/dark-mode --dense
41
+ node packages/cli/bin/astryx-vue.mjs template --list
42
+ node packages/cli/bin/astryx-vue.mjs template login-card --skeleton
43
+ node packages/cli/bin/astryx-vue.mjs build "登录页"
44
+ node packages/cli/bin/astryx-vue.mjs search login --type template
45
+ node packages/cli/bin/astryx-vue.mjs manifest --json
46
+ ```
47
+
48
+ ## 相关包
49
+
50
+ | 包 | 是什么 |
51
+ | --- | --- |
52
+ | `@astryx-vue/core` | 这个 CLI 记录的那些 Vue 组件 |
53
+ | `@astryx-vue/themes` | Neutral、Butter、Chocolate、Gothic、Matcha、Stone、Y2K |
package/README.md CHANGED
@@ -71,177 +71,25 @@ Global flags:
71
71
  | `--zh` | Simplified Chinese where the docs have a translation. |
72
72
  | `--cwd <dir>` | Project directory. Defaults to the working directory; `template` and `init` write inside it, and `upgrade`/`doctor` read it. |
73
73
 
74
- A composable takes `useClipboard` or `use-clipboard`. `component useClipboard` still prints it, and adds a line that points at the `composable` command. `component --list` lists components only. `search --type` accepts `component`, `composable`, `doc`, or `template`; composable hits carry `domain: "composable"` and a `command` that points at `composable <Name>`. A query that hits a name outranks one that only hits page text. Each result names the package it comes from, ranked by score and then, when scores tie, by domain (component, composable, doc, template) and name, so the order never moves between runs.
75
74
 
76
- `docs` takes a topic id, the same id with `themes/` instead of `theme/`, or a bare name that matches one topic, such as `dark-mode`. `--json` returns `docs.list` or `docs.detail`, and `docs.detail` includes the page `body`. `--depth 2` keeps `#` and `##` and drops every deeper heading with the body under it, which is how a long page stays readable; `headings` in the JSON lists only the headings that survived, and `depth` says what was asked for.
75
+ ## Docs
77
76
 
78
- ## Themes
77
+ The full command reference — JSON envelopes, error codes, template rules, `init` block rules, and the maintainer steps — lives on the docs site: <https://astryx-vue.pages.dev/en/guide/cli>. The site is the only copy of that text; this README stays short on purpose.
79
78
 
80
- `theme` lists the seven shipped themes with their own one-sentence summaries. `theme <name>` prints the four steps that load one: the stylesheet import, the `data-astryx-theme` attribute, how switching or running two side by side works, and how to verify. The steps come from the theme's own docs page and the themes index page; the command writes nothing.
79
+ ## Programmatic use
81
80
 
82
- A theme slug or its display name both resolve, so `theme butter` and `theme Butter` are the same request. `--json` returns `theme.list` or `theme.detail`, and `theme.detail` carries the whole docs page as `body` for an agent that wants the long version.
81
+ ```ts
82
+ import { assertResponse, runCli } from '@astryx-vue/cli'
83
83
 
84
- ## Upgrading
85
-
86
- `upgrade` reads the `@astryx-vue/core` version from the project's `package.json` and compares it with the version this CLI was generated from. It never asks the network.
87
-
88
- - Older: it prints every section of the upgrade guide — what changed, how it was written before, how it is written now, what to do — then looks through the project's own sources for the old names the guide replaces, and lists file and line. A theme target name only counts as an object key (`progressbar: {`), because the same word is also an HTML tag; a property or option name counts as a word, in camelCase or kebab-case.
89
- - Current: it says nothing needs doing and prints the changelog link.
90
- - Not installed, or a range it cannot read as a version: it says that, and either names the install command for the project's package manager or prints every form.
91
-
92
- Both lists come from `apps/docs/docs/{en,zh}/guide/migration.md`: the sections are the page's `###` sections, and the old names are the names its section titles and its `Removed | Use instead` table state.
93
-
94
- ## Doctor
95
-
96
- `doctor` reports five checks and changes nothing:
97
-
98
- | Check | Fails or warns when |
99
- | --- | --- |
100
- | `core-version` | `@astryx-vue/core` is missing (fail), its range cannot be read as a version, or it is older or newer than this CLI's catalog (warn) |
101
- | `themes-installed` | `@astryx-vue/themes` is not a dependency (fail) |
102
- | `vue-version` | `vue` is missing (fail) or below 3.5.0 (warn) |
103
- | `theme-stylesheet` | No `@astryx-vue/themes/*.css` import is found (fail), or the file name is not one of the built-in themes (warn) |
104
- | `theme-attribute` | No `data-astryx-theme` is found (warn; Neutral works without it) |
105
-
106
- Exit code is `0` when every check passed or only warned, and `1` when one failed; the text still prints, and `--json` returns `doctor.report`. Sources are read from the project directory, skipping `node_modules`, build output, and hidden directories.
107
-
108
- ## Templates
109
-
110
- `template --list` prints all 57 playground pages. `--type page` is the only kind today; `--type block` answers with a note instead of an error.
111
-
112
- Every template carries a hand-written one-sentence description and a keyword list in English and Chinese, in `src/generate/template-meta/` (one file per template category). The component list is extracted from the page's `@astryx-vue/core` imports, so it cannot drift from the source. Each record also carries `dependencies`, the npm packages the page imports; a ready page names only `vue` and `@astryx-vue/core`, and the catalog generator fails when a ready page pulls in anything else. That is why `template --list`, `template <slug>`, and their `--json` forms can say exactly what a copied page needs.
113
-
114
- Some pages import a package a project cannot install: five use `@astryx-vue/playground-charts`, a path alias that only exists inside the docs playground. Those are marked `isReady: false` with the reason. A page is ready when every non-relative import it really performs is `vue` or `@astryx-vue/core`; text inside a code sample the page displays, such as `@astryxdesign/core`, is page content and does not count. `--list` and `template <slug>` still show them; copying is refused with `ERR_TEMPLATE_NOT_READY`.
115
-
116
- `template <slug> <path>` writes one file:
117
-
118
- - a path that ends in a file name, or that already names a file, is the target file;
119
- - anything else is a directory and receives `<PascalCaseSlug>.vue`;
120
- - an existing file is only replaced with `--overwrite` (or `-f`), otherwise `ERR_FILE_EXISTS`;
121
- - a path outside the project directory is refused with `ERR_PATH_TRAVERSAL`.
122
-
123
- Copying rewrites the playground's `/template-assets/…` photo paths to an inline grey placeholder and reports how many it replaced, so the file renders without the docs site. It does not copy any asset and does not install anything: it names the packages the project's `package.json` does not list, plus an install command with the lockfile's package manager.
124
-
125
- `--skeleton` prints the component tags with layout attributes only: `gap`, `padding`, `width`, `maxWidth`, `height`, `columns`, `axis`, `justify`, `align`, `density`, `variant`, `hasDivider`, and the `rowGap`/`columnGap`/`contentPadding`/`minHeight` variants. Text, bindings other than layout, event handlers, and native HTML wrappers are dropped, so a page positioned with plain `<div>` reads flatter than it renders. The skeleton is a structure reference, not a compilable file.
126
-
127
- ## Build
128
-
129
- `build "<idea>"` ranks the templates locally: field-weighted keyword matching over slug, title, description, keywords, and the components a page uses, with rare words counting more than common ones. No network, no model, same answer every run. Chinese is matched by character pairs, so no spaces are needed.
130
-
131
- - `TEMPLATE` is the closest page. `OTHER TEMPLATES` is the next two. A page that cannot be copied keeps its place only when it answers the idea clearly better, and it is marked with the reason.
132
- - `COMPONENTS` (up to 6) and `COMPOSABLES` (up to 4) come from the same ranking `search` uses, so the two commands cannot disagree. When the idea names fewer pieces than the limit, the recommended page's own components fill the rest, marked as such.
133
- - A weak match is dropped rather than padded. An idea nothing matches returns no template and says so.
134
- - `build` with no idea prints the workflow: find a template, copy it, then look up each component before writing code.
135
-
136
- ## JSON envelopes
137
-
138
- Success responses are `{ apiVersion: 1, type, data }`. `template`, `build`, and `docs` responses carry `package` for where the records come from (`@astryx-vue/cli` for templates, `@astryx-vue/docs` for pages). Every template entry of `template.list`, `template.show`, and `template.skeleton` carries its own `dependencies` array next to `components`. `component.detail` and `composable.detail` carry the owning package, and every entry of `component.list` and `composable.list` carries its own, because one list can span packages.
139
-
140
- | Type | Command |
141
- | --- | --- |
142
- | `template.list`, `template.show`, `template.skeleton`, `template.copy` | `template` |
143
- | `build.help`, `build.kit` | `build` |
144
- | `component.list`, `component.detail` | `component` |
145
- | `composable.list`, `composable.detail` | `composable` |
146
- | `docs.list`, `docs.detail` | `docs` |
147
- | `search` | `search` |
148
- | `manifest` | `manifest` |
149
- | `theme.list`, `theme.detail` | `theme` |
150
- | `upgrade.report` | `upgrade` |
151
- | `doctor.report` | `doctor` |
152
- | `init.run` | `init` |
153
- | `help`, `version` | `--help`, `--version`, or no arguments with `--json` |
154
-
155
- Exit codes are `0` for success, `1` when the command ran but failed, and `2` for invalid usage. Branch on `code` in the JSON envelope, not on the message text.
156
-
157
- | Code | When |
158
- | --- | --- |
159
- | `ERR_UNKNOWN_TEMPLATE` | No template matches the slug. Up to five close slugs come back as suggestions. |
160
- | `ERR_TEMPLATE_NOT_READY` | The template imports a package the project cannot install. |
161
- | `ERR_FILE_EXISTS` | The target file exists and `--overwrite` was not passed. |
162
- | `ERR_PATH_TRAVERSAL` | A target path resolves outside the project directory. |
163
- | `ERR_INVALID_ARGUMENT` | Two modes were combined, a value is outside its choices, or the idea is blank. |
164
- | `ERR_UNKNOWN_COMPONENT`, `ERR_UNKNOWN_COMPOSABLE`, `ERR_UNKNOWN_TOPIC` | No record matches the name. |
165
- | `ERR_UNKNOWN_AGENT` | `--agent` is not `cursor`, `claude`, `codex`, `hermes`, or `all`. |
166
- | `ERR_UNKNOWN_FEATURE` | `--features` names something other than `agents`. |
167
- | `ERR_UNKNOWN_THEME` | No theme has that slug or display name. |
168
-
169
- `manifest --json` carries the same list per command, so an agent can read the codes without this table.
170
-
171
- ## Agent block
172
-
173
- `init` prints the setup guide — which packages to install (canary ones with `@canary`, once they are on npm), the theme import, the locale snippet, and what to verify — and writes this managed block:
174
-
175
- ```html
176
- <!-- Maintained by astryx-vue, do not edit manually -->
177
- ...
178
- <!-- astryx-vue block end -->
179
- ```
180
-
181
- If the file is missing, the command creates it. If the markers are already there, every run replaces only the block with freshly generated text. Other text stays. If the file exists without the markers, the block is appended. A second run still rewrites the block; it does not skip because the markers are already present. When the generated text matches, the bytes stay the same.
182
-
183
- The block names every package the catalog covers. A package npm does not serve yet (a `private` one) is still named, but with a note and no install command, so nobody is sent to a 404; a published canary package would get the `@canary` specifier. Then it teaches the three-step workflow (`build` to pick a page, `template` to copy it, `component`/`composable`/`docs` before writing code), lists the lookup commands, and says how to run the binary. It does not paste the component catalog.
184
-
185
- The install command in the guide follows the project: the `packageManager` field first, then `pnpm-lock.yaml`, `yarn.lock`, `bun.lock(b)`, or `package-lock.json`. With neither, all four forms are listed. `--features` is optional now; passing `agents` is the same as leaving it out, and any other feature is `ERR_UNKNOWN_FEATURE`.
186
-
187
- Where it writes:
188
-
189
- - no `--agent`: the first agent file the project already has, in the order `AGENTS.md`, `CLAUDE.md`, `.cursorrules`, `.hermes.md`, `HERMES.md`; `AGENTS.md` when none exists;
190
- - `--agent cursor` or `--agent codex`: `AGENTS.md`;
191
- - `--agent claude`: `CLAUDE.md`;
192
- - `--agent hermes`: `.hermes.md`, or `HERMES.md` when the project already has that file;
193
- - `--agent all`: every known file that exists, or `AGENTS.md` when none does;
194
- - `--agent-docs-path <file>`: exactly that file, relative to the project directory. Missing directories are created; a path outside the project is refused with `ERR_PATH_TRAVERSAL`.
195
-
196
- ## Where the text comes from
197
-
198
- The CLI does not keep a second copy of the component docs. `packages/cli/scripts/generate-catalog.mjs` reads:
199
-
200
- - `apps/docs/components/demos/*/meta.ts` for names, groups, and order
201
- - `apps/docs/docs/{en,zh}/components/<slug>/` for usage, props, slots, events, and example titles
202
- - `apps/docs/components/demos/<slug>/*.vue` for the example source those pages point at
203
- - `apps/docs/components/demo-infra/reference/*.json` for anatomy and accessibility
204
- - `apps/docs/docs/{en,zh}/guide` and `themes` for search topics, topic bodies, and the theme records (`theme`), plus the install commands and snippets `init` prints
205
- - `apps/docs/docs/{en,zh}/guide/migration.md` for the upgrade guide's sections and the old names `upgrade` searches for
206
- - `apps/docs/components/playground/pages/*.vue` for the template sources, and `templateManifest.ts` for their titles and categories
207
- - every `packages/*/package.json` for which package owns a component, and its exports when a page does not show an import
208
-
209
- The template descriptions and keywords are hand-written in `packages/cli/src/generate/template-meta/`, one file per template category, and merged by `template-meta.ts`. They are source data, not a generated file.
210
-
211
- Example titles and descriptions are kept once per language, but the example code is stored once for both: both pages point at the same `.vue` file. The generator stops with an error naming the component and the example number if the two pages ever show different code, so one language cannot silently borrow the other's snippet. `examplesFor()` in `packages/cli/src/catalog.ts` is the only place that picks a language back out.
212
-
213
- The result is committed at `packages/cli/src/generated/catalog.json`. `pnpm --filter @astryx-vue/cli build` regenerates it and bundles it into `dist`, which is what the published tarball runs. After a docs or template change, regenerate it and commit the diff:
214
-
215
- ```sh
216
- pnpm --filter @astryx-vue/cli generate
84
+ const result = await runCli(['search', 'dialog', '--json'], { cwd: process.cwd() })
85
+ const found = assertResponse(result.stdout, 'search')
217
86
  ```
218
87
 
219
- `pnpm check` runs `generate:check`. That fails when `catalog.json` is stale, when a PascalCase export of any package is not imported by a documented component, and when a playground page has no metadata in `template-meta.ts`. `./icons`, `./locale`, CSS, and the package root are not component exports.
220
-
221
- `catalog.packages` lists every package the catalog covers (`core`, `themes`, `richtext`, `charts`, `lab`) with its directory, whether it publishes only under the `canary` tag, and whether it is `published`. `published` is false for a `private` package: npm has no version of it, so no output prints an install command for it. A component's `package` is the one that exports it by subpath, else the one that has a source entry for it.
222
-
223
- `swizzle`, `discover`, `gap-report`, `integration`, and `blog` are not part of this package, and there is no MCP server. `theme`, `upgrade`, and `doctor` print guidance and reports; none of them edits a project file.
88
+ `runCli` takes the same arguments as the binary and returns `{ exitCode, stdout, stderr }`. `assertResponse` narrows the envelope to the type you expect.
224
89
 
225
- ## Related packages
90
+ ## Contributing
226
91
 
227
- | Package | What it is |
228
- | --- | --- |
229
- | `@astryx-vue/core` | The Vue components this CLI documents |
230
- | `@astryx-vue/themes` | Neutral, Butter, Chocolate, Gothic, Matcha, Stone, and Y2K |
231
-
232
- ## Try it in this repo
233
-
234
- ```sh
235
- pnpm --filter @astryx-vue/cli build
236
- node packages/cli/bin/astryx-vue.mjs component Button
237
- node packages/cli/bin/astryx-vue.mjs composable useClipboard
238
- node packages/cli/bin/astryx-vue.mjs docs guide/dark-mode --dense
239
- node packages/cli/bin/astryx-vue.mjs template --list
240
- node packages/cli/bin/astryx-vue.mjs template login-card --skeleton
241
- node packages/cli/bin/astryx-vue.mjs build "login page with SSO"
242
- node packages/cli/bin/astryx-vue.mjs search login --type template
243
- node packages/cli/bin/astryx-vue.mjs manifest --json
244
- ```
92
+ Where the text comes from, how to regenerate the catalog, and how to run the command from a checkout are in [CONTRIBUTING.md](./CONTRIBUTING.md).
245
93
 
246
94
  ## License
247
95
 
package/README.zh-CN.md CHANGED
@@ -71,176 +71,25 @@ npm run astryx-vue -- component Button
71
71
  | `--zh` | 文档有中文版时用简体中文。 |
72
72
  | `--cwd <dir>` | 项目目录,默认当前工作目录;`template` 和 `init` 只往这里面写,`upgrade` 和 `doctor` 只在这里读。 |
73
73
 
74
- 组合式函数可写 `useClipboard` 或 `use-clipboard`。`component useClipboard` 也能查到,并多印一行指向 `composable` 命令。`component --list` 只列组件。`search --type` 接受 `component`、`composable`、`doc`、`template`;组合式函数命中的结果是 `domain: "composable"`,`command` 指向 `composable <Name>`。命中名字的结果排在只命中正文的结果前面。每条结果都标出来自哪个包;分数相同时按领域(组件、组合式函数、文档、模板)再按名字排序,跑两次顺序不会变。
75
74
 
76
- `docs` 接受主题 id、把 `theme/` 换成 `themes/` 的写法,或能唯一匹配某个主题的裸名,例如 `dark-mode`。`--json` 返回 `docs.list` 或 `docs.detail`,`docs.detail` 里有整页 `body`。`--depth 2` 只留 `#` 和 `##`,更深的标题连同它下面的正文一起省掉,长页面因此读得下去;JSON 里的 `headings` 只列还在正文里的标题,`depth` 说明调用方要的是第几层。
75
+ ## 完整文档
77
76
 
78
- ## 主题
77
+ 命令的完整说明——JSON 信封、错误码、模板规则、`init` 区块规则、维护者步骤——都在文档站:<https://astryx-vue.pages.dev/guide/cli>。这份正文只在文档站维护,包里的 README 故意保持简短。
79
78
 
80
- `theme` 列出内置的 7 套主题,每套带文档页自己的那句说明。`theme <名字>` 输出接入四步:引哪个样式表、`data-astryx-theme` 写什么、怎么换主题或同时用两套、改完怎么验证。这些文字来自主题自己的文档页和主题索引页,命令不写任何文件。
79
+ ## 在代码里调用
81
80
 
82
- slug 和显示名都能用,`theme butter` 和 `theme Butter` 是同一个请求。`--json` 返回 `theme.list` 或 `theme.detail`,`theme.detail` 里带整页 `body`,供想要长文的助手读。
81
+ ```ts
82
+ import { assertResponse, runCli } from '@astryx-vue/cli'
83
83
 
84
- ## 升级
85
-
86
- `upgrade` 读项目 `package.json` 里 `@astryx-vue/core` 的版本,和这个 CLI 生成时对应的版本比,不联网。
87
-
88
- - 比 CLI 旧:打印升级指南的每一节(变了什么、以前怎么写、现在怎么写、怎么改),再在项目自己的源码里找出指南里被替换掉的旧名字,列出文件和行号。主题挂点名只认对象键(`progressbar: {`),因为同一个词也是 HTML 标签;属性和选项名按单词认,camelCase 和 kebab-case 都算。
89
- - 已经一样:说明不用升级,并给出更新日志链接。
90
- - 没装,或版本写法读不出来:说明情况,并按项目包管理器给出安装命令(读不出包管理器就列全部写法)。
91
-
92
- 这两份内容都来自 `apps/docs/docs/{en,zh}/guide/migration.md`:小节就是页面的 `###` 小节,旧名字来自小节标题和页面里 `Removed | Use instead` 表格。
93
-
94
- ## 体检
95
-
96
- `doctor` 报告五项检查,不改任何文件:
97
-
98
- | 检查 | 什么时候失败或提醒 |
99
- | --- | --- |
100
- | `core-version` | 没装 `@astryx-vue/core`(失败);版本写法读不出来,或比 CLI 的 catalog 旧/新(提醒) |
101
- | `themes-installed` | 没有依赖 `@astryx-vue/themes`(失败) |
102
- | `vue-version` | 没装 `vue`(失败)或低于 3.5.0(提醒) |
103
- | `theme-stylesheet` | 没找到任何 `@astryx-vue/themes/*.css` 引入(失败),或文件名不是内置主题(提醒) |
104
- | `theme-attribute` | 没找到 `data-astryx-theme`(提醒;Neutral 不写也能用) |
105
-
106
- 全部通过或只有提醒时退出码是 `0`,有失败时是 `1`,文本照样打印;`--json` 返回 `doctor.report`。只读项目目录里的源码,跳过 `node_modules`、构建产物和隐藏目录。
107
-
108
- ## 模板
109
-
110
- `template --list` 列出全部 57 个演练场页面。目前 `--type` 只有 `page` 一种;`--type block` 会给一句说明,不报错。
111
-
112
- 每个模板都有一句手写说明和中英两套关键词,放在 `src/generate/template-meta/`,每类模板一个文件。组件清单从页面里 `@astryx-vue/core` 的 import 自动提取,不会和源码脱节。每条记录还带 `dependencies`:这个页面 import 了哪些 npm 包。可复制的模板只会有 `vue` 和 `@astryx-vue/core`,可复制模板若多引一个包,生成 catalog 时直接报错。所以 `template --list`、`template <slug>` 和它们的 `--json` 都能直接说清落地要装什么。
113
-
114
- 有些页面引用了用户项目装不上的包:5 个用 `@astryx-vue/playground-charts`(只存在于文档站演练场的路径别名)。这些标为 `isReady: false` 并写明原因。判断标准是页面真正执行的导入:只要非相对导入都是 `vue` 或 `@astryx-vue/core` 就算可复制;页面里展示的示例代码文本(例如 `@astryxdesign/core`)属于页面内容,不算导入。`--list` 和 `template <slug>` 照样能看,复制会被拒绝,报 `ERR_TEMPLATE_NOT_READY`。
115
-
116
- `template <slug> <path>` 只写一个文件:
117
-
118
- - 以文件名结尾的路径,或本身就指向一个文件的路径,就写这个文件;
119
- - 其余都当作目录,写入 `<PascalCaseSlug>.vue`;
120
- - 已存在的文件要加 `--overwrite`(或 `-f`)才覆盖,否则报 `ERR_FILE_EXISTS`;
121
- - 越出项目目录的路径报 `ERR_PATH_TRAVERSAL`。
122
-
123
- 复制时会把演练场的 `/template-assets/…` 图片地址换成一个内联的灰色占位图,并报告替换了多少处,这样文件不依赖文档站也能渲染。它不复制任何资源文件,也不安装任何东西:只列出项目 `package.json` 里没有的包,再按锁文件对应的包管理器给一条安装命令。
124
-
125
- `--skeleton` 只输出组件标签和布局属性:`gap`、`padding`、`width`、`maxWidth`、`height`、`columns`、`axis`、`justify`、`align`、`density`、`variant`、`hasDivider`,以及 `rowGap`/`columnGap`/`contentPadding`/`minHeight`。文字、布局以外的绑定、事件处理函数和原生 HTML 包裹层都会丢掉,所以用 `<div>` 定位置的页面看起来会比实际更平。骨架是结构参考,不是能直接编译的文件。
126
-
127
- ## build
128
-
129
- `build "<想法>"` 在本地给模板打分:对 slug、标题、说明、关键词和页面用到的组件做字段加权关键词匹配,越少见的词权重越高。没有网络,没有模型,同一句话每次结果一样。中文按字符二元组匹配,不需要空格分词。
130
-
131
- - `TEMPLATE` 是最接近的页面,`OTHER TEMPLATES` 是接下来两个。不能复制的页面只有在明显更贴合时才排到前面,并标出原因。
132
- - `COMPONENTS`(最多 6 个)和 `COMPOSABLES`(最多 4 个)用的是 `search` 那套排序,两个命令不会互相矛盾。想法里点到的部件不够时,用推荐页面自己用到的组件补齐,并标出来源。
133
- - 命中太弱的直接丢掉,不凑数。完全没命中时不给模板,并说明这一点。
134
- - 不带想法时 `build` 打印工作流:先找模板,再复制,最后写代码前逐个查组件。
135
-
136
- ## JSON 信封
137
-
138
- 成功响应是 `{ apiVersion: 1, type, data }`。`template`、`build`、`docs` 的响应带 `package`,说明记录来自哪个包(模板是 `@astryx-vue/cli`,文档页是 `@astryx-vue/docs`)。`template.list`、`template.show`、`template.skeleton` 的每条模板记录在 `components` 旁边带 `dependencies`。`component.detail` 和 `composable.detail` 带所属的包;`component.list` 和 `composable.list` 的每一条各自带 `package`,因为一个列表可能横跨多个包。
139
-
140
- | 类型 | 命令 |
141
- | --- | --- |
142
- | `template.list`、`template.show`、`template.skeleton`、`template.copy` | `template` |
143
- | `build.help`、`build.kit` | `build` |
144
- | `component.list`、`component.detail` | `component` |
145
- | `composable.list`、`composable.detail` | `composable` |
146
- | `docs.list`、`docs.detail` | `docs` |
147
- | `search` | `search` |
148
- | `manifest` | `manifest` |
149
- | `theme.list`、`theme.detail` | `theme` |
150
- | `upgrade.report` | `upgrade` |
151
- | `doctor.report` | `doctor` |
152
- | `init.run` | `init` |
153
- | `help`、`version` | `--help`、`--version`,或带 `--json` 不带参数 |
154
-
155
- 退出码:成功 `0`,命令跑了但没成功 `1`,用法错误 `2`。分支判断请用信封里的 `code`,不要匹配消息文本。
156
-
157
- | 错误码 | 什么时候出现 |
158
- | --- | --- |
159
- | `ERR_UNKNOWN_TEMPLATE` | 没有模板匹配这个 slug。会附带最多 5 个相近的 slug。 |
160
- | `ERR_TEMPLATE_NOT_READY` | 模板引用了项目装不上的包。 |
161
- | `ERR_FILE_EXISTS` | 目标文件已存在,且没加 `--overwrite`。 |
162
- | `ERR_PATH_TRAVERSAL` | 目标路径越出了项目目录。 |
163
- | `ERR_INVALID_ARGUMENT` | 两个模式混用、取值不在选项内,或想法是空白串。 |
164
- | `ERR_UNKNOWN_COMPONENT`、`ERR_UNKNOWN_COMPOSABLE`、`ERR_UNKNOWN_TOPIC` | 没有记录匹配这个名字。 |
165
- | `ERR_UNKNOWN_AGENT` | `--agent` 不是 `cursor`、`claude`、`codex`、`hermes` 或 `all`。 |
166
- | `ERR_UNKNOWN_FEATURE` | `--features` 写了 `agents` 以外的东西。 |
167
-
168
- `manifest --json` 按命令携带同一份错误码列表,agent 不用读这张表。
169
-
170
- ## agent 区块
171
-
172
- `init` 先打印接入说明(装哪些包、canary 的带 `@canary`(前提是已经发到 npm)、主题样式引哪一行、语言怎么提供、改完怎么验证),再写入这个受管理区块:
173
-
174
- ```html
175
- <!-- Maintained by astryx-vue, do not edit manually -->
176
- ...
177
- <!-- astryx-vue block end -->
178
- ```
179
-
180
- 文件不存在就创建。已经有标记时,每次只替换区块内容,其余文字保持不动。文件存在但没有标记时,把区块追加到末尾。第二次运行仍然重写区块,不会因为标记已存在就跳过;生成内容一致时字节不变。
181
-
182
- 区块开头列一遍 catalog 覆盖的包。npm 上还没有的包(仓库里标了 `private`)照样列名字,但会写明「还没发布到 npm」并且不给安装命令,免得有人照着敲一个 404;已经发布的 canary 包才会写成 `@canary`。然后写三步工作流(用 `build` 挑页面,用 `template` 复制,写代码前用 `component`/`composable`/`docs` 查),加上查询命令清单和怎么运行这个命令。它不会把组件目录贴进来。
183
-
184
- 接入说明里的安装命令跟着项目走:先看 `packageManager` 字段,再看 `pnpm-lock.yaml`、`yarn.lock`、`bun.lock(b)`、`package-lock.json`。两样都没有就把四种写法都列出来。`--features` 现在是可选的,写 `agents` 和不写一样,写别的仍然是 `ERR_UNKNOWN_FEATURE`。
185
-
186
- 写到哪里:
187
-
188
- - 不加 `--agent`:项目里已经有的第一个 agent 文件,顺序是 `AGENTS.md`、`CLAUDE.md`、`.cursorrules`、`.hermes.md`、`HERMES.md`;都没有就写 `AGENTS.md`;
189
- - `--agent cursor` 或 `--agent codex`:`AGENTS.md`;
190
- - `--agent claude`:`CLAUDE.md`;
191
- - `--agent hermes`:`.hermes.md`;项目里已有 `HERMES.md` 时写它;
192
- - `--agent all`:已经存在的全部已知文件;一个都没有就写 `AGENTS.md`;
193
- - `--agent-docs-path <file>`:就写这个文件,相对项目目录解析。缺的目录会自动创建,越出项目目录报 `ERR_PATH_TRAVERSAL`。
194
-
195
- ## 文本从哪来
196
-
197
- CLI 不另存一份组件文档。`packages/cli/scripts/generate-catalog.mjs` 读:
198
-
199
- - `apps/docs/components/demos/*/meta.ts`:名字、分组和顺序
200
- - `apps/docs/docs/{en,zh}/components/<slug>/`:用法、props、插槽、事件和示例标题
201
- - `apps/docs/components/demos/<slug>/*.vue`:那些页面指向的示例源码
202
- - `apps/docs/components/demo-infra/reference/*.json`:anatomy 和无障碍信息
203
- - `apps/docs/docs/{en,zh}/guide` 和 `themes`:搜索主题和正文,以及 `theme` 用的主题记录、`init` 用的安装命令和代码片段
204
- - `apps/docs/docs/{en,zh}/guide/migration.md`:`upgrade` 打印的升级小节,和它去源码里找的旧名字
205
- - `apps/docs/components/playground/pages/*.vue`:模板源码;`templateManifest.ts`:模板标题和分类
206
- - 每个 `packages/*/package.json`:判断组件属于哪个包、版本和说明(`upgrade`/`doctor`/`init` 用);页面里没写 import 时用它的 exports 推断
207
-
208
- 模板的说明和关键词是手写的,在 `packages/cli/src/generate/template-meta/`,每类模板一个文件,由 `template-meta.ts` 合并,属于源数据,不是生成物。
209
-
210
- 示例的标题和说明按语言各存一份,但示例代码两种语言只存一份:两个页面指向的是同一个 `.vue` 文件。以后要是一个页面改了代码、另一个没改,生成时直接报错并指出是哪个组件的第几个示例,不会让一种语言悄悄用上另一种的代码。取回某种语言的示例只走 `packages/cli/src/catalog.ts` 里的 `examplesFor()`。
211
-
212
- 结果提交在 `packages/cli/src/generated/catalog.json`。`pnpm --filter @astryx-vue/cli build` 会重新生成并打进 `dist`,也就是 npm 包实际运行的那份。文档或模板改过之后,重新生成并提交 diff:
213
-
214
- ```sh
215
- pnpm --filter @astryx-vue/cli generate
84
+ const result = await runCli(['search', 'dialog', '--json'], { cwd: process.cwd() })
85
+ const found = assertResponse(result.stdout, 'search')
216
86
  ```
217
87
 
218
- `pnpm check` 会跑 `generate:check`。它会在这几种情况下失败:`catalog.json` 过期、某个包里有 PascalCase 导出没有任何组件文档引用、或者某个演练场页面在 `template-meta.ts` 里没有元数据。`./icons`、`./locale`、CSS 和包根不算组件导出。
219
-
220
- `catalog.packages` 列出 catalog 覆盖的每个包(`core`、`themes`、`richtext`、`charts`、`lab`),带目录、「是否只发 canary」的标记,以及 `published`。`private` 的包 `published` 是 false:npm 上没有它的版本,所以任何输出都不会给它写安装命令。组件的 `package` 取「有它这个子路径导出的包」,没有就取「`src/` 里有它这个目录的包」。
88
+ `runCli` 收的参数和命令行一致,返回 `{ exitCode, stdout, stderr }`。`assertResponse` 按你要的类型校验信封。
221
89
 
222
- `swizzle`、`discover`、`gap-report`、`integration`、`blog` 不在这个包里,也没有 MCP 服务。`theme`、`upgrade`、`doctor` 只输出指引和报告,都不改项目文件。
90
+ ## 参与开发
223
91
 
224
- ## 相关包
225
-
226
- | 包 | 是什么 |
227
- | --- | --- |
228
- | `@astryx-vue/core` | 这个 CLI 记录的那些 Vue 组件 |
229
- | `@astryx-vue/themes` | Neutral、Butter、Chocolate、Gothic、Matcha、Stone、Y2K |
230
-
231
- ## 在本仓库里试
232
-
233
- ```sh
234
- pnpm --filter @astryx-vue/cli build
235
- node packages/cli/bin/astryx-vue.mjs component Button
236
- node packages/cli/bin/astryx-vue.mjs composable useClipboard
237
- node packages/cli/bin/astryx-vue.mjs docs guide/dark-mode --dense
238
- node packages/cli/bin/astryx-vue.mjs template --list
239
- node packages/cli/bin/astryx-vue.mjs template login-card --skeleton
240
- node packages/cli/bin/astryx-vue.mjs build "登录页"
241
- node packages/cli/bin/astryx-vue.mjs search login --type template
242
- node packages/cli/bin/astryx-vue.mjs manifest --json
243
- ```
92
+ 文本从哪来、怎么重新生成目录、怎么在本仓库里直接跑这个命令,见 [CONTRIBUTING.md](./CONTRIBUTING.md)。
244
93
 
245
94
  ## 许可
246
95
 
package/dist/index.js CHANGED
@@ -633,7 +633,7 @@ const catalog = {
633
633
  {
634
634
  "name": "@astryx-vue/core",
635
635
  "directory": "packages/core",
636
- "version": "0.5.2",
636
+ "version": "0.7.0",
637
637
  "description": "Vue 3 components for the unofficial astryx-vue port of Astryx. Not affiliated with Meta.",
638
638
  "canary": false,
639
639
  "published": true
@@ -641,7 +641,7 @@ const catalog = {
641
641
  {
642
642
  "name": "@astryx-vue/charts",
643
643
  "directory": "packages/charts",
644
- "version": "0.1.0",
644
+ "version": "0.1.2",
645
645
  "description": "Astryx charts for Vue — a config-model data visualization layer (d3-based).",
646
646
  "canary": false,
647
647
  "published": true
@@ -649,7 +649,7 @@ const catalog = {
649
649
  {
650
650
  "name": "@astryx-vue/lab",
651
651
  "directory": "packages/lab",
652
- "version": "0.1.1",
652
+ "version": "0.2.1",
653
653
  "description": "Astryx lab components for Vue 3 — experimental components that may change. Ships only under the canary dist-tag.",
654
654
  "canary": true,
655
655
  "published": true
@@ -657,7 +657,7 @@ const catalog = {
657
657
  {
658
658
  "name": "@astryx-vue/richtext",
659
659
  "directory": "packages/richtext",
660
- "version": "0.5.2",
660
+ "version": "0.7.0",
661
661
  "description": "Astryx rich text for Vue — Lexical nodes and lossless Markdown import/export. Not affiliated with Meta.",
662
662
  "canary": false,
663
663
  "published": true
@@ -665,7 +665,7 @@ const catalog = {
665
665
  {
666
666
  "name": "@astryx-vue/themes",
667
667
  "directory": "packages/themes",
668
- "version": "0.5.2",
668
+ "version": "0.7.0",
669
669
  "description": "CSS themes for the unofficial astryx-vue port of Astryx. Not affiliated with Meta.",
670
670
  "canary": false,
671
671
  "published": true
@@ -39710,7 +39710,7 @@ const catalog = {
39710
39710
  },
39711
39711
  {
39712
39712
  "guidance": "Do",
39713
- "text": "Use `align=\"start\"` when start or end content is taller than a single line of text."
39713
+ "text": "Use `align=\"first-line\"` when a wrapping label sits beside a small mark (a status dot, a radio, a timestamp) that belongs to its first line. Use `align=\"start\"` to line up the tops of a tall slot and the text."
39714
39714
  },
39715
39715
  {
39716
39716
  "guidance": "Do",
@@ -39756,7 +39756,7 @@ const catalog = {
39756
39756
  {
39757
39757
  "name": "`align`",
39758
39758
  "description": "`align`",
39759
- "type": "`'center' | 'start'`",
39759
+ "type": "`'center' | 'start' | 'first-line'`",
39760
39760
  "default": "`'center'`"
39761
39761
  },
39762
39762
  {
@@ -39868,9 +39868,9 @@ const catalog = {
39868
39868
  },
39869
39869
  {
39870
39870
  "name": "`align`",
39871
- "type": "`'center' | 'start'`",
39871
+ "type": "`'center' | 'start' | 'first-line'`",
39872
39872
  "default": "`'center'`",
39873
- "description": "前面和后面那两块内容竖直方向怎么对齐。"
39873
+ "description": "前面和后面那两块内容竖直方向怎么对齐。`center` 把每块对到整块文字的中心;`start` 让插槽顶部和文字顶部对齐;`first-line` 把每块对到标签第一行的中线上,标签换行时小标记也跟第一行走。插槽比这一行高时,文字会被压下去。"
39874
39874
  },
39875
39875
  {
39876
39876
  "name": "`density`",
@@ -39978,7 +39978,7 @@ const catalog = {
39978
39978
  "zh": [
39979
39979
  {
39980
39980
  "heading": "建议",
39981
- "text": "- 推荐: 常见排版用带名字的插槽(`startContent`、`label`、`description`、`endContent`),八成的场景这样写就够了。\n- 推荐: 菜单和密集列表用 `density=\"compact\"`,普通行用 `\"balanced\"`,宽松布局用 `\"spacious\"`。\n- 推荐: 内容长短不固定时,用 `labelLines` 和 `descriptionLines` 控制在第几行截断。\n- 推荐: 前面或后面的内容比一行字更高时,用 `align=\"start\"` 让它顶部对齐。\n- 推荐: 横滑出来的操作要在行里再留一个入口(菜单,或者 `endContent` 里的按钮)。横滑只是给手指的快捷方式,只能滑出来的操作,键盘和鼠标用户永远够不着。\n- 避免: 不要在可点的 `Item` 里再套按钮或链接。焦点和点击目标会变得难猜。\n- 避免: 不要拿 `Item` 做页面之间的跳转。那种场景用导航组件。\n- 避免: 不要把已读未读、收件箱之类的业务逻辑直接塞进来。包一层薄薄的组件(类似 PreviewItem)更好。"
39981
+ "text": "- 推荐: 常见排版用带名字的插槽(`startContent`、`label`、`description`、`endContent`),八成的场景这样写就够了。\n- 推荐: 菜单和密集列表用 `density=\"compact\"`,普通行用 `\"balanced\"`,宽松布局用 `\"spacious\"`。\n- 推荐: 内容长短不固定时,用 `labelLines` 和 `descriptionLines` 控制在第几行截断。\n- 推荐: 换行标签旁边是归属于第一行的小标记(状态点、单选按钮、时间戳)时,用 `align=\"first-line\"`;要让较高的插槽顶部和文字顶部对齐,用 `align=\"start\"`。\n- 推荐: 横滑出来的操作要在行里再留一个入口(菜单,或者 `endContent` 里的按钮)。横滑只是给手指的快捷方式,只能滑出来的操作,键盘和鼠标用户永远够不着。\n- 避免: 不要在可点的 `Item` 里再套按钮或链接。焦点和点击目标会变得难猜。\n- 避免: 不要拿 `Item` 做页面之间的跳转。那种场景用导航组件。\n- 避免: 不要把已读未读、收件箱之类的业务逻辑直接塞进来。包一层薄薄的组件(类似 PreviewItem)更好。"
39982
39982
  },
39983
39983
  {
39984
39984
  "heading": "插槽",
@@ -39991,6 +39991,17 @@ const catalog = {
39991
39991
  ]
39992
39992
  },
39993
39993
  "examples": [
39994
+ {
39995
+ "title": {
39996
+ "en": "Align First Line",
39997
+ "zh": "按第一行对齐"
39998
+ },
39999
+ "description": {
40000
+ "en": "Center a small mark on the label's first line when the label wraps, rather than the whole text block. Set align to first-line for a status dot, a radio, or a timestamp that belongs to the first line.",
40001
+ "zh": "标签换行时,把小标记对到标签第一行的中线上,而不是整块文字的中心。状态点、单选按钮、时间戳这类属于第一行的标记用 first-line。"
40002
+ },
40003
+ "code": "<script setup lang=\"ts\">\nimport { Badge } from '@astryx-vue/core/Badge'\nimport { Item } from '@astryx-vue/core/List'\nimport { StatusDot } from '@astryx-vue/core/StatusDot'\nimport { Text } from '@astryx-vue/core/Text'\n<\/script>\n\n<template>\n <div class=\"item-align-first-line\">\n <Item\n align=\"first-line\"\n :label-lines=\"2\"\n label=\"Quarterly planning notes are ready for the team to review\"\n description=\"Agenda, decisions, and follow-ups\"\n >\n <template #startContent>\n <StatusDot label=\"Active\" variant=\"success\" />\n </template>\n <template #endContent>\n <Text color=\"secondary\">\n 12m\n </Text>\n </template>\n </Item>\n <Item\n align=\"first-line\"\n :label-lines=\"2\"\n label=\"Customer research interviews are scheduled for next week\"\n >\n <template #startContent>\n <StatusDot label=\"Away\" variant=\"warning\" />\n </template>\n <template #endContent>\n <Badge label=\"New\" variant=\"blue\" />\n </template>\n </Item>\n <Item\n align=\"first-line\"\n label=\"Launch checklist\"\n description=\"Remaining tasks before release\"\n >\n <template #startContent>\n <StatusDot label=\"Online\" variant=\"success\" />\n </template>\n <template #endContent>\n <Text color=\"secondary\">\n Fri\n </Text>\n </template>\n </Item>\n </div>\n</template>\n\n<style scoped>\n.item-align-first-line {\n display: flex;\n flex-direction: column;\n width: min(100%, 340px);\n min-width: 0;\n}\n</style>"
40004
+ },
39994
40005
  {
39995
40006
  "title": {
39996
40007
  "en": "Basic",
@@ -48695,7 +48706,7 @@ const catalog = {
48695
48706
  "name": "`variant`",
48696
48707
  "type": "`'pages' | 'count' | 'compact' | 'dots' | 'input' | 'none'`",
48697
48708
  "default": "`'pages'`",
48698
- "description": "Middle of the control. `input` is an editable page field."
48709
+ "description": "Middle of the control. `input` is an editable page field; the `compact` readout can add first/last buttons with `hasFirstLast`."
48699
48710
  },
48700
48711
  {
48701
48712
  "name": "`siblingCount`",
@@ -48724,8 +48735,8 @@ const catalog = {
48724
48735
  {
48725
48736
  "name": "`hasFirstLast`",
48726
48737
  "type": "`boolean`",
48727
- "default": "`input` when the page count is known",
48728
- "description": "Shows buttons that jump to the first and last page."
48738
+ "default": "`true` for `input`; `false` for `compact`",
48739
+ "description": "Shows buttons that jump to the first and last page. Shown in the `input` variant unless `false`, and in the `compact` variant only when `true`; other variants ignore it. Both are omitted when the page count is unknown."
48729
48740
  },
48730
48741
  {
48731
48742
  "name": "`changeAction`",
@@ -48820,7 +48831,7 @@ const catalog = {
48820
48831
  "name": "`variant`",
48821
48832
  "type": "`'pages' | 'count' | 'compact' | 'dots' | 'input' | 'none'`",
48822
48833
  "default": "`'pages'`",
48823
- "description": "中间区域。 `input` 是可输入的页码。"
48834
+ "description": "中间区域。 `input` 是可输入的页码;`compact` 的「第几页 / 共几页」可以用 `hasFirstLast` 加上首尾按钮。"
48824
48835
  },
48825
48836
  {
48826
48837
  "name": "`siblingCount`",
@@ -48849,8 +48860,8 @@ const catalog = {
48849
48860
  {
48850
48861
  "name": "`hasFirstLast`",
48851
48862
  "type": "`boolean`",
48852
- "default": "仅 `input` 且总页数已知",
48853
- "description": "是否在上一页和下一页外侧放跳到首尾的按钮。"
48863
+ "default": "`input` 为 `true`;`compact` 为 `false`",
48864
+ "description": "是否在上一页和下一页外侧放跳到首尾的按钮。`input` 形态默认显示(传 `false` 关闭);`compact` 形态只在传 `true` 时显示;其他形态忽略。总页数未知时两个按钮都不显示。"
48854
48865
  },
48855
48866
  {
48856
48867
  "name": "`changeAction`",
@@ -48978,6 +48989,17 @@ const catalog = {
48978
48989
  "zh": "输入页码后跳转,两侧可以直达首页和末页。总数未知时改用 hasMore,不再显示末页。"
48979
48990
  },
48980
48991
  "code": "<script setup lang=\"ts\">\nimport { Pagination } from '@astryx-vue/core/Pagination'\nimport { ref } from 'vue'\n\nconst page = ref(3)\n<\/script>\n\n<template>\n <Pagination\n v-model:page=\"page\"\n :page-size=\"10\"\n :total=\"240\"\n variant=\"input\"\n />\n</template>"
48992
+ },
48993
+ {
48994
+ "title": {
48995
+ "en": "Compact with first and last",
48996
+ "zh": "紧凑分页加首尾"
48997
+ },
48998
+ "description": {
48999
+ "en": "The compact readout can opt into first and last buttons when the page count is known.",
49000
+ "zh": "总数已知时,紧凑分页可以打开首页和末页按钮。"
49001
+ },
49002
+ "code": "<script setup lang=\"ts\">\nimport { Pagination } from '@astryx-vue/core/Pagination'\nimport { ref } from 'vue'\n\nconst page = ref(5)\n<\/script>\n\n<template>\n <Pagination\n v-model:page=\"page\"\n :page-size=\"10\"\n :total=\"100\"\n variant=\"compact\"\n has-first-last\n />\n</template>"
48981
49003
  }
48982
49004
  ],
48983
49005
  "anatomy": {
@@ -59238,6 +59260,70 @@ const catalog = {
59238
59260
  "zh": "https://astryx-vue.pages.dev/components/lightbox"
59239
59261
  }
59240
59262
  },
59263
+ {
59264
+ "slug": "drawer",
59265
+ "name": "Drawer",
59266
+ "package": "@astryx-vue/core",
59267
+ "kind": "component",
59268
+ "title": {
59269
+ "en": "Drawer",
59270
+ "zh": "抽屉"
59271
+ },
59272
+ "group": "Overlay",
59273
+ "groupTitle": {
59274
+ "en": "Overlay",
59275
+ "zh": "浮层"
59276
+ },
59277
+ "importPath": "@astryx-vue/core/Drawer",
59278
+ "exportName": "Drawer",
59279
+ "summary": {
59280
+ "en": "`Drawer` is a panel that slides in from one side of the screen, for filters, details, or a short form. It shares the dialog's modal machinery: page scrolling is locked, focus stays inside, and closing returns focus to whatever opened it.",
59281
+ "zh": "`Drawer` 是从屏幕一侧滑出的面板,用来放筛选、详情或一小段表单。它和弹窗共用同一套模态机制:打开时锁住页面滚动、把焦点关在面板里、关闭后把焦点还给打开它的元素。"
59282
+ },
59283
+ "usage": {
59284
+ "en": "`Drawer` is a panel that slides in from one side of the screen, for filters, details, or a short form. It shares the dialog's modal machinery: page scrolling is locked, focus stays inside, and closing returns focus to whatever opened it.\n\n`placement` picks the edge, `end` by default. `purpose` decides how it closes: `info` allows Escape and a scrim click, `form` allows only Escape, `required` allows neither.\n\n```vue\n<script setup lang=\"ts\">\nimport { Button } from '@astryx-vue/core/Button'\nimport { Drawer, DrawerHeader } from '@astryx-vue/core/Drawer'\nimport { ref } from 'vue'\n\nconst isOpen = ref(false)\n<\/script>\n\n<template>\n <Button @click=\"isOpen = true\">\n Open filters\n </Button>\n <Drawer :is-open=\"isOpen\" label=\"Filters\" @open-change=\"isOpen = $event\">\n <DrawerHeader title=\"Filters\" subtitle=\"Narrow the list\" @open-change=\"isOpen = false\" />\n <p>Category, price range and availability go here.</p>\n </Drawer>\n</template>\n```\n\n<drawerOverview />",
59285
+ "zh": "`Drawer` 是从屏幕一侧滑出的面板,用来放筛选、详情或一小段表单。它和弹窗共用同一套模态机制:打开时锁住页面滚动、把焦点关在面板里、关闭后把焦点还给打开它的元素。\n\n`placement` 决定贴哪一边,默认 `end`(中文界面是右边)。`purpose` 决定怎么关:`info` 允许 Escape 和点遮罩,`form` 只允许 Escape,`required` 两者都不允许。\n\n```vue\n<script setup lang=\"ts\">\nimport { Button } from '@astryx-vue/core/Button'\nimport { Drawer, DrawerHeader } from '@astryx-vue/core/Drawer'\nimport { ref } from 'vue'\n\nconst isOpen = ref(false)\n<\/script>\n\n<template>\n <Button @click=\"isOpen = true\">\n 打开筛选\n </Button>\n <Drawer :is-open=\"isOpen\" label=\"筛选\" @open-change=\"isOpen = $event\">\n <DrawerHeader title=\"筛选\" subtitle=\"缩小列表范围\" @open-change=\"isOpen = false\" />\n <p>分类、价格区间和是否有货放在这里。</p>\n </Drawer>\n</template>\n```\n\n<drawerOverview />"
59286
+ },
59287
+ "practices": {
59288
+ "en": [],
59289
+ "zh": []
59290
+ },
59291
+ "props": {
59292
+ "en": [],
59293
+ "zh": []
59294
+ },
59295
+ "slots": {
59296
+ "en": [],
59297
+ "zh": []
59298
+ },
59299
+ "events": {
59300
+ "en": [],
59301
+ "zh": []
59302
+ },
59303
+ "notes": {
59304
+ "en": [{
59305
+ "heading": "Title row",
59306
+ "text": "`DrawerHeader` gives you a title, a subtitle, and start/end slots. The close button appears when someone listens for `open-change`; pass `has-close-button` to decide it yourself."
59307
+ }],
59308
+ "zh": [{
59309
+ "heading": "标题行",
59310
+ "text": "`DrawerHeader` 提供标题、副标题、行首和行尾插槽。关闭按钮默认在有人监听 `open-change` 时出现;想写死就传 `has-close-button`。"
59311
+ }]
59312
+ },
59313
+ "examples": [],
59314
+ "anatomy": {
59315
+ "en": [],
59316
+ "zh": []
59317
+ },
59318
+ "accessibility": {
59319
+ "en": [],
59320
+ "zh": []
59321
+ },
59322
+ "docsUrl": {
59323
+ "en": "https://astryx-vue.pages.dev/en/components/drawer",
59324
+ "zh": "https://astryx-vue.pages.dev/components/drawer"
59325
+ }
59326
+ },
59241
59327
  {
59242
59328
  "slug": "list",
59243
59329
  "name": "List",
@@ -59259,8 +59345,8 @@ const catalog = {
59259
59345
  "zh": "`List` 用来呈现一组彼此相关的条目,`ListItem` 是其中的一行。它渲染出语义正确的 `<ul>` 或 `<ol>`,并统一安排一行内部「起始内容 + 标签 + 描述 + 结束内容」四块位置,所以设置项、通知、步骤说明这类界面不必每次都重新写一遍行布局。"
59260
59346
  },
59261
59347
  "usage": {
59262
- "en": "`List` presents a collection of related entries, and `ListItem` is one row inside it. The pair renders a semantic `<ul>` or `<ol>` and lays out every row as start content, label, description, and end content, so a settings screen, an inbox, or a set of steps does not have to rebuild that row layout each time.\n\nOne row needs nothing more than a `label`:\n\n```vue\n<script setup lang=\"ts\">\nimport { List, ListItem } from '@astryx-vue/core/List'\n<\/script>\n\n<template>\n <List header=\"Inbox\">\n <ListItem label=\"Mentions\" description=\"Comments that name you\" />\n <ListItem label=\"Build finished\" description=\"main passed every check\" />\n </List>\n</template>\n```\n\n`description` is the secondary line under the label. The `startContent` and `endContent` slots place an icon, an avatar, a badge, or an action at either end of the row, and `label` and `description` each have a same-named slot for content that needs richer markup than a string.\n\nThe list sets the density for all of its rows, and a single row can override it. `compact` uses 4px of block padding, `balanced` is the 8px default, and `spacious` uses 12px and widens the inline padding by one step as well. `hasDividers` rules a line between neighbouring rows, and the last row never draws one.\n\n`listStyle` decides what appears in front of each row: `disc` draws a filled bullet, `circle` draws an outlined one, and `decimal` switches the list to an `<ol>` and numbers the rows, with `start` choosing the first number. The default `none` draws no marker at all.\n\nA row becomes interactive in one of two ways. Give it `onClick` and a button appears inside the row, filling the content area, so keyboard users can tab to it and press Enter or Space; give it `href` and that same place becomes a link, with `noopener` and `noreferrer` added automatically when `target=\"_blank\"`. Clicking another control inside the row, such as a button in `endContent`, does not run the row's own action.\n\n`isSelected` marks the row as selected. When the row carries a role that permits `aria-selected` — `option`, `tab`, or `row`, for instance — the state is written there; otherwise it falls back to `aria-current=\"true\"`, which is valid on any element, so assistive technology still hears which row is current. `isDisabled` makes the row inert and dims its content.\n\nThe `header` slot renders a title above the list and associates it through `aria-labelledby`, so a screen reader announces the title when it enters the list. `edgeCompensation=\"inline\"` is for a list placed inside a padded container: it pulls every row toward the container's inline content edges so the row text lines up with a sibling heading, cancelling no more than the container padding each edge actually publishes.\n\nThe package also exports the more general `Item` primitive. `ListItem` is that primitive rendered as an `<li>` with the density and marker from its list; use `Item` directly when the same row layout is needed outside a list, or when you need its extra controls — `as`, `align`, `labelLines`, `descriptionLines`, and `layout`. Most screens only need `List` and `ListItem`.",
59263
- "zh": "`List` 用来呈现一组彼此相关的条目,`ListItem` 是其中的一行。它渲染出语义正确的 `<ul>` 或 `<ol>`,并统一安排一行内部「起始内容 + 标签 + 描述 + 结束内容」四块位置,所以设置项、通知、步骤说明这类界面不必每次都重新写一遍行布局。\n\n一行最少只需要一个 `label`:\n\n```vue\n<script setup lang=\"ts\">\nimport { List, ListItem } from '@astryx-vue/core/List'\n<\/script>\n\n<template>\n <List header=\"Inbox\">\n <ListItem label=\"Mentions\" description=\"Comments that name you\" />\n <ListItem label=\"Build finished\" description=\"main passed every check\" />\n </List>\n</template>\n```\n\n`description` 是标签下方的次要文字。使用 `startContent` 和 `endContent` 两个插槽可以把图标、头像、徽章或操作按钮放在行的两端;`label` 和 `description` 也各有一个同名插槽,当文字需要用富文本表达时改用它即可。\n\n列表的密度由 `List` 的 `density` 统一决定,单行可以用 `ListItem` 的 `density` 覆盖它。`compact` 对应 4 像素的上下内边距,`balanced` 是默认的 8 像素,`spacious` 是 12 像素并把左右内边距也放宽一档。`hasDividers` 会在相邻两行之间画一条分隔线,最后一行不会画。\n\n`listStyle` 决定每行前面画什么标记:`disc` 是实心圆点,`circle` 是空心圆点,`decimal` 会改用 `<ol>` 渲染并给出序号,`start` 用来指定序号从几开始。不需要标记时保持默认的 `none`。\n\n一行可以通过两种方式变成可交互。给出 `onClick` 之后,行内部会出现一个占满内容区的按钮,键盘用户可以用 Tab 聚焦它、用回车或空格触发;给出 `href` 时同一位置会变成链接,`target=\"_blank\"` 会自动补上 `noopener` 和 `noreferrer`。点击行内的其他控件(例如 `endContent` 里的按钮)不会触发这一行自己的动作。\n\n`isSelected` 把行标记为选中。如果这一行带有允许 `aria-selected` 的角色,例如 `option`、`tab` 或 `row`,状态会写进 `aria-selected`;否则会退回到在任何元素上都合法的 `aria-current=\"true\"`,屏幕阅读器仍然能读到当前项。`isDisabled` 会让行变成惰性的,并把它的内容调淡。\n\n`header` 插槽会在列表上方渲染一段标题,并用 `aria-labelledby` 与列表关联起来,于是屏幕阅读器在进入列表时会先读出这个标题。`edgeCompensation=\"inline\"` 用于把列表放在带内边距的容器里时让每一行的文字与同级的标题对齐:它会按每个逻辑边缘各自可用的容器内边距,抵消掉行自身的左右内边距,最多抵消到零。\n\n包里同时导出了更通用的原语 `Item`。`ListItem` 就是它在 `<li>` 上的一层封装,加上来自 `List` 的密度和标记;需要在非列表结构里复用同一套行布局时,可以直接使用 `Item`,它能通过 `as` 决定根元素,并提供 `align`、`labelLines`、`descriptionLines`、`layout` 等额外控制。大多数界面只需要 `List` 和 `ListItem`。"
59348
+ "en": "`List` presents a collection of related entries, and `ListItem` is one row inside it. The pair renders a semantic `<ul>` or `<ol>` and lays out every row as start content, label, description, and end content, so a settings screen, an inbox, or a set of steps does not have to rebuild that row layout each time.\n\nOne row needs nothing more than a `label`:\n\n```vue\n<script setup lang=\"ts\">\nimport { List, ListItem } from '@astryx-vue/core/List'\n<\/script>\n\n<template>\n <List header=\"Inbox\">\n <ListItem label=\"Mentions\" description=\"Comments that name you\" />\n <ListItem label=\"Build finished\" description=\"main passed every check\" />\n </List>\n</template>\n```\n\n`description` is the secondary line under the label. The `startContent` and `endContent` slots place an icon, an avatar, a badge, or an action at either end of the row, and `label` and `description` each have a same-named slot for content that needs richer markup than a string.\n\nThe list sets the density for all of its rows, and a single row can override it. `compact` uses 4px of block padding, `balanced` is the 8px default, and `spacious` uses 12px and widens the inline padding by one step as well. `hasDividers` rules a line between neighbouring rows, and the last row never draws one.\n\n`listStyle` decides what appears in front of each row: `disc` draws a filled bullet, `circle` draws an outlined one, and `decimal` switches the list to an `<ol>` and numbers the rows, with `start` choosing the first number. The default `none` draws no marker at all.\n\nA row becomes interactive in one of two ways. Give it `onClick` and a button appears inside the row, filling the content area, so keyboard users can tab to it and press Enter or Space; give it `href` and that same place becomes a link, with `noopener` and `noreferrer` added automatically when `target=\"_blank\"`. Clicking another control inside the row, such as a button in `endContent`, does not run the row's own action.\n\n`isSelected` marks the row as selected. When the row carries a role that permits `aria-selected` — `option`, `tab`, or `row`, for instance — the state is written there; otherwise it falls back to `aria-current=\"true\"`, which is valid on any element, so assistive technology still hears which row is current. `isDisabled` makes the row inert and dims its content.\n\nThe `header` slot renders a title above the list and associates it through `aria-labelledby`, so a screen reader announces the title when it enters the list.\n\n`edge` decides where each row's text, dividers, and hover and selection backgrounds sit relative to the list's edges and the padding of the container around it. The default `padded` keeps the row's own inline padding. `flush` removes it, so text starts on the line of a heading above the list and lines and backgrounds stop at the text. `outset` keeps text on that line and lets lines and backgrounds reach into the container's padding. `bleed` puts text on the container's padding line and runs lines and backgrounds to its edges with square corners, like table rows in a Card. A row's first content — its marker, icon, or avatar, otherwise its label — is what lines up with the heading.\n\n`outset` and `bleed` read the padding published by the nearest container that shares it: a Card, Section, BottomSheet, a padded ScrollableArea, or a Layout region. Place the list directly in that container's padding; padding that Stack, Center, or your own styles add in between is not counted. With no such container, such as a standalone list or one in a Popover, they look like `padded`. `edgeCompensation=\"inline\"` still works as a deprecated alias of `edge=\"outset\"`; set `edge` in new code.\n\nThe package also exports the more general `Item` primitive. `ListItem` is that primitive rendered as an `<li>` with the density and marker from its list; use `Item` directly when the same row layout is needed outside a list, or when you need its extra controls — `as`, `align`, `labelLines`, `descriptionLines`, and `layout`. Most screens only need `List` and `ListItem`.",
59349
+ "zh": "`List` 用来呈现一组彼此相关的条目,`ListItem` 是其中的一行。它渲染出语义正确的 `<ul>` 或 `<ol>`,并统一安排一行内部「起始内容 + 标签 + 描述 + 结束内容」四块位置,所以设置项、通知、步骤说明这类界面不必每次都重新写一遍行布局。\n\n一行最少只需要一个 `label`:\n\n```vue\n<script setup lang=\"ts\">\nimport { List, ListItem } from '@astryx-vue/core/List'\n<\/script>\n\n<template>\n <List header=\"Inbox\">\n <ListItem label=\"Mentions\" description=\"Comments that name you\" />\n <ListItem label=\"Build finished\" description=\"main passed every check\" />\n </List>\n</template>\n```\n\n`description` 是标签下方的次要文字。使用 `startContent` 和 `endContent` 两个插槽可以把图标、头像、徽章或操作按钮放在行的两端;`label` 和 `description` 也各有一个同名插槽,当文字需要用富文本表达时改用它即可。\n\n列表的密度由 `List` 的 `density` 统一决定,单行可以用 `ListItem` 的 `density` 覆盖它。`compact` 对应 4 像素的上下内边距,`balanced` 是默认的 8 像素,`spacious` 是 12 像素并把左右内边距也放宽一档。`hasDividers` 会在相邻两行之间画一条分隔线,最后一行不会画。\n\n`listStyle` 决定每行前面画什么标记:`disc` 是实心圆点,`circle` 是空心圆点,`decimal` 会改用 `<ol>` 渲染并给出序号,`start` 用来指定序号从几开始。不需要标记时保持默认的 `none`。\n\n一行可以通过两种方式变成可交互。给出 `onClick` 之后,行内部会出现一个占满内容区的按钮,键盘用户可以用 Tab 聚焦它、用回车或空格触发;给出 `href` 时同一位置会变成链接,`target=\"_blank\"` 会自动补上 `noopener` 和 `noreferrer`。点击行内的其他控件(例如 `endContent` 里的按钮)不会触发这一行自己的动作。\n\n`isSelected` 把行标记为选中。如果这一行带有允许 `aria-selected` 的角色,例如 `option`、`tab` 或 `row`,状态会写进 `aria-selected`;否则会退回到在任何元素上都合法的 `aria-current=\"true\"`,屏幕阅读器仍然能读到当前项。`isDisabled` 会让行变成惰性的,并把它的内容调淡。\n\n`header` 插槽会在列表上方渲染一段标题,并用 `aria-labelledby` 与列表关联起来,于是屏幕阅读器在进入列表时会先读出这个标题。\n\n`edge` 决定每行文字、分隔线以及悬停和选中背景相对列表边缘与外围容器的位置。默认的 `padded` 保留行自身的内边距。`flush` 去掉这段内边距,文字顶到列表上方标题所在的竖线,分隔线和背景也止于文字。`outset` 同样把文字放在这条线上,让分隔线和背景伸进容器的内边距。`bleed` 让文字落在容器的内边距线上,分隔线和背景一直铺到容器边缘、四角为直角,像 Card 里的表格行。与标题对齐的是行里的第一块内容:有标记、图标或头像时就是它,否则是标签。\n\n`outset` 和 `bleed` 读取最近的、共享内边距的容器公布的数值:Card、Section、BottomSheet、带内边距的 ScrollableArea,或 Layout 区域。列表要直接放在这个容器的内边距里;Stack、Center 或你自己的样式在中间加的内边距不计入。没有这样的容器时(单独的列表、Popover 里的列表),它们和 `padded` 表现一样。`edgeCompensation=\"inline\"` 仍然可用,是 `edge=\"outset\"` 的弃用别名;新代码请写 `edge`。\n\n包里同时导出了更通用的原语 `Item`。`ListItem` 就是它在 `<li>` 上的一层封装,加上来自 `List` 的密度和标记;需要在非列表结构里复用同一套行布局时,可以直接使用 `Item`,它能通过 `as` 决定根元素,并提供 `align`、`labelLines`、`descriptionLines`、`layout` 等额外控制。大多数界面只需要 `List` 和 `ListItem`。"
59264
59350
  },
59265
59351
  "practices": {
59266
59352
  "en": [
@@ -59280,6 +59366,10 @@ const catalog = {
59280
59366
  "guidance": "Do",
59281
59367
  "text": "Set `descriptionLines` when the length of the content varies, or rely on the default single-line truncation for string descriptions, so the row height does not jump."
59282
59368
  },
59369
+ {
59370
+ "guidance": "Do",
59371
+ "text": "Use `edge` to line row text up with a heading above the list: `flush` for static rows, `outset` or `bleed` for rows inside a Card, Section, or BottomSheet."
59372
+ },
59283
59373
  {
59284
59374
  "guidance": "Don't",
59285
59375
  "text": "Nest a second control inside an interactive row as its main action. The row's button already fills the content area, and two click targets make the focus order and the behaviour hard to predict."
@@ -59310,6 +59400,10 @@ const catalog = {
59310
59400
  "guidance": "推荐",
59311
59401
  "text": "内容长度不定时给出 `descriptionLines`,或者让组件按默认的单行截断来裁剪字符串描述,避免行高随文字跳动。"
59312
59402
  },
59403
+ {
59404
+ "guidance": "推荐",
59405
+ "text": "用 `edge` 让行文字与列表上方的标题对齐:静态行用 `flush`,Card、Section 或 BottomSheet 里的行用 `outset` 或 `bleed`。"
59406
+ },
59313
59407
  {
59314
59408
  "guidance": "避免",
59315
59409
  "text": "在可点击的行里再放一个可点击的控件作为主要操作。行的按钮已经占据整块内容区,两个点击目标会让焦点顺序和行为都变得难以预测。"
@@ -59341,11 +59435,17 @@ const catalog = {
59341
59435
  "default": "`false`",
59342
59436
  "description": "Whether to rule a line between neighbouring rows. The last row never draws one, and rows lose their rounded corners while dividers are on."
59343
59437
  },
59438
+ {
59439
+ "name": "`edge`",
59440
+ "type": "`'padded' | 'flush' | 'outset' | 'bleed'`",
59441
+ "default": "`'padded'`",
59442
+ "description": "Where each row's text, dividers, and hover and selection backgrounds sit relative to the list's edges and the padding of the container around it. `padded` keeps the row's own inline padding. `flush` removes it, so text starts on the line of a heading above the list and lines and backgrounds stop at the text. `outset` keeps text on that line and lets lines and backgrounds reach into the container's padding. `bleed` puts text on the container's padding line and runs lines and backgrounds to its edges with square corners, like table rows. A row's first content — its marker, icon, or avatar, otherwise its label — is what lines up with the heading. `outset` and `bleed` read the padding published by the nearest container that shares it (Card, Section, BottomSheet, a padded ScrollableArea, or a Layout region) and look like `padded` when there is none."
59443
+ },
59344
59444
  {
59345
59445
  "name": "`edgeCompensation`",
59346
59446
  "type": "`'inline'`",
59347
59447
  "default": "-",
59348
- "description": "Cancels each row's inline inset, up to the container padding published on each edge, so row text lines up with a sibling heading. It reads `--container-padding-inline-start` and `--container-padding-inline-end` and treats a missing value as zero, so a row is never pulled outside its container. Omitting the prop leaves row positions unchanged."
59448
+ "description": "Deprecated: use `edge=\"outset\"`, which this sets. Still works; `edge` wins when both are set."
59349
59449
  },
59350
59450
  {
59351
59451
  "name": "`listStyle`",
@@ -59490,11 +59590,17 @@ const catalog = {
59490
59590
  "default": "`false`",
59491
59591
  "description": "是否在相邻两行之间绘制分隔线。最后一行不会绘制,带分隔线时行不再有圆角。"
59492
59592
  },
59593
+ {
59594
+ "name": "`edge`",
59595
+ "type": "`'padded' | 'flush' | 'outset' | 'bleed'`",
59596
+ "default": "`'padded'`",
59597
+ "description": "每行文字、分隔线以及悬停和选中背景相对列表边缘与外围容器的位置。`padded` 保留行自身的内边距;`flush` 去掉这段内边距,文字顶到列表上方标题的竖线,分隔线和背景也止于文字;`outset` 把文字放在这条线上,让分隔线和背景伸进容器的内边距;`bleed` 让文字落在容器的内边距线上,分隔线和背景铺到容器边缘、四角为直角,像表格行。与标题对齐的是行里的第一块内容:有标记、图标或头像时就是它,否则是标签。`outset` 和 `bleed` 读取最近的、共享内边距的容器公布的数值(Card、Section、BottomSheet、带内边距的 ScrollableArea,或 Layout 区域);没有这样的容器时和 `padded` 表现一样。"
59598
+ },
59493
59599
  {
59494
59600
  "name": "`edgeCompensation`",
59495
59601
  "type": "`'inline'`",
59496
59602
  "default": "-",
59497
- "description": "按每个逻辑边缘可用的容器内边距,抵消行自身的左右内边距,让行内文字与同级标题对齐。它读取容器发布的 `--container-padding-inline-start` 与 `--container-padding-inline-end`,没有发布时按 0 处理,因此不会把行拉出容器之外。省略时行的位置不变。"
59603
+ "description": "已弃用:请改用 `edge=\"outset\"`,两者位置相同。仍然可用;两者同时设置时以 `edge` 为准。"
59498
59604
  },
59499
59605
  {
59500
59606
  "name": "`listStyle`",
@@ -59710,7 +59816,7 @@ const catalog = {
59710
59816
  },
59711
59817
  {
59712
59818
  "heading": "List",
59713
- "text": "The root element is a `<ul>`, or an `<ol>` when <code>listStyle=\"decimal\"</code>. A header adds a wrapper element around it.\n\nThe following go to the list element:\n\n- HTML attributes.\n- `aria-*` attributes.\n- `data-*` attributes.\n- `class`.\n- `style`.\n\nThe component writes `role=\"list\"`, `data-density`, `data-dividers`, `data-list-style`, and `data-edge-compensation`. Without a rendered header an `aria-labelledby` you pass is kept; with one, the list's own header takes over."
59819
+ "text": "The root element is a `<ul>`, or an `<ol>` when <code>listStyle=\"decimal\"</code>. A header adds a wrapper element around it.\n\nThe following go to the list element:\n\n- HTML attributes.\n- `aria-*` attributes.\n- `data-*` attributes.\n- `class`.\n- `style`.\n\nThe component writes `role=\"list\"`, `data-density`, `data-dividers`, `data-list-style`, and `data-edge` (the resolved `padded`, `flush`, `outset`, or `bleed`), plus `data-edge-compensation` while the deprecated prop is set. Without a rendered header an `aria-labelledby` you pass is kept; with one, the list's own header takes over."
59714
59820
  },
59715
59821
  {
59716
59822
  "heading": "ListItem / Item",
@@ -59728,7 +59834,7 @@ const catalog = {
59728
59834
  },
59729
59835
  {
59730
59836
  "heading": "List",
59731
- "text": "根元素是 `<ul>`,`listStyle=\"decimal\"` 时是 `<ol>`;给出 `header` 时外层会多一个包裹元素。\n\n以下内容传入列表元素:\n\n- HTML 属性。\n- `aria-*` 属性。\n- `data-*` 属性。\n- `class`。\n- `style`。\n\n组件自己写入 `role=\"list\"`、`data-density`、`data-dividers`、`data-list-style` 与 `data-edge-compensation`。未渲染 `header` 时,使用方传入的 `aria-labelledby` 会保留;渲染了 `header` 时由组件的标题接管。"
59837
+ "text": "根元素是 `<ul>`,`listStyle=\"decimal\"` 时是 `<ol>`;给出 `header` 时外层会多一个包裹元素。\n\n以下内容传入列表元素:\n\n- HTML 属性。\n- `aria-*` 属性。\n- `data-*` 属性。\n- `class`。\n- `style`。\n\n组件自己写入 `role=\"list\"`、`data-density`、`data-dividers`、`data-list-style` 与 `data-edge`(解析后的 `padded`、`flush`、`outset` 或 `bleed`);弃用的 prop 有值时还会写入 `data-edge-compensation`。未渲染 `header` 时,使用方传入的 `aria-labelledby` 会保留;渲染了 `header` 时由组件的标题接管。"
59732
59838
  },
59733
59839
  {
59734
59840
  "heading": "ListItem / Item",
@@ -59791,6 +59897,28 @@ const catalog = {
59791
59897
  "zh": "触摸横滑露出归档。鼠标不会开始这次拖动,所以同一操作还要留在行里。"
59792
59898
  },
59793
59899
  "code": "<script setup lang=\"ts\">\nimport { Button } from '@astryx-vue/core/Button'\nimport { List, ListItem } from '@astryx-vue/core/List'\nimport { ref } from 'vue'\n\nconst note = ref('Swipe the row on a touch screen. Archive stays in the row for keyboard and mouse.')\n\nfunction archive(): void {\n note.value = 'Archived.'\n}\n<\/script>\n\n<template>\n <List>\n <ListItem\n label=\"Invoice 1042\"\n description=\"Due Friday\"\n :swipe-actions=\"{ trailing: [{ label: 'Archive', onActivate: archive, variant: 'accent' }] }\"\n >\n <template #endContent>\n <Button label=\"Archive\" size=\"sm\" variant=\"secondary\" @click=\"archive\" />\n </template>\n </ListItem>\n </List>\n <p>{{ note }}</p>\n</template>"
59900
+ },
59901
+ {
59902
+ "title": {
59903
+ "en": "Flush rows",
59904
+ "zh": "标题对齐"
59905
+ },
59906
+ "description": {
59907
+ "en": "Static rows with flush drop their inline padding, so text, dividers, and backgrounds all sit on the heading's line.",
59908
+ "zh": "静态行用 flush 去掉左右内边距,文字、分隔线和背景都落在标题的竖线上。"
59909
+ },
59910
+ "code": "<script setup lang=\"ts\">\nimport { Heading } from '@astryx-vue/core/Heading'\nimport { List, ListItem } from '@astryx-vue/core/List'\n<\/script>\n\n<template>\n <div class=\"list-preview__section\">\n <Heading :level=\"4\">\n Shipping\n </Heading>\n <List edge=\"flush\" has-dividers>\n <ListItem label=\"Standard\" description=\"Arrives in 5–7 days\" />\n <ListItem label=\"Express\" description=\"Arrives tomorrow\" />\n <ListItem label=\"Pickup\" description=\"Ready at the counter\" />\n </List>\n </div>\n</template>"
59911
+ },
59912
+ {
59913
+ "title": {
59914
+ "en": "Edge in a card",
59915
+ "zh": "卡片内的行边缘"
59916
+ },
59917
+ "description": {
59918
+ "en": "Interactive rows in a Card use outset so text lines up with the heading while backgrounds keep their room; bleed runs rows to the card's edges with square corners, like table rows.",
59919
+ "zh": "Card 里的可交互行用 outset 让文字对齐标题、背景保留余量;bleed 则像表格行一样铺到卡片边缘,四角为直角。"
59920
+ },
59921
+ "code": "<script setup lang=\"ts\">\nimport { Card } from '@astryx-vue/core/Card'\nimport { Heading } from '@astryx-vue/core/Heading'\nimport { List, ListItem } from '@astryx-vue/core/List'\nimport { VStack } from '@astryx-vue/core/Stack'\nimport { ref } from 'vue'\n\nconst outsetSelected = ref('mentions')\nconst bleedSelected = ref('mentions')\n<\/script>\n\n<template>\n <div class=\"list-preview__stack\">\n <Card :width=\"360\">\n <VStack :gap=\"2\">\n <Heading :level=\"4\">\n Outset rows\n </Heading>\n <List edge=\"outset\" has-dividers>\n <ListItem\n label=\"Mentions\"\n description=\"Comments that name you\"\n :is-selected=\"outsetSelected === 'mentions'\"\n @click=\"outsetSelected = 'mentions'\"\n />\n <ListItem\n label=\"Follows\"\n description=\"New followers this week\"\n :is-selected=\"outsetSelected === 'follows'\"\n @click=\"outsetSelected = 'follows'\"\n />\n </List>\n </VStack>\n </Card>\n <Card :width=\"360\">\n <VStack :gap=\"2\">\n <Heading :level=\"4\">\n Bleed rows\n </Heading>\n <List edge=\"bleed\" has-dividers>\n <ListItem\n label=\"Mentions\"\n description=\"Comments that name you\"\n :is-selected=\"bleedSelected === 'mentions'\"\n @click=\"bleedSelected = 'mentions'\"\n />\n <ListItem\n label=\"Follows\"\n description=\"New followers this week\"\n :is-selected=\"bleedSelected === 'follows'\"\n @click=\"bleedSelected = 'follows'\"\n />\n </List>\n </VStack>\n </Card>\n </div>\n</template>"
59794
59922
  }
59795
59923
  ],
59796
59924
  "anatomy": {
@@ -76850,13 +76978,15 @@ const catalog = {
76850
76978
  "Themes",
76851
76979
  "Upgrading and checking a project",
76852
76980
  "Flags",
76853
- "Agent file"
76981
+ "Agent file",
76982
+ "From code"
76854
76983
  ],
76855
76984
  "zh": [
76856
76985
  "核心命令",
76857
76986
  "页面模板与代码生成",
76858
76987
  "常用参数",
76859
- "配合 AI Agent(生成上下文文件)"
76988
+ "配合 AI Agent(生成上下文文件)",
76989
+ "在代码里调用"
76860
76990
  ]
76861
76991
  },
76862
76992
  "keywords": {
@@ -76864,8 +76994,8 @@ const catalog = {
76864
76994
  "zh": []
76865
76995
  },
76866
76996
  "body": {
76867
- "en": "# CLI\n\n`@astryx-vue/cli` looks up astryx-vue component docs and playground pages from the terminal, for people and for agents. The binary is `astryx-vue`. It is not Meta's `astryx` command, and it does not speak React or StyleX.\n\nInstall it as a dev dependency:\n\n```sh\npnpm add -D @astryx-vue/cli\n```\n\nThe package declares a `bin`, so the command is on `node_modules/.bin` right after installing:\n\n```sh\npnpm exec astryx-vue component Button\nnpx astryx-vue search dialog --json\n```\n\nWithout installing it, run `npx @astryx-vue/cli …`. The scope is required: the bare `astryx-vue` name on npm is not this package.\n\nA script alias is optional, and only useful when an agent shell does not have `node_modules/.bin` on `PATH`:\n\n```json\n{\n \"scripts\": {\n \"astryx-vue\": \"astryx-vue\"\n }\n}\n```\n\n```sh\nnpm run astryx-vue -- component Button\n```\n\n## Commands\n\n`astryx-vue component --list` lists the documented components. `astryx-vue component Button` prints one component's usage, props, slots, events, and examples.\n\n`astryx-vue composable --list` lists the composables, and `astryx-vue composable useClipboard` prints one composable's parameters and return values. Vue calls these functions composables, so the CLI never says hook. `component useClipboard` still finds it and points at the `composable` command.\n\n`astryx-vue docs` lists the guide and theme topics. `astryx-vue docs guide/dark-mode` prints one page's text, and `astryx-vue docs themes/tokens` works too.\n\n`astryx-vue search dialog` ranks components, composables, docs topics, and page templates. `--type` narrows the domain to `component`, `composable`, `doc`, or `template`. `astryx-vue manifest --json` describes the commands, options, response types, error codes, and exit codes. `astryx-vue init --features agents` writes the agent context file.\n\n## Templates\n\n`astryx-vue template --list` lists the 57 playground pages, grouped by category. Every template is a page today; `--type block` answers with a note rather than an error. Each line says what the page needs: a ready template names only `vue` and `@astryx-vue/core`, so copying one leaves no install step.\n\n`astryx-vue template login-card` prints one template's description, the components it uses, the packages it needs, and its source. `--skeleton` prints only the layout skeleton: component tags with layout attributes such as `gap`, `padding`, and `columns`, without text, handlers, or native HTML wrappers.\n\n`astryx-vue template login-card ./src/pages` writes `src/pages/LoginCard.vue`. A path that ends in a file name is the file itself. An existing file is only replaced with `--overwrite` (or `-f`), and a path outside the project is refused. Copying rewrites the playground's demo photo paths to an inline grey placeholder and reports how many it replaced; it does not copy assets and does not install anything, it only names the packages the project is missing.\n\nA few templates import something a project cannot install, such as the playground-only `@astryx-vue/playground-charts` alias. They are marked not ready: you can read them, and copying is refused with the reason.\n\n## Build\n\n`astryx-vue build \"<idea>\"` turns one sentence into a starting point: the closest page template, two alternatives, and the components to use, each with the command to run next. It also lists composables separately.\n\nThe ranking is local and deterministic: keyword matching over each template's slug, title, description, keywords, and components, with rare words weighted higher. There is no network call and no model. Chinese is matched by character pairs, so no spaces are needed.\n\n`astryx-vue build` with no idea prints the workflow: pick a page, copy it, then look up each component before writing code.\n\n## Themes\n\n`astryx-vue theme` lists the built-in themes. `astryx-vue theme butter` prints the four steps that load one: the stylesheet import, the `data-astryx-theme` attribute, how to switch themes or run two side by side, and how to check the result. The steps are this site's own theme pages, so the command and the docs cannot disagree. It writes nothing.\n\n## Upgrading and checking a project\n\n`astryx-vue upgrade` reads the `@astryx-vue/core` version in the project's `package.json`, compares it with the version the CLI documents, and prints the sections of the [upgrade guide](/en/guide/migration) — what changed, how it was written before, how it is written now, what to do. It then looks through the project's own sources for the old names that guide replaces and lists file and line. Nothing is changed, and nothing is fetched from the network.\n\n`astryx-vue doctor` reports five checks and edits nothing: whether core is installed and how its version compares, whether themes is installed, whether `vue` satisfies 3.5.0, whether a theme stylesheet is imported, and whether `data-astryx-theme` is set. Exit code is 0 when everything passed or only warned, and 1 when a check failed.\n\n## Flags\n\n`--json` prints a typed envelope on stdout. `--dense` shortens the text people read; together with `--json` it is ignored, and the envelope stays complete. `--zh` uses Simplified Chinese where this site has a translation. `--cwd` is the project directory `template` and `init` write inside.\n\nThe template `--json` types are `template.list`, `template.show`, `template.skeleton`, and `template.copy`; `build` returns `build.help` and `build.kit`. Errors carry a code such as `ERR_UNKNOWN_TEMPLATE`, `ERR_TEMPLATE_NOT_READY`, `ERR_FILE_EXISTS`, or `ERR_PATH_TRAVERSAL`.\n\n## Agent file\n\n`init` prints the setup guide — which packages to install (a canary package takes `@canary`, once it is on npm), the theme import, the locale snippet, and what to verify — and writes the managed block below. It creates `AGENTS.md` when no agent file exists. Without `--agent`, an agent file the project already has is reused, in the order `AGENTS.md`, `CLAUDE.md`, `.cursorrules`, `.hermes.md`, `HERMES.md`. `--agent claude` writes `CLAUDE.md`, `hermes` writes `.hermes.md` (or `HERMES.md` when the project already has one), `cursor` and `codex` write `AGENTS.md`, and `all` writes every known file that exists. `--agent-docs-path <file>` writes exactly that file.\n\nThe command owns only the block between these markers:\n\n```html\n<!-- Maintained by astryx-vue, do not edit manually -->\n...\n<!-- astryx-vue block end -->\n```\n\nEvery run replaces that block with freshly generated text. Text outside the markers stays. If the file exists and the markers are missing, the block is appended.\n\nThe block names every package the CLI documents, marks the canary ones, and says plainly when a package is not on npm yet — those get no install command, so nobody is sent to a missing package. Then it describes a three-step workflow: run `build` to pick a page, run `template` to copy it, then run `component`, `composable`, or `docs` before writing code. It also says how to invoke the binary.\n\nThe install command follows the project's package manager: the `packageManager` field first, then a lockfile. When neither says which manager it is, all four forms are printed.\n\nThe sentences the CLI prints come from this docs site and from the playground pages. After a docs or template change, regenerate the catalog in the CLI package. The maintainer steps are in the `packages/cli` README.\n\nThis version does not include `swizzle`, `discover`, `gap-report`, `integration`, or `blog`, and there is no MCP server.",
76868
- "zh": "# 命令行\n\n`@astryx-vue/cli` 是一个轻巧的命令行工具,用来在终端里随时查 astryx-vue 的组件文档和演练场页面,方便开发者和 AI Agent 使用。二进制命令名是 `astryx-vue`(请注意:它专为本项目服务,并非 Meta 原版的 `astryx` 命令,也不提供 React 或 StyleX 的功能)。\n\n建议在项目中作为开发依赖安装:\n\n```sh\npnpm add -D @astryx-vue/cli\n```\n\n包里声明了 `bin`,装完命令就在 `node_modules/.bin` 里,直接就能用:\n\n```sh\npnpm exec astryx-vue component Button\nnpx astryx-vue search dialog --json\n```\n\n不安装、只想临时用时,运行 `npx @astryx-vue/cli …`。注意 scope 不能省略,因为 npm 上裸名 `astryx-vue` 并不是这个包。\n\n脚本别名是可选项,只有当 agent 的环境没有把 `node_modules/.bin` 放进 `PATH` 时才需要在 `package.json` 里添加:\n\n```json\n{\n \"scripts\": {\n \"astryx-vue\": \"astryx-vue\"\n }\n}\n```\n\n配置后即可在终端运行:\n\n```sh\nnpm run astryx-vue -- component Button\n```\n\n## 核心命令\n\n- `astryx-vue component --list`:列出当前所有可用组件。\n- `astryx-vue component Button`:快速查看指定组件的基本用法、Props 属性、插槽、事件以及代码示例。\n- `astryx-vue composable --list`:列出组合式函数,`astryx-vue composable useClipboard` 打印其参数和返回值。Vue 里这类函数叫组合式函数(composable),所以命令与文档中统一不使用 hook 这个词。若误输入 `component useClipboard` 仍然能查到,并会友好提示改用 `composable` 命令。\n- `astryx-vue docs`:列出指南与主题文档。`astryx-vue docs guide/dark-mode` 或 `astryx-vue docs themes/tokens` 可直接在终端阅读正文。\n- `astryx-vue search dialog`:在组件、组合式函数、文档主题和页面模板里按相关度搜索。`--type` 可以限定为 `component`、`composable`、`doc` 或 `template`。\n- `astryx-vue manifest --json`:输出 CLI 自身的接口定义清单(命令、选项、响应结构、错误码及退出码),方便被上层工具或脚本消费。\n- `astryx-vue theme`:列出全部内置主题;`astryx-vue theme butter` 输出这套主题的接入四步(引哪个样式表、`data-astryx-theme` 写什么、怎么换主题或同时用两套、改完怎么验证)。\n- `astryx-vue upgrade`:读项目里 `@astryx-vue/core` 的版本,和这个 CLI 对应版本比较,打印[升级指南](/guide/migration)里要做的每一件事,再在项目源码里指出旧名字所在文件和行号。不联网,不改文件。\n- `astryx-vue doctor`:检查依赖装没装、版本对不对、主题样式引没引、主题属性设没设,只报告不改文件;有失败时退出码是 1。\n- `astryx-vue init`:打印给 AI 助手的接入说明(装哪些包、主题样式引哪一行、语言怎么提供),并在项目根目录维护 agent 上下文文件。\n\n## 页面模板与代码生成\n\n- `astryx-vue template --list`:列出 57 个演练场页面模板,按分类分组。目前所有模板都是页面;`--type block` 会给出一句说明,不报错。每行还会说明这个页面要什么:可复制的模板只依赖 `vue` 和 `@astryx-vue/core`,落地后不用再装别的包。\n- `astryx-vue template login-card`:打印一个模板的说明、用到的组件、要装的包和完整源码。`--skeleton` 只打印布局骨架(组件标签加上 `gap`、`padding`、`columns` 这类布局属性,不含文字、事件处理和原生 HTML 包裹层)。\n- `astryx-vue template login-card ./src/pages`:将模板代码生成并写出到 `src/pages/LoginCard.vue`。如果文件已存在,需要加上 `--overwrite`(或 `-f`)才会覆盖,避免误冲掉已有代码。复制时会自动把演示图片替换为内联的灰色占位图,不依赖额外的静态图片资源。\n- `astryx-vue build \"<想法>\"`:把一句话需求变成开发起点,在本地匹配出最接近的页面模板、备选方案以及需要搭配使用的组件与函数。打分完全在本地完成,无网络开销也无需调用外部大模型。\n\n## 常用参数\n\n- `--json`:以 JSON 格式输出带类型的结构化信封数据,适合被工具或脚本解析。模板相关的类型包括 `template.list`、`template.show`、`template.skeleton`、`template.copy`;`build` 返回 `build.help` 和 `build.kit`。出错时信封里带有错误码(如 `ERR_UNKNOWN_TEMPLATE`、`ERR_FILE_EXISTS` 等)。\n- `--dense`:精简输出文本,降低阅读噪音;与 `--json` 配合使用时会被自动忽略,保持 JSON 结构完整。\n- `--zh`:当文档具备中文翻译时,优先输出简体中文内容。\n- `--cwd`:指定项目目录,`template` 和 `init` 只往这个目录下写文件,`upgrade` 和 `doctor` 只在这个目录里读。\n\n## 配合 AI Agent(生成上下文文件)\n\n项目里没有任何 agent 文件时,`init` 默认创建 `AGENTS.md`。不加 `--agent` 时会优先写项目里已有的配置文件,检测顺序为 `AGENTS.md`、`CLAUDE.md`、`.cursorrules`、`.hermes.md`、`HERMES.md`。`--agent claude` 写 `CLAUDE.md`,`hermes` 写 `.hermes.md`,`cursor` 和 `codex` 写 `AGENTS.md`,`all` 则写所有已存在的已知文件。通过 `--agent-docs-path <file>` 也可以指定输出到任意文件。\n\n命令行工具在维护这些文件时,只会在以下固定的起止标记之间更新内容:\n\n```html\n<!-- Maintained by astryx-vue, do not edit manually -->\n...\n<!-- astryx-vue block end -->\n```\n\n区块开头列出这个 CLI 覆盖的每个包,能发 canary 的标出来;npm 上还没有的包会直说「还没发布到 npm」,并且不给安装命令,免得有人照着敲一个装不到的包名。随后是三步工作流:用 `build` 挑页面,用 `template` 复制,写代码前用 `component`、`composable` 或 `docs` 查询用法。\n\n`init` 打印的安装命令跟着项目走:先看 `packageManager` 字段,再看锁文件;两样都没有时把四种写法都列出来。\n\n每次运行命令,工具只会用最新的组件库信息替换这两个标记之间的内容,标记之外由你自己撰写的项目规则会完好无损地保留下来。如果文件已存在但尚未包含这些标记,工具会安全地将其追加到文件末尾。\n\n> **维护者提醒**:CLI 打印的内容均提取自当前文档站和演练场页面。当文档或模板修改后,需要重新生成 CLI 包内置的目录快照,操作步骤记录在 `packages/cli` 的 README 中。\n>\n> 这一版暂未包含 `swizzle`、`discover`、`gap-report`、`integration`、`blog`,也没有 MCP 服务。"
76997
+ "en": "# CLI\n\n`@astryx-vue/cli` looks up astryx-vue component docs and playground pages from the terminal, for people and for agents. The binary is `astryx-vue`. It is not Meta's `astryx` command, and it does not speak React or StyleX.\n\nInstall it as a dev dependency:\n\n```sh\npnpm add -D @astryx-vue/cli\n```\n\nThe package declares a `bin`, so the command is on `node_modules/.bin` right after installing:\n\n```sh\npnpm exec astryx-vue component Button\nnpx astryx-vue search dialog --json\n```\n\nWithout installing it, run `npx @astryx-vue/cli …`. The scope is required: the bare `astryx-vue` name on npm is not this package.\n\nA script alias is optional, and only useful when an agent shell does not have `node_modules/.bin` on `PATH`:\n\n```json\n{\n \"scripts\": {\n \"astryx-vue\": \"astryx-vue\"\n }\n}\n```\n\n```sh\nnpm run astryx-vue -- component Button\n```\n\n## Commands\n\n`astryx-vue component --list` lists the documented components. `astryx-vue component Button` prints one component's usage, props, slots, events, and examples.\n\n`astryx-vue composable --list` lists the composables, and `astryx-vue composable useClipboard` prints one composable's parameters and return values. Vue calls these functions composables, so the CLI never says hook. `component useClipboard` still finds it and points at the `composable` command.\n\n`astryx-vue docs` lists the guide and theme topics. `astryx-vue docs guide/dark-mode` prints one page's text, and `astryx-vue docs themes/tokens` works too.\n\n`astryx-vue search dialog` ranks components, composables, docs topics, and page templates. `--type` narrows the domain to `component`, `composable`, `doc`, or `template`. `astryx-vue manifest --json` describes the commands, options, response types, error codes, and exit codes. `astryx-vue init --features agents` writes the agent context file.\n\n## Templates\n\n`astryx-vue template --list` lists the 57 playground pages, grouped by category. Every template is a page today; `--type block` answers with a note rather than an error. Each line says what the page needs: a ready template names only `vue` and `@astryx-vue/core`, so copying one leaves no install step.\n\n`astryx-vue template login-card` prints one template's description, the components it uses, the packages it needs, and its source. `--skeleton` prints only the layout skeleton: component tags with layout attributes such as `gap`, `padding`, and `columns`, without text, handlers, or native HTML wrappers.\n\n`astryx-vue template login-card ./src/pages` writes `src/pages/LoginCard.vue`. A path that ends in a file name is the file itself. An existing file is only replaced with `--overwrite` (or `-f`), and a path outside the project is refused. Copying rewrites the playground's demo photo paths to an inline grey placeholder and reports how many it replaced; it does not copy assets and does not install anything, it only names the packages the project is missing.\n\nA few templates import something a project cannot install, such as the playground-only `@astryx-vue/playground-charts` alias. They are marked not ready: you can read them, and copying is refused with the reason.\n\n## Build\n\n`astryx-vue build \"<idea>\"` turns one sentence into a starting point: the closest page template, two alternatives, and the components to use, each with the command to run next. It also lists composables separately.\n\nThe ranking is local and deterministic: keyword matching over each template's slug, title, description, keywords, and components, with rare words weighted higher. There is no network call and no model. Chinese is matched by character pairs, so no spaces are needed.\n\n`astryx-vue build` with no idea prints the workflow: pick a page, copy it, then look up each component before writing code.\n\n## Themes\n\n`astryx-vue theme` lists the built-in themes. `astryx-vue theme butter` prints the four steps that load one: the stylesheet import, the `data-astryx-theme` attribute, how to switch themes or run two side by side, and how to check the result. The steps are this site's own theme pages, so the command and the docs cannot disagree. It writes nothing.\n\n## Upgrading and checking a project\n\n`astryx-vue upgrade` reads the `@astryx-vue/core` version in the project's `package.json`, compares it with the version the CLI documents, and prints the sections of the [upgrade guide](/en/guide/migration) — what changed, how it was written before, how it is written now, what to do. It then looks through the project's own sources for the old names that guide replaces and lists file and line. Nothing is changed, and nothing is fetched from the network.\n\n`astryx-vue doctor` reports five checks and edits nothing: whether core is installed and how its version compares, whether themes is installed, whether `vue` satisfies 3.5.0, whether a theme stylesheet is imported, and whether `data-astryx-theme` is set. Exit code is 0 when everything passed or only warned, and 1 when a check failed.\n\n## Flags\n\n`--json` prints a typed envelope on stdout. `--dense` shortens the text people read; together with `--json` it is ignored, and the envelope stays complete. `--zh` uses Simplified Chinese where this site has a translation. `--cwd` is the project directory `template` and `init` write inside.\n\nThe template `--json` types are `template.list`, `template.show`, `template.skeleton`, and `template.copy`; `build` returns `build.help` and `build.kit`. Errors carry a code such as `ERR_UNKNOWN_TEMPLATE`, `ERR_TEMPLATE_NOT_READY`, `ERR_FILE_EXISTS`, or `ERR_PATH_TRAVERSAL`.\n\n## Agent file\n\n`init` prints the setup guide — which packages to install (a canary package takes `@canary`, once it is on npm), the theme import, the locale snippet, and what to verify — and writes the managed block below. It creates `AGENTS.md` when no agent file exists. Without `--agent`, an agent file the project already has is reused, in the order `AGENTS.md`, `CLAUDE.md`, `.cursorrules`, `.hermes.md`, `HERMES.md`. `--agent claude` writes `CLAUDE.md`, `hermes` writes `.hermes.md` (or `HERMES.md` when the project already has one), `cursor` and `codex` write `AGENTS.md`, and `all` writes every known file that exists. `--agent-docs-path <file>` writes exactly that file.\n\nThe command owns only the block between these markers:\n\n```html\n<!-- Maintained by astryx-vue, do not edit manually -->\n...\n<!-- astryx-vue block end -->\n```\n\nEvery run replaces that block with freshly generated text. Text outside the markers stays. If the file exists and the markers are missing, the block is appended.\n\nThe block names every package the CLI documents, marks the canary ones, and says plainly when a package is not on npm yet — those get no install command, so nobody is sent to a missing package. Then it describes a three-step workflow: run `build` to pick a page, run `template` to copy it, then run `component`, `composable`, or `docs` before writing code. It also says how to invoke the binary.\n\nThe install command follows the project's package manager: the `packageManager` field first, then a lockfile. When neither says which manager it is, all four forms are printed.\n\nThe sentences the CLI prints come from this docs site and from the playground pages. After a docs or template change, regenerate the catalog in the CLI package. The maintainer steps are in `packages/cli/CONTRIBUTING.md`.\n\n## From code\n\n`runCli` takes the same arguments as the binary and returns `{ exitCode, stdout, stderr }`:\n\n```ts\nimport { assertResponse, runCli } from '@astryx-vue/cli'\n\nconst result = await runCli(['search', 'dialog', '--json'], { cwd: process.cwd() })\nconst found = assertResponse(result.stdout, 'search')\n```\n\n`assertResponse` narrows the envelope to the type you expect. `parseResponse` returns the envelope without asserting, and `isError` checks one.\n\nThis version does not include `swizzle`, `discover`, `gap-report`, `integration`, or `blog`, and there is no MCP server.",
76998
+ "zh": "# 命令行\n\n`@astryx-vue/cli` 是一个轻巧的命令行工具,用来在终端里随时查 astryx-vue 的组件文档和演练场页面,方便开发者和 AI Agent 使用。二进制命令名是 `astryx-vue`(请注意:它专为本项目服务,并非 Meta 原版的 `astryx` 命令,也不提供 React 或 StyleX 的功能)。\n\n建议在项目中作为开发依赖安装:\n\n```sh\npnpm add -D @astryx-vue/cli\n```\n\n包里声明了 `bin`,装完命令就在 `node_modules/.bin` 里,直接就能用:\n\n```sh\npnpm exec astryx-vue component Button\nnpx astryx-vue search dialog --json\n```\n\n不安装、只想临时用时,运行 `npx @astryx-vue/cli …`。注意 scope 不能省略,因为 npm 上裸名 `astryx-vue` 并不是这个包。\n\n脚本别名是可选项,只有当 agent 的环境没有把 `node_modules/.bin` 放进 `PATH` 时才需要在 `package.json` 里添加:\n\n```json\n{\n \"scripts\": {\n \"astryx-vue\": \"astryx-vue\"\n }\n}\n```\n\n配置后即可在终端运行:\n\n```sh\nnpm run astryx-vue -- component Button\n```\n\n## 核心命令\n\n- `astryx-vue component --list`:列出当前所有可用组件。\n- `astryx-vue component Button`:快速查看指定组件的基本用法、Props 属性、插槽、事件以及代码示例。\n- `astryx-vue composable --list`:列出组合式函数,`astryx-vue composable useClipboard` 打印其参数和返回值。Vue 里这类函数叫组合式函数(composable),所以命令与文档中统一不使用 hook 这个词。若误输入 `component useClipboard` 仍然能查到,并会友好提示改用 `composable` 命令。\n- `astryx-vue docs`:列出指南与主题文档。`astryx-vue docs guide/dark-mode` 或 `astryx-vue docs themes/tokens` 可直接在终端阅读正文。\n- `astryx-vue search dialog`:在组件、组合式函数、文档主题和页面模板里按相关度搜索。`--type` 可以限定为 `component`、`composable`、`doc` 或 `template`。\n- `astryx-vue manifest --json`:输出 CLI 自身的接口定义清单(命令、选项、响应结构、错误码及退出码),方便被上层工具或脚本消费。\n- `astryx-vue theme`:列出全部内置主题;`astryx-vue theme butter` 输出这套主题的接入四步(引哪个样式表、`data-astryx-theme` 写什么、怎么换主题或同时用两套、改完怎么验证)。\n- `astryx-vue upgrade`:读项目里 `@astryx-vue/core` 的版本,和这个 CLI 对应版本比较,打印[升级指南](/guide/migration)里要做的每一件事,再在项目源码里指出旧名字所在文件和行号。不联网,不改文件。\n- `astryx-vue doctor`:检查依赖装没装、版本对不对、主题样式引没引、主题属性设没设,只报告不改文件;有失败时退出码是 1。\n- `astryx-vue init`:打印给 AI 助手的接入说明(装哪些包、主题样式引哪一行、语言怎么提供),并在项目根目录维护 agent 上下文文件。\n\n## 页面模板与代码生成\n\n- `astryx-vue template --list`:列出 57 个演练场页面模板,按分类分组。目前所有模板都是页面;`--type block` 会给出一句说明,不报错。每行还会说明这个页面要什么:可复制的模板只依赖 `vue` 和 `@astryx-vue/core`,落地后不用再装别的包。\n- `astryx-vue template login-card`:打印一个模板的说明、用到的组件、要装的包和完整源码。`--skeleton` 只打印布局骨架(组件标签加上 `gap`、`padding`、`columns` 这类布局属性,不含文字、事件处理和原生 HTML 包裹层)。\n- `astryx-vue template login-card ./src/pages`:将模板代码生成并写出到 `src/pages/LoginCard.vue`。如果文件已存在,需要加上 `--overwrite`(或 `-f`)才会覆盖,避免误冲掉已有代码。复制时会自动把演示图片替换为内联的灰色占位图,不依赖额外的静态图片资源。\n- `astryx-vue build \"<想法>\"`:把一句话需求变成开发起点,在本地匹配出最接近的页面模板、备选方案以及需要搭配使用的组件与函数。打分完全在本地完成,无网络开销也无需调用外部大模型。\n\n## 常用参数\n\n- `--json`:以 JSON 格式输出带类型的结构化信封数据,适合被工具或脚本解析。模板相关的类型包括 `template.list`、`template.show`、`template.skeleton`、`template.copy`;`build` 返回 `build.help` 和 `build.kit`。出错时信封里带有错误码(如 `ERR_UNKNOWN_TEMPLATE`、`ERR_FILE_EXISTS` 等)。\n- `--dense`:精简输出文本,降低阅读噪音;与 `--json` 配合使用时会被自动忽略,保持 JSON 结构完整。\n- `--zh`:当文档具备中文翻译时,优先输出简体中文内容。\n- `--cwd`:指定项目目录,`template` 和 `init` 只往这个目录下写文件,`upgrade` 和 `doctor` 只在这个目录里读。\n\n## 配合 AI Agent(生成上下文文件)\n\n项目里没有任何 agent 文件时,`init` 默认创建 `AGENTS.md`。不加 `--agent` 时会优先写项目里已有的配置文件,检测顺序为 `AGENTS.md`、`CLAUDE.md`、`.cursorrules`、`.hermes.md`、`HERMES.md`。`--agent claude` 写 `CLAUDE.md`,`hermes` 写 `.hermes.md`,`cursor` 和 `codex` 写 `AGENTS.md`,`all` 则写所有已存在的已知文件。通过 `--agent-docs-path <file>` 也可以指定输出到任意文件。\n\n命令行工具在维护这些文件时,只会在以下固定的起止标记之间更新内容:\n\n```html\n<!-- Maintained by astryx-vue, do not edit manually -->\n...\n<!-- astryx-vue block end -->\n```\n\n区块开头列出这个 CLI 覆盖的每个包,能发 canary 的标出来;npm 上还没有的包会直说「还没发布到 npm」,并且不给安装命令,免得有人照着敲一个装不到的包名。随后是三步工作流:用 `build` 挑页面,用 `template` 复制,写代码前用 `component`、`composable` 或 `docs` 查询用法。\n\n`init` 打印的安装命令跟着项目走:先看 `packageManager` 字段,再看锁文件;两样都没有时把四种写法都列出来。\n\n每次运行命令,工具只会用最新的组件库信息替换这两个标记之间的内容,标记之外由你自己撰写的项目规则会完好无损地保留下来。如果文件已存在但尚未包含这些标记,工具会安全地将其追加到文件末尾。\n\n> **维护者提醒**:CLI 打印的内容均提取自当前文档站和演练场页面。当文档或模板修改后,需要重新生成 CLI 包内置的目录快照,操作步骤记录在 `packages/cli/CONTRIBUTING.md` 中。\n\n## 在代码里调用\n\n`runCli` 收的参数和命令行一致,返回 `{ exitCode, stdout, stderr }`:\n\n```ts\nimport { assertResponse, runCli } from '@astryx-vue/cli'\n\nconst result = await runCli(['search', 'dialog', '--json'], { cwd: process.cwd() })\nconst found = assertResponse(result.stdout, 'search')\n```\n\n`assertResponse` 按你要的类型校验信封;`parseResponse` 只解析不断言,`isError` 判断是不是错误信封。\n>\n> 这一版暂未包含 `swizzle`、`discover`、`gap-report`、`integration`、`blog`,也没有 MCP 服务。"
76869
76999
  },
76870
77000
  "docsUrl": {
76871
77001
  "en": "https://astryx-vue.pages.dev/en/guide/cli",
@@ -82670,7 +82800,7 @@ function wantsJson(argv) {
82670
82800
  }
82671
82801
  //#endregion
82672
82802
  //#region package.json
82673
- var version = "0.4.2";
82803
+ var version = "0.4.4";
82674
82804
  //#endregion
82675
82805
  //#region src/envelope.ts
82676
82806
  function successEnvelope(type, data, packageName) {
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@astryx-vue/cli",
3
3
  "type": "module",
4
- "version": "0.4.2",
4
+ "version": "0.4.4",
5
5
  "description": "Agent-friendly CLI for the unofficial astryx-vue port of Astryx. Not affiliated with Meta.",
6
6
  "author": "ShenQingchuan",
7
7
  "license": "MIT",
@@ -32,6 +32,7 @@
32
32
  "astryx-vue": "./bin/astryx-vue.mjs"
33
33
  },
34
34
  "files": [
35
+ "CONTRIBUTING.md",
35
36
  "README.md",
36
37
  "README.zh-CN.md",
37
38
  "bin",