@yargote/artifacts-cli 0.1.0-beta.2 → 0.1.0-beta.3

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -11,9 +11,9 @@ Use Node 24 or later. The beta targets macOS and Linux. Windows has not been ver
11
11
  Choose one persistent installation:
12
12
 
13
13
  ```sh
14
- npm install --global @yargote/artifacts-cli@0.1.0-beta.2
14
+ npm install --global @yargote/artifacts-cli@0.1.0-beta.3
15
15
  # Or, with pnpm and an existing global bin directory on PATH:
16
- pnpm add --global @yargote/artifacts-cli@0.1.0-beta.2
16
+ pnpm add --global @yargote/artifacts-cli@0.1.0-beta.3
17
17
  artifacts --version
18
18
  ```
19
19
 
@@ -22,8 +22,8 @@ If pnpm reports that no global bin directory exists, run `pnpm setup` and reopen
22
22
  For one-off execution, pin the same version:
23
23
 
24
24
  ```sh
25
- npx --yes @yargote/artifacts-cli@0.1.0-beta.2 --help
26
- pnpm dlx @yargote/artifacts-cli@0.1.0-beta.2 --help
25
+ npx --yes @yargote/artifacts-cli@0.1.0-beta.3 --help
26
+ pnpm dlx @yargote/artifacts-cli@0.1.0-beta.3 --help
27
27
  ```
28
28
 
29
29
  `@beta` follows future prereleases. Exact versions remain reproducible. No stable `latest` release is part of this beta. An organization's pnpm minimum release age or trust policy can delay a new release. Follow that policy rather than disabling it globally.
@@ -134,9 +134,9 @@ Global `--server RESOURCE` and `--state-dir DIR` options can appear before or af
134
134
 
135
135
  Check `artifacts --help` for `skills` support. The published `0.1.0-beta.1` package predates this command group. These commands describe the current source and packages built from it.
136
136
 
137
- `artifacts skills` and `artifacts skills list` print each bundled skill's name and description, separated by a tab. The initial catalog contains `artifacts-cli`. Its complete source directory is bundled with the CLI, so the instructions match the installed version and work offline.
137
+ `artifacts skills` and `artifacts skills list` print each bundled skill's name and description, separated by a tab. The catalog contains `artifacts-cli` and `artifacts-design`, which owns HTML design, bundled fonts, and the font embedding script. Their complete source directories are bundled with the CLI, so the instructions match the installed version and work offline.
138
138
 
139
- `artifacts skills get artifacts-cli` prints the complete `SKILL.md` without headers or changes to its bytes. `--full` includes every bundled reference and template. Each file starts with a catalog-relative marker such as `=== artifacts-cli/references/commands.md ===`. `artifacts skills get --all` prints every `SKILL.md` with these markers. Add `--full` to include every skill's support files.
139
+ `artifacts skills get artifacts-cli` prints the complete `SKILL.md` without headers or changes to its bytes. `--full` includes every bundled reference and template. Binary files such as fonts appear as a marker with their size, for example `=== artifacts-design/fonts/public-sans.woff2 (binary, 26636 bytes) ===`; read them from `artifacts skills path artifacts-design`. Each file starts with a catalog-relative marker such as `=== artifacts-cli/references/commands.md ===`. `artifacts skills get --all` prints every `SKILL.md` with these markers. Add `--full` to include every skill's support files.
140
140
 
141
141
  `artifacts skills path` prints the absolute bundled catalog directory. Add a skill name to print that skill's directory. These paths belong to the CLI package and do not depend on the current directory.
142
142
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@yargote/artifacts-cli",
3
- "version": "0.1.0-beta.2",
3
+ "version": "0.1.0-beta.3",
4
4
  "private": false,
5
5
  "type": "module",
6
6
  "description": "Command-line client for the Artifacts REST API.",
@@ -27,13 +27,14 @@
27
27
  "skills"
28
28
  ],
29
29
  "dependencies": {
30
- "@yargote/artifacts-contracts": "0.1.0-beta.2",
31
- "@yargote/artifacts-client": "0.1.0-beta.2",
30
+ "@yargote/artifacts-contracts": "0.1.0-beta.3",
31
+ "@yargote/artifacts-client": "0.1.0-beta.3",
32
32
  "openid-client": "6.8.8",
33
33
  "zod": "4.6.5"
34
34
  },
35
35
  "artifactsSkills": [
36
- "artifacts-cli"
36
+ "artifacts-cli",
37
+ "artifacts-design"
37
38
  ],
38
39
  "publishConfig": {
39
40
  "access": "public",
@@ -21,7 +21,7 @@ Use self-contained HTML for interactive content. Inline styles and scripts and e
21
21
 
22
22
  Keep the whole revision within 1 MiB of decoded content, metadata, attachment paths, media types, and attachment bytes. At most 32 attachments are accepted. Base64 changes wire size, not the decoded limit. If required content exceeds a limit, explain the constraint without silently removing it.
23
23
 
24
- Finish requested visual and interaction checks before saving when browser tools are available. A successful CLI write proves persistence, not visual quality or browser compatibility. Interactive HTML follows the server's Chrome policy; do not promise it in every browser.
24
+ Before writing or restyling HTML, run `artifacts skills get artifacts-design --full` and follow it. It owns typography, embedded fonts, color, layout, and the rendered checks. Finish those checks before saving when browser tools are available. A successful CLI write proves persistence, not visual quality or browser compatibility. Interactive HTML follows the server's Chrome policy; do not promise it in every browser.
25
25
 
26
26
  ## Create or revise
27
27
 
@@ -0,0 +1,76 @@
1
+ ---
2
+ name: artifacts-design
3
+ description: Design the HTML document of a retained Artifact with embedded fonts, light and dark Catppuccin tokens, purposeful layout, and rendered checks. Use before writing or restyling HTML for the artifacts or artifacts-cli skills.
4
+ ---
5
+
6
+ # Design HTML artifacts
7
+
8
+ The calling skill owns the snapshot, size limit, and save workflow. This skill owns how the document looks and behaves. Follow the user's visual direction first. When revising content only, keep an existing artifact's established style. When the user asks to redesign or improve the look, start from these defaults.
9
+
10
+ The document renders in a sandbox that blocks every network request. Fonts, images, and icons load only as `data:` URIs. External stylesheets, Google Fonts links, and CDN scripts silently fail. Inline all CSS and JavaScript.
11
+
12
+ ## Start from the starter
13
+
14
+ Copy the `<style>` block of [the starter](templates/starter.html) into the new document. It defines the color, font, and spacing tokens and the shared primitives: eyebrow and label, lede, summary strip, pills, expandable rows, panel, note, table, filter bar, and footer. Delete primitives the page does not use. Derive every new color and type decision from its tokens.
15
+
16
+ The starter supplies a vocabulary, not a layout. Compose the body for the reader's task:
17
+
18
+ | Purpose | Composition |
19
+ | --- | --- |
20
+ | Report or guide | A conclusion first, a reading column near 68 characters, and evidence beside the claim it supports. |
21
+ | Inventory, ledger, or status | A summary strip, a filter bar with a live count, then one `.row` per record. Its summary shows the identifier, title, and state; its detail holds everything else. Set `--cols` on `.rows` to match the summary cells. Rows stack to one column below 720 px. |
22
+ | Compare options | Aligned criteria in a table, consistent scales, and the tradeoff stated in words. |
23
+ | Explore data | A dominant chart or table sized to carry the page, with filters beside it. |
24
+ | Calculate or simulate | Labeled inputs beside the result they change, with units and a realistic starting example. |
25
+ | Explain a concept | A sequence of large diagrams with short captions; read [visual explanations](references/visual-explanations.md). |
26
+
27
+ Open in a complete state with the user's real data. Never ship an empty shell or placeholder text.
28
+
29
+ ## Typography
30
+
31
+ Use the bundled faces through the starter's tokens:
32
+
33
+ - `--f-display` is Bricolage Grotesque. Use it for `h1` to `h3` and large figures only.
34
+ - `--f-body` is Public Sans for running text and controls.
35
+ - `--f-mono` is JetBrains Mono for code, identifiers, table headers, eyebrows, and labels.
36
+ - Newsreader is an optional reading face. For a long essay or editorial guide, set `--f-body` to `"Newsreader",Charter,Georgia,serif` and keep Public Sans for controls.
37
+
38
+ Keep the starter's scale: `h1` at `clamp(28px,4vw,40px)`, body at 15 to 16 px with a 1.55 line height, uppercase mono labels at 11 to 12 px with `.06em` tracking, and `text-wrap: balance` on headings. Use `font-variant-numeric: tabular-nums` wherever digits align.
39
+
40
+ After the HTML file is complete, embed the fonts it names:
41
+
42
+ ```sh
43
+ node <skill-directory>/scripts/embed-fonts.mjs page.html
44
+ ```
45
+
46
+ For the CLI, `<skill-directory>` is the output of `artifacts skills path artifacts-design`. The script rewrites the file in place and is safe to rerun after edits. It embeds only the families the page names and adds italic faces only for `font-style: italic`, `<em>`, `<i>`, or `<cite>`. It refuses to write a page above 1 MiB. Three families add about 135 KB. Newsreader adds about 80 to 165 KB. Do not name another family unless you embed its `woff2` yourself, because an unembedded face silently falls back. Without Node, keep the fallback stacks and report that the fonts were not embedded.
47
+
48
+ ## Color
49
+
50
+ The starter follows the viewer's system theme. Light uses Catppuccin Latte and dark uses Mocha, with Blue as the only accent. [Catppuccin roles](references/catppuccin.md) lists every token and the extra chart colors.
51
+
52
+ - Define each color once as a token on bare `:root` and redefine only the tokens in both dark blocks. Never write a literal color in a component rule.
53
+ - Keep `body` on `var(--page)`. Use `--surface` for the objects you set apart, and `--sunken` for table headers and hover.
54
+ - Show state with soft pills: a `*-soft` background and `*-ink` text. Do not fill badges with saturated pastels. Map every state to neutral, accent, ok, warn, or bad. Do not add a hue for an extra state; distinguish it with its label.
55
+ - Use the accent for links, focus, the selected state, and one primary action. Status colors carry meaning only.
56
+ - When the user names a single dark look, set the Mocha values on bare `:root`, keep `color-scheme: dark`, and drop the media blocks.
57
+
58
+ ## Layout and components
59
+
60
+ - Center one `.wrap` column at `--width` with `max(16px, env(safe-area-inset-*))` side gutters. Space siblings with grid or flex `gap`.
61
+ - Use borders and fills by role. A summary strip, a table, and one panel can each earn a surface. Running text and headings sit on the page.
62
+ - Mark structure that is true: an eyebrow names the source or scope, a label names a field, and numbering appears only for real sequences.
63
+ - Encode attention in form. A row that needs action gets a pill or a muted closed state, not only a different word.
64
+ - Put wide code and diagrams in their own `overflow-x: auto` container. Use a `<table>` only for short records that stay readable when scrolled; use expandable rows for records with long text. The page itself never scrolls sideways at 375 px.
65
+ - Draw diagrams in inline SVG with token colors and labels inside the view box. For icons, use inline Tabler outline SVGs with `currentColor`.
66
+ - Respect `prefers-reduced-motion`. Use motion only to explain a change of state.
67
+
68
+ ## Copy
69
+
70
+ Give the `<title>` a specific name of two to four words, or the name the user already uses for the document. Write labels from the reader's side, in the user's language. State units next to numbers. Put the source and date of the data in the footer.
71
+
72
+ ## Check the rendered page
73
+
74
+ Run the [interface checks](references/interface-checks.md) when browser tools are available. Render at 375 px and 1280 px in both light and dark. Confirm that the fonts loaded, nothing overflows, and every control changes something visible. Fix what you see once, then save. Report checks you could not run.
75
+
76
+ The license notices for bundled fonts and adapted guidance are in [third-party notices](THIRD-PARTY-NOTICES.md).
@@ -0,0 +1,124 @@
1
+ # Third-party notices
2
+
3
+ These notices apply to the named adapted references and bundled fonts. They do not set a license
4
+ for the Artifacts application or the original Artifacts skill instructions.
5
+
6
+ ## Emil Kowalski skills
7
+
8
+ Adapted in [references/interface-checks.md](references/interface-checks.md). [Upstream license](https://raw.githubusercontent.com/emilkowalski/skills/refs/heads/main/LICENSE).
9
+
10
+ ```text
11
+ MIT License
12
+
13
+ Copyright (c) 2026 Emil Kowalski
14
+
15
+ Permission is hereby granted, free of charge, to any person obtaining a copy
16
+ of this software and associated documentation files (the "Software"), to deal
17
+ in the Software without restriction, including without limitation the rights
18
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
19
+ copies of the Software, and to permit persons to whom the Software is
20
+ furnished to do so, subject to the following conditions:
21
+
22
+ The above copyright notice and this permission notice shall be included in all
23
+ copies or substantial portions of the Software.
24
+
25
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
26
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
27
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
28
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
29
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
30
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
31
+ SOFTWARE.
32
+ ```
33
+
34
+ ## Vercel Web Interface Guidelines
35
+
36
+ Adapted in [references/interface-checks.md](references/interface-checks.md). [Upstream license](https://raw.githubusercontent.com/vercel-labs/web-interface-guidelines/refs/heads/main/LICENSE).
37
+
38
+ ```text
39
+ MIT License
40
+
41
+ Copyright (c) 2025 Vercel Labs
42
+
43
+ Permission is hereby granted, free of charge, to any person obtaining a copy
44
+ of this software and associated documentation files (the "Software"), to deal
45
+ in the Software without restriction, including without limitation the rights
46
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
47
+ copies of the Software, and to permit persons to whom the Software is
48
+ furnished to do so, subject to the following conditions:
49
+
50
+ The above copyright notice and this permission notice shall be included in all
51
+ copies or substantial portions of the Software.
52
+
53
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
54
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
55
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
56
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
57
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
58
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
59
+ SOFTWARE.
60
+ ```
61
+
62
+ ## Catppuccin palette
63
+
64
+ Adapted in [references/catppuccin.md](references/catppuccin.md). [Upstream license](https://raw.githubusercontent.com/catppuccin/palette/refs/heads/main/LICENSE).
65
+
66
+ ```text
67
+ MIT License
68
+
69
+ Copyright (c) 2021 Catppuccin
70
+
71
+ Permission is hereby granted, free of charge, to any person obtaining a copy
72
+ of this software and associated documentation files (the "Software"), to deal
73
+ in the Software without restriction, including without limitation the rights
74
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
75
+ copies of the Software, and to permit persons to whom the Software is
76
+ furnished to do so, subject to the following conditions:
77
+
78
+ The above copyright notice and this permission notice shall be included in all
79
+ copies or substantial portions of the Software.
80
+
81
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
82
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
83
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
84
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
85
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
86
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
87
+ SOFTWARE.
88
+ ```
89
+
90
+ ## Vercel Agent Skills
91
+
92
+ Adapted in [interface guidelines](references/interface-checks.md).
93
+ [Upstream repository](https://github.com/vercel-labs/agent-skills).
94
+ This notice is retained from the reviewed Packy Web Pack.
95
+
96
+ ```text
97
+ Vercel Agent Skills
98
+
99
+ The upstream repository declares these skills licensed under the MIT License.
100
+
101
+ Copyright (c) Vercel
102
+
103
+ Permission is hereby granted, free of charge, to any person obtaining a copy
104
+ of this software and associated documentation files (the "Software"), to deal
105
+ in the Software without restriction, including without limitation the rights
106
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
107
+ copies of the Software, and to permit persons to whom the Software is
108
+ furnished to do so, subject to the following conditions:
109
+
110
+ The above copyright notice and this permission notice shall be included in all
111
+ copies or substantial portions of the Software.
112
+
113
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
114
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
115
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
116
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
117
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
118
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
119
+ SOFTWARE.
120
+ ```
121
+
122
+ ## Bundled fonts
123
+
124
+ The files in [fonts](fonts) are the Latin subsets of Bricolage Grotesque, Public Sans, JetBrains Mono, and Newsreader, as served by Google Fonts on 2026-10-17. Each is licensed under the SIL Open Font License, Version 1.1. The copyright lines and the complete license text are in [fonts/OFL.txt](fonts/OFL.txt).
@@ -0,0 +1,96 @@
1
+ Copyright 2022 The Bricolage Grotesque Project Authors (https://github.com/ateliertriay/bricolage)
2
+ Copyright 2015 The Public Sans Project Authors (https://github.com/uswds/public-sans)
3
+ Copyright 2020 The JetBrains Mono Project Authors (https://github.com/JetBrains/JetBrainsMono)
4
+ Copyright 2020 The Newsreader Project Authors (http://github.com/productiontype/Newsreader)
5
+
6
+ This Font Software is licensed under the SIL Open Font License, Version 1.1.
7
+ This license is copied below, and is also available with a FAQ at:
8
+ https://scripts.sil.org/OFL
9
+
10
+
11
+ -----------------------------------------------------------
12
+ SIL OPEN FONT LICENSE Version 1.1 - 26 February 2007
13
+ -----------------------------------------------------------
14
+
15
+ PREAMBLE
16
+ The goals of the Open Font License (OFL) are to stimulate worldwide
17
+ development of collaborative font projects, to support the font creation
18
+ efforts of academic and linguistic communities, and to provide a free and
19
+ open framework in which fonts may be shared and improved in partnership
20
+ with others.
21
+
22
+ The OFL allows the licensed fonts to be used, studied, modified and
23
+ redistributed freely as long as they are not sold by themselves. The
24
+ fonts, including any derivative works, can be bundled, embedded,
25
+ redistributed and/or sold with any software provided that any reserved
26
+ names are not used by derivative works. The fonts and derivatives,
27
+ however, cannot be released under any other type of license. The
28
+ requirement for fonts to remain under this license does not apply
29
+ to any document created using the fonts or their derivatives.
30
+
31
+ DEFINITIONS
32
+ "Font Software" refers to the set of files released by the Copyright
33
+ Holder(s) under this license and clearly marked as such. This may
34
+ include source files, build scripts and documentation.
35
+
36
+ "Reserved Font Name" refers to any names specified as such after the
37
+ copyright statement(s).
38
+
39
+ "Original Version" refers to the collection of Font Software components as
40
+ distributed by the Copyright Holder(s).
41
+
42
+ "Modified Version" refers to any derivative made by adding to, deleting,
43
+ or substituting -- in part or in whole -- any of the components of the
44
+ Original Version, by changing formats or by porting the Font Software to a
45
+ new environment.
46
+
47
+ "Author" refers to any designer, engineer, programmer, technical
48
+ writer or other person who contributed to the Font Software.
49
+
50
+ PERMISSION & CONDITIONS
51
+ Permission is hereby granted, free of charge, to any person obtaining
52
+ a copy of the Font Software, to use, study, copy, merge, embed, modify,
53
+ redistribute, and sell modified and unmodified copies of the Font
54
+ Software, subject to the following conditions:
55
+
56
+ 1) Neither the Font Software nor any of its individual components,
57
+ in Original or Modified Versions, may be sold by itself.
58
+
59
+ 2) Original or Modified Versions of the Font Software may be bundled,
60
+ redistributed and/or sold with any software, provided that each copy
61
+ contains the above copyright notice and this license. These can be
62
+ included either as stand-alone text files, human-readable headers or
63
+ in the appropriate machine-readable metadata fields within text or
64
+ binary files as long as those fields can be easily viewed by the user.
65
+
66
+ 3) No Modified Version of the Font Software may use the Reserved Font
67
+ Name(s) unless explicit written permission is granted by the corresponding
68
+ Copyright Holder. This restriction only applies to the primary font name as
69
+ presented to the users.
70
+
71
+ 4) The name(s) of the Copyright Holder(s) or the Author(s) of the Font
72
+ Software shall not be used to promote, endorse or advertise any
73
+ Modified Version, except to acknowledge the contribution(s) of the
74
+ Copyright Holder(s) and the Author(s) or with their explicit written
75
+ permission.
76
+
77
+ 5) The Font Software, modified or unmodified, in part or in whole,
78
+ must be distributed entirely under this license, and must not be
79
+ distributed under any other license. The requirement for fonts to
80
+ remain under this license does not apply to any document created
81
+ using the Font Software.
82
+
83
+ TERMINATION
84
+ This license becomes null and void if any of the above conditions are
85
+ not met.
86
+
87
+ DISCLAIMER
88
+ THE FONT SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
89
+ EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO ANY WARRANTIES OF
90
+ MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT
91
+ OF COPYRIGHT, PATENT, TRADEMARK, OR OTHER RIGHT. IN NO EVENT SHALL THE
92
+ COPYRIGHT HOLDER BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
93
+ INCLUDING ANY GENERAL, SPECIAL, INDIRECT, INCIDENTAL, OR CONSEQUENTIAL
94
+ DAMAGES, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
95
+ FROM, OUT OF THE USE OR INABILITY TO USE THE FONT SOFTWARE OR FROM
96
+ OTHER DEALINGS IN THE FONT SOFTWARE.
@@ -0,0 +1,38 @@
1
+ # Catppuccin roles
2
+
3
+ The starter maps the official [Catppuccin palette](https://github.com/catppuccin/palette/blob/main/palette.json) to semantic tokens. Latte serves the light theme and Mocha the dark theme. Blue is the Artifacts accent.
4
+
5
+ | Token | Role | Latte (light) | Mocha (dark) |
6
+ | --- | --- | --- | --- |
7
+ | `--page` | Page background | Base `#eff1f5` | Mantle `#181825` |
8
+ | `--surface` | Strip, table, or panel | White `#ffffff` | Base `#1e1e2e` |
9
+ | `--sunken` | Table header, hover, code | Mantle `#e6e9ef` | Crust `#11111b` |
10
+ | `--line` | Hairline divider | Surface 0 `#ccd0da` | Surface 0 `#313244` |
11
+ | `--control` | Input and button boundary | Overlay 1 `#8c8fa1` | Overlay 0 `#6c7086` |
12
+ | `--fg` | Main text | Text `#4c4f69` | Text `#cdd6f4` |
13
+ | `--muted` | Secondary text and labels | Subtext 1 `#5c5f77` | Subtext 0 `#a6adc8` |
14
+ | `--accent` | Focus, selection, primary fill | Blue `#1e66f5` | Blue `#89b4fa` |
15
+ | `--accent-ink` | Links and accent text | `#1957d0` | Blue `#89b4fa` |
16
+ | `--ok` / `--ok-ink` | Success mark / text | Green `#40a02b` / `#307820` | Green `#a6e3a1` |
17
+ | `--warn` / `--warn-ink` | Warning mark / text | Yellow `#df8e1d` / `#915c13` | Yellow `#f9e2af` |
18
+ | `--bad` / `--bad-ink` | Error mark / text | Red `#d20f39` / `#b20d30` | Red `#f38ba8` |
19
+
20
+ The Latte ink values darken the palette color so pill text reaches 4.5:1 on its soft background. Each `*-soft` token mixes 14 % of its color into `--surface`, so it adapts to both themes without a second definition.
21
+
22
+ Every text token reaches 4.5:1 on `--page` and `--surface` in both themes. `--control` reaches 3:1 for input boundaries. `--line` is decorative and must not be the only boundary of a control.
23
+
24
+ ## Chart series
25
+
26
+ Use these in order after Blue. Give each series a direct label or marker as well as a color.
27
+
28
+ | Series | Latte | Mocha |
29
+ | --- | --- | --- |
30
+ | 1 | Blue `#1e66f5` | Blue `#89b4fa` |
31
+ | 2 | Peach `#fe640b` | Peach `#fab387` |
32
+ | 3 | Teal `#179299` | Teal `#94e2d5` |
33
+ | 4 | Mauve `#8839ef` | Mauve `#cba6f7` |
34
+ | 5 | Yellow `#df8e1d` | Yellow `#f9e2af` |
35
+
36
+ Mauve is a data color only, never the accent.
37
+
38
+ The palette's license notice is retained in [third-party notices](../THIRD-PARTY-NOTICES.md).
@@ -0,0 +1,50 @@
1
+ # Interface checks for HTML artifacts
2
+
3
+ Use these checks for visual authoring and review. Apply only the checks relevant to the artifact. Keep the requested content and behavior central.
4
+
5
+ ## Accessible, resilient interfaces
6
+
7
+ - Use semantic headings, buttons, links, and tables. Label controls and icon buttons. Give meaningful images alternative text.
8
+ - Make every action keyboard accessible. Keep focus visible and unobscured. Restore focus after dismissing overlays.
9
+ - Announce asynchronous status changes without interrupting reading. Pair errors with a useful next action.
10
+ - Provide click and keyboard alternatives for gestures. Keep essential information available without hover.
11
+ - Preserve zoom and paste. Associate input labels with their controls. Use suitable input types.
12
+ - Support narrow viewports, empty results, and long content. Let flex children shrink. Wrap text before hiding overflow.
13
+ - Reserve image dimensions. Defer below-fold media. Batch layout reads and writes. Paginate or virtualize long lists when needed.
14
+ - Use tabular numerals for comparisons and `Intl` for localized values. Keep units attached to numbers.
15
+ - Set native controls to the document's color scheme. Keep hover, active, and focus states distinguishable.
16
+ - Avoid motion that blocks interaction. Provide pause controls for extended autoplay and a reduced-motion alternative.
17
+
18
+ ## Motion and interaction detail
19
+
20
+ Adapt motion to repetition and purpose. Frequent actions should respond immediately. Add animation when it explains a state change or spatial relationship.
21
+
22
+ Prefer interruptible CSS transitions with named properties. Start around 100 to 160 ms for press feedback and 125 to 250 ms for small overlays. Use an ease-out curve for entry. Anchor popover transforms to their trigger and center modal transforms.
23
+
24
+ Prefer `transform` and `opacity` for movement. Avoid `transition: all`, large blur effects, and entrance scaling from zero. Keep feedback subtle enough that labels remain readable.
25
+
26
+ Under `prefers-reduced-motion: reduce`, remove decorative movement and retain immediate state feedback. Gate hover effects with `(hover: hover) and (pointer: fine)`. Do not make content wait for an entrance animation.
27
+
28
+ ## Adapt to the Artifacts sandbox
29
+
30
+ Keep input-driven calculations in document memory. Use labeled controls without a form submission workflow. Do not imply that an in-document control saves a retained revision or publishes an artifact.
31
+
32
+ Keep filters and tabs local to the document. Do not depend on host URL synchronization, router packages, persistent storage, network requests, or remote fonts. Embed fonts and other required assets as `data:` URIs within the snapshot budget.
33
+
34
+ ## Check the result
35
+
36
+ When browser tools are available, inspect the generated document at a narrow viewport around 375 px and a wide viewport around 1280 px, or the user's requested sizes. Verify that body content fits, labels remain legible, and only intentionally wide tables or code regions scroll locally. Check the artifact itself rather than relying on the surrounding application's appearance.
37
+
38
+ Exercise every distinct action with keyboard and pointer input. Observe the expected output or state change, including input errors, reset behavior, and empty filter results where applicable. Confirm that focus stays visible and follows opened and dismissed overlays. Check reduced motion when animation is present. Inspect the console for runtime errors and confirm that required content and controls work without external requests.
39
+
40
+ Finish when each relevant check passes or an unavailable check is identified. Fix observed failures and repeat affected checks. Source inspection establishes markup and script intent; report browser behavior as verified only after observing it.
41
+
42
+ For a requested audit, identify each issue by element or source location and state its correction. Distinguish source inspection from browser verification. Do not turn ordinary artifact delivery into an unsolicited audit report.
43
+
44
+ ## Sources
45
+
46
+ Adapted on 2026-10-14 from [Emil Kowalski's design engineering skill](https://raw.githubusercontent.com/emilkowalski/skills/refs/heads/main/skills/emil-design-eng/SKILL.md) and [Vercel's web design guidelines skill](https://raw.githubusercontent.com/vercel-labs/agent-skills/refs/heads/main/skills/web-design-guidelines/SKILL.md).
47
+
48
+ The accessibility and interface checks incorporate Vercel's Web Interface Guidelines `command.md` as local guidance. Its remote command workflow and output format are not required. The sandbox adaptations are specific to Artifacts.
49
+
50
+ License notices are retained in [Third-party notices](../THIRD-PARTY-NOTICES.md).
@@ -0,0 +1,13 @@
1
+ # Beginner visual explanations
2
+
3
+ When the user requests a visual HTML explanation for a beginner, assume no prior knowledge. Identify the topic from the conversation. Ask for it only if it is missing or ambiguous.
4
+
5
+ Build a visual sequence with one idea per scene. Start with a concrete example the reader can recognize, then show how the parts connect or change. Use large illustrations, diagrams, or pictures to carry the explanation. Decorative icons and oversized headings do not replace explanatory images.
6
+
7
+ Pair each visual with a short heading and a brief caption. Use familiar words and define necessary terms where they first appear. Keep labels close to what they explain. Preserve essential qualifications and the limits of an analogy even when the user asks for few words.
8
+
9
+ Use inline SVG or CSS for diagrams. Use embedded raster images when the subject benefits from illustrations or photographs. Resize and compress them to fit the snapshot budget while keeping details readable. If image tools are unavailable, use a clear diagram when it can convey the concept faithfully. Report any requested imagery you cannot provide.
10
+
11
+ Make the main visual dominant and its labels legible on narrow and wide screens. Give meaningful images text alternatives. Add interaction only when it helps the reader understand a relationship or change. Keep the core explanation accessible without operating a control.
12
+
13
+ Before saving, check that the sequence explains the topic with its pictures and short captions, without a separate essay. Apply the [interface guidelines](interface-checks.md) visual checks and report any checks you could not run.
@@ -0,0 +1,45 @@
1
+ #!/usr/bin/env node
2
+ import { readFile, writeFile } from 'node:fs/promises';
3
+ import { dirname, join } from 'node:path';
4
+ import { fileURLToPath } from 'node:url';
5
+
6
+ const fonts = join(dirname(fileURLToPath(import.meta.url)), '../fonts');
7
+ const latin = 'U+0000-00FF,U+0131,U+0152-0153,U+02BB-02BC,U+02C6,U+02DA,U+02DC,U+0304,U+0308,U+0329,U+2000-206F,U+20AC,U+2122,U+2191,U+2193,U+2212,U+2215,U+FEFF,U+FFFD';
8
+ const faces = [
9
+ { family: 'Bricolage Grotesque', style: 'normal', weight: '500 800', file: 'bricolage-grotesque.woff2' },
10
+ { family: 'Public Sans', style: 'normal', weight: '400 700', file: 'public-sans.woff2' },
11
+ { family: 'Public Sans', style: 'italic', weight: '400 700', file: 'public-sans-italic.woff2' },
12
+ { family: 'JetBrains Mono', style: 'normal', weight: '400 700', file: 'jetbrains-mono.woff2' },
13
+ { family: 'Newsreader', style: 'normal', weight: '400 700', file: 'newsreader.woff2' },
14
+ { family: 'Newsreader', style: 'italic', weight: '400 700', file: 'newsreader-italic.woff2' },
15
+ ];
16
+ const limit = 1024 * 1024;
17
+ const embedded = /<style data-artifacts-fonts>[\s\S]*?<\/style>\n?/;
18
+
19
+ const paths = process.argv.slice(2);
20
+ if (paths.length !== 1) {
21
+ process.stderr.write('Usage: node embed-fonts.mjs PAGE.html\nEmbeds the bundled font faces that PAGE.html names, in place.\n');
22
+ process.exit(2);
23
+ }
24
+ const [path] = paths;
25
+ const page = (await readFile(path, 'utf8')).replace(embedded, '');
26
+ const italic = /font-style\s*:\s*italic|<(em|i|cite)[\s>]/i.test(page);
27
+ const used = faces.filter((face) => page.includes(face.family) && (face.style === 'normal' || italic));
28
+ const rules = await Promise.all(used.map(async (face) => {
29
+ const data = (await readFile(join(fonts, face.file))).toString('base64');
30
+ return `@font-face{font-family:"${face.family}";font-style:${face.style};font-weight:${face.weight};font-display:swap;src:url(data:font/woff2;base64,${data}) format("woff2");unicode-range:${latin}}`;
31
+ }));
32
+ const block = rules.length ? `<style data-artifacts-fonts>${rules.join('')}</style>\n` : '';
33
+
34
+ const anchor = page.search(/<style[\s>]/i);
35
+ const head = /<head[^>]*>\n?/i.exec(page) ?? /<!doctype[^>]*>\n?/i.exec(page);
36
+ const at = anchor >= 0 ? anchor : head ? head.index + head[0].length : 0;
37
+ const output = page.slice(0, at) + block + page.slice(at);
38
+ const bytes = Buffer.byteLength(output);
39
+ if (bytes > limit) {
40
+ process.stderr.write(`Not written. ${path} would be ${bytes} bytes, above the 1 MiB revision limit. Drop a family or the italic faces, or shrink the content.\n`);
41
+ process.exit(1);
42
+ }
43
+ await writeFile(path, output);
44
+ const names = used.map((face) => `${face.family}${face.style === 'italic' ? ' italic' : ''}`);
45
+ process.stdout.write(`${names.length ? `Embedded ${names.join(', ')}` : 'No bundled family named; nothing embedded'}. ${path} is ${bytes} bytes.\n`);
@@ -0,0 +1,170 @@
1
+ <!doctype html>
2
+ <html lang="es">
3
+ <head>
4
+ <meta charset="utf-8">
5
+ <meta name="viewport" content="width=device-width,initial-scale=1">
6
+ <title>Artifact starter</title>
7
+ <style>
8
+ /* Layout: one centered column; summary before detail, then the dominant table, chart, or diagram. */
9
+ :root{
10
+ --page:#eff1f5; --surface:#ffffff; --sunken:#e6e9ef; --line:#ccd0da; --control:#8c8fa1;
11
+ --fg:#4c4f69; --muted:#5c5f77; --accent:#1e66f5; --on-accent:#ffffff; --accent-ink:#1957d0;
12
+ --ok:#40a02b; --ok-ink:#307820; --warn:#df8e1d; --warn-ink:#915c13; --bad:#d20f39; --bad-ink:#b20d30;
13
+ --neutral-soft:color-mix(in srgb,var(--fg) 9%,var(--surface));
14
+ --accent-soft:color-mix(in srgb,var(--accent) 14%,var(--surface));
15
+ --ok-soft:color-mix(in srgb,var(--ok) 14%,var(--surface));
16
+ --warn-soft:color-mix(in srgb,var(--warn) 14%,var(--surface));
17
+ --bad-soft:color-mix(in srgb,var(--bad) 14%,var(--surface));
18
+ --f-display:"Bricolage Grotesque","Avenir Next",system-ui,sans-serif;
19
+ --f-body:"Public Sans","Segoe UI",system-ui,sans-serif;
20
+ --f-mono:"JetBrains Mono",ui-monospace,"SF Mono",Menlo,Consolas,monospace;
21
+ --radius:10px; --width:1120px;
22
+ color-scheme:light;
23
+ }
24
+ @media (prefers-color-scheme:dark){:root:not([data-theme="light"]){
25
+ --page:#181825; --surface:#1e1e2e; --sunken:#11111b; --line:#313244; --control:#6c7086;
26
+ --fg:#cdd6f4; --muted:#a6adc8; --accent:#89b4fa; --on-accent:#11111b; --accent-ink:#89b4fa;
27
+ --ok:#a6e3a1; --ok-ink:#a6e3a1; --warn:#f9e2af; --warn-ink:#f9e2af; --bad:#f38ba8; --bad-ink:#f38ba8;
28
+ color-scheme:dark}}
29
+ :root[data-theme="dark"]{
30
+ --page:#181825; --surface:#1e1e2e; --sunken:#11111b; --line:#313244; --control:#6c7086;
31
+ --fg:#cdd6f4; --muted:#a6adc8; --accent:#89b4fa; --on-accent:#11111b; --accent-ink:#89b4fa;
32
+ --ok:#a6e3a1; --ok-ink:#a6e3a1; --warn:#f9e2af; --warn-ink:#f9e2af; --bad:#f38ba8; --bad-ink:#f38ba8;
33
+ color-scheme:dark}
34
+
35
+ *{box-sizing:border-box}
36
+ html{background:var(--page)}
37
+ body{margin:0;background:var(--page);color:var(--fg);font:15px/1.55 var(--f-body);-webkit-font-smoothing:antialiased}
38
+ .wrap{max-width:var(--width);margin:0 auto;padding-left:max(16px,env(safe-area-inset-left,0px));padding-right:max(16px,env(safe-area-inset-right,0px));padding-block:32px 64px;display:grid;gap:24px}
39
+ .wrap>*{min-width:0}
40
+ h1,h2,h3{font-family:var(--f-display);margin:0;text-wrap:balance;letter-spacing:-.01em}
41
+ h1{font-size:clamp(28px,4vw,40px);line-height:1.05;font-weight:700}
42
+ h2{font-size:clamp(20px,2.4vw,24px);line-height:1.2;font-weight:700}
43
+ h3{font-size:17px;line-height:1.3;font-weight:600}
44
+ p{margin:0}
45
+ a{color:var(--accent-ink);text-underline-offset:3px}
46
+ code,kbd,pre{font-family:var(--f-mono);font-size:.86em}
47
+ pre{margin:0;padding:14px 16px;background:var(--sunken);border-radius:8px;overflow-x:auto;line-height:1.5}
48
+ :focus-visible{outline:2px solid var(--accent);outline-offset:2px}
49
+
50
+ .eyebrow,.label{font:600 11.5px/1.2 var(--f-mono);letter-spacing:.06em;text-transform:uppercase;color:var(--muted)}
51
+ header{display:grid;gap:8px}
52
+ .lede{color:var(--muted);max-width:68ch;font-size:16px}
53
+ .prose{display:grid;gap:12px;max-width:68ch}
54
+ section{display:grid;gap:14px}
55
+
56
+ .strip{display:flex;flex-wrap:wrap;gap:1px;background:var(--line);border:1px solid var(--line);border-radius:var(--radius);overflow:hidden}
57
+ .stat{flex:1 1 150px;display:grid;gap:2px;padding:14px 16px;background:var(--surface)}
58
+ .stat b{font:700 26px/1.1 var(--f-display);font-variant-numeric:tabular-nums}
59
+ .stat span{font-size:13px;color:var(--muted)}
60
+
61
+ .pill{display:inline-flex;align-items:center;gap:6px;font:600 11.5px/1.25 var(--f-body);padding:3px 8px;border-radius:5px;background:var(--neutral-soft);color:var(--muted)}
62
+ .pills{display:flex;flex-wrap:wrap;gap:5px}
63
+ .pill.accent{background:var(--accent-soft);color:var(--accent-ink)}
64
+ .pill.ok{background:var(--ok-soft);color:var(--ok-ink)}
65
+ .pill.warn{background:var(--warn-soft);color:var(--warn-ink)}
66
+ .pill.bad{background:var(--bad-soft);color:var(--bad-ink)}
67
+
68
+ .panel{background:var(--surface);border:1px solid var(--line);border-radius:var(--radius);padding:16px 18px;display:grid;gap:10px}
69
+ .note{border-left:3px solid var(--accent);padding:2px 0 2px 14px;display:grid;gap:4px;max-width:72ch}
70
+ .note.warn{border-color:var(--warn)}
71
+
72
+ .table{background:var(--surface);border:1px solid var(--line);border-radius:var(--radius);overflow-x:auto}
73
+ table{width:100%;border-collapse:collapse;font-size:14px}
74
+ th{font:600 11px/1.2 var(--f-mono);letter-spacing:.06em;text-transform:uppercase;color:var(--muted);text-align:left;padding:10px 14px;background:var(--sunken);border-bottom:1px solid var(--line);white-space:nowrap}
75
+ td{padding:11px 14px;border-bottom:1px solid var(--line);vertical-align:top;font-variant-numeric:tabular-nums}
76
+ tr:last-child td{border-bottom:0}
77
+ td.num,th.num{text-align:right;white-space:nowrap}
78
+
79
+ .rows{background:var(--surface);border:1px solid var(--line);border-radius:var(--radius);overflow:hidden}
80
+ .rows-head,.row>summary{display:grid;grid-template-columns:var(--cols,88px minmax(0,2fr) minmax(0,1fr) minmax(0,1.4fr));gap:16px;padding:12px 16px;align-items:start}
81
+ .rows-head{font:600 11px/1.2 var(--f-mono);letter-spacing:.06em;text-transform:uppercase;color:var(--muted);background:var(--sunken);border-bottom:1px solid var(--line)}
82
+ .row{border-bottom:1px solid var(--line)}
83
+ .row:last-child{border-bottom:0}
84
+ .row>summary{cursor:pointer;list-style:none}
85
+ .row>summary::-webkit-details-marker{display:none}
86
+ .row>summary>*{min-width:0}
87
+ .row>summary>:first-child::before{content:"▸";display:inline-block;margin-right:6px;color:var(--muted);font-size:11px;transition:transform .15s}
88
+ .row[open]>summary>:first-child::before{transform:rotate(90deg)}
89
+ .row-detail{padding:4px 16px 18px;display:grid;grid-template-columns:repeat(auto-fit,minmax(min(260px,100%),1fr));gap:14px 28px}
90
+ .row-detail dl{margin:0;display:grid;gap:10px;align-content:start}
91
+ .row-detail dd{margin:2px 0 0;font-size:13.5px;overflow-wrap:anywhere}
92
+ .row.closed>summary{color:var(--muted)}
93
+
94
+ .bar{display:flex;flex-wrap:wrap;gap:10px;align-items:center}
95
+ input,select,button{font:inherit;font-size:14px;color:var(--fg);background:var(--surface);border:1px solid var(--control);border-radius:8px;padding:8px 10px;min-width:0}
96
+ button{cursor:pointer}
97
+ button.primary{background:var(--accent);border-color:var(--accent);color:var(--on-accent);font-weight:600}
98
+ .chip{border:1px solid var(--line);border-radius:999px;padding:5px 11px;font-size:13px;font-weight:500}
99
+ .chip[aria-pressed="true"]{background:var(--accent-soft);border-color:var(--accent);color:var(--accent-ink)}
100
+ .count{margin-left:auto;font:13px var(--f-mono);color:var(--muted)}
101
+
102
+ footer{font-size:12.5px;color:var(--muted);display:grid;gap:4px;border-top:1px solid var(--line);padding-top:16px}
103
+
104
+ @media (max-width:720px){.rows-head{display:none}.row>summary{grid-template-columns:minmax(0,1fr);gap:6px}}
105
+ @media (hover:hover) and (pointer:fine){tbody tr:hover td,.row>summary:hover{background:var(--sunken)}button:hover{border-color:var(--accent)}}
106
+ @media (prefers-reduced-motion:reduce){*{transition:none!important;animation:none!important}}
107
+ </style>
108
+ </head>
109
+ <body>
110
+ <main class="wrap">
111
+ <header>
112
+ <div class="eyebrow">Specimen · artifacts-design</div>
113
+ <h1>Starter primitives</h1>
114
+ <p class="lede">Replace this body with the artifact's own composition. Keep the tokens and the primitives the page needs, and delete the rest.</p>
115
+ </header>
116
+
117
+ <section class="strip" aria-label="Summary">
118
+ <div class="stat"><b>34</b><span>Records</span></div>
119
+ <div class="stat"><b>23</b><span>Open</span></div>
120
+ <div class="stat"><b>4</b><span>Need attention</span></div>
121
+ </section>
122
+
123
+ <section>
124
+ <h2>Status and data</h2>
125
+ <div class="bar">
126
+ <input id="q" type="search" placeholder="Search" aria-label="Search">
127
+ <button class="chip" aria-pressed="true">All</button>
128
+ <button class="chip" aria-pressed="false">Open</button>
129
+ <span class="count">3 of 34</span>
130
+ </div>
131
+ <div class="table">
132
+ <table>
133
+ <thead><tr><th>Item</th><th>State</th><th class="num">Value</th></tr></thead>
134
+ <tbody>
135
+ <tr><td>Accent</td><td><span class="pill accent">In review</span></td><td class="num">1,204</td></tr>
136
+ <tr><td>Success</td><td><span class="pill ok">Passed</span></td><td class="num">98.6 %</td></tr>
137
+ <tr><td>Warning and error</td><td><span class="pill warn">Pending</span> <span class="pill bad">Failed</span></td><td class="num">−12</td></tr>
138
+ </tbody>
139
+ </table>
140
+ </div>
141
+ </section>
142
+
143
+ <section>
144
+ <h2>Expandable records</h2>
145
+ <div class="rows">
146
+ <div class="rows-head" aria-hidden="true"><span>ID</span><span>Title</span><span>Group</span><span>State</span></div>
147
+ <details class="row">
148
+ <summary><b>#2088</b><span>Parallelize search and listing data fetching</span><span>Performance</span><span class="pills"><span class="pill">Draft</span><span class="pill ok">Build passed</span></span></summary>
149
+ <div class="row-detail">
150
+ <dl><dt class="label">Next step</dt><dd>Keep separate and wait for team review.</dd></dl>
151
+ <dl><dt class="label">Branch</dt><dd><code>refactor/search-plp-parallel-fetch</code></dd></dl>
152
+ </div>
153
+ </details>
154
+ <details class="row closed">
155
+ <summary><b>#2086</b><span>Temporary workspace configuration</span><span>Temporary</span><span class="pills"><span class="pill">Closed</span></span></summary>
156
+ <div class="row-detail"><dl><dt class="label">Reason</dt><dd>Closed without merge by the owner.</dd></dl></div>
157
+ </details>
158
+ </div>
159
+ </section>
160
+
161
+ <section class="prose">
162
+ <h2>Reading text</h2>
163
+ <p>Body copy stays near 68 characters per line. Use <code>code</code> for identifiers and the <span class="label">label</span> style for metadata.</p>
164
+ <div class="note"><b>Note.</b><p>A note marks one consequential statement. It is not a container for ordinary paragraphs.</p></div>
165
+ </section>
166
+
167
+ <footer><span>Source and date of the data go here.</span></footer>
168
+ </main>
169
+ </body>
170
+ </html>
package/src/skills.js CHANGED
@@ -67,12 +67,27 @@ async function fullFiles(directory, prefix = '') {
67
67
  }
68
68
  return files;
69
69
  }
70
+ function isText(content) {
71
+ if (content.includes(0))
72
+ return false;
73
+ try {
74
+ new TextDecoder('utf-8', { fatal: true }).decode(content);
75
+ return true;
76
+ }
77
+ catch {
78
+ return false;
79
+ }
80
+ }
70
81
  async function labeledContent(skills, full) {
71
82
  const chunks = [];
72
83
  for (const skill of skills) {
73
84
  const files = full ? ['SKILL.md', ...(await fullFiles(skill.directory)).filter((path) => path !== 'SKILL.md')] : ['SKILL.md'];
74
85
  for (const path of files) {
75
86
  const content = path === 'SKILL.md' ? skill.skillText : await readFile(join(skill.directory, path));
87
+ if (!isText(content)) {
88
+ chunks.push(Buffer.from(`=== ${skill.name}/${path} (binary, ${content.length} bytes) ===\n`));
89
+ continue;
90
+ }
76
91
  chunks.push(Buffer.from(`=== ${skill.name}/${path} ===\n`), content);
77
92
  if (content.at(-1) !== 10)
78
93
  chunks.push(Buffer.from('\n'));