create-forte-ui 1.0.0-alpha.5 → 1.0.0-alpha.7

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
@@ -10,7 +10,7 @@ npm create forte-ui@latest my-app
10
10
 
11
11
  Four questions get you to a running app (name, framework, Tailwind, accent
12
12
  color); everything else — secondary color, neutral tint, radius, density,
13
- motion, fonts — hides behind one "customize further?" gate. Every prompt has a
13
+ motion, light/dark, fonts — hides behind one "customize further?" gate. Every prompt has a
14
14
  flag twin, so the [Theme Studio](https://forte-ui.com/theme/) can hand you a
15
15
  complete command line and `--yes` runs without a single question:
16
16
 
package/dist/args.d.ts CHANGED
@@ -27,4 +27,4 @@ export type CliOptions = {
27
27
  export declare class UsageError extends Error {
28
28
  }
29
29
  export declare function parseCliArgs(argv: string[]): CliOptions;
30
- export declare const HELP = "create-forte-ui \u2014 scaffold a new app wired up with forte-ui\n\nUsage\n pnpm create forte-ui [name] [flags]\n npm create forte-ui@latest [name] -- [flags]\n\nEvery flag has a prompt twin; a passed flag suppresses its prompt. With no\nflags you get the questionnaire, with --yes you get a Next.js + Tailwind app\non the library's default theme.\n\nProject\n [name] project directory (prompted if omitted)\n -f, --framework next | vite\n --tailwind wire the Tailwind v4 bridge (default yes)\n --no-tailwind plain CSS setup\n --pm npm | pnpm | yarn | bun (default: whoever invoked us)\n --library version spec for @forte-ui/react \u2014 exact\n (\"1.0.0-alpha.4\"), a dist-tag (\"alpha\"), or a range.\n Default: latest.\n --no-install write files only; skip installing dependencies\n and the agent skill\n --no-skill skip installing the forte-ui agent skill\n (skills.sh \u2014 .agents/skills plus .claude/skills)\n -y, --yes accept the defaults for everything not passed\n\nTheme \u2014 every skipped value keeps the library default and writes NOTHING,\nso the app keeps following the library when defaults are tuned.\n --seed, --accent accent seed, hex (\"#6d43d4\")\n --secondary secondary seed, hex\n --tint neutral tint, 0 (pure grey) to 1 (default)\n --radius none | soft | pill\n --density compact | spacious\n --motion system (default) | reduce | full\n \"full\" overrides the OS reduced-motion preference\n for everyone \u2014 prefer leaving it unset.\n --font-sans a catalogue name (\"Inter\", \"DM Sans\", ...)\n --font-mono a catalogue name (\"JetBrains Mono\", ...)\n\nDesign the theme visually instead: https://forte-ui.com/theme\n";
30
+ export declare const HELP = "create-forte-ui \u2014 scaffold a new app wired up with forte-ui\n\nUsage\n pnpm create forte-ui [name] [flags]\n npm create forte-ui@latest [name] -- [flags]\n\nEvery flag has a prompt twin; a passed flag suppresses its prompt. With no\nflags you get the questionnaire, with --yes you get a Next.js + Tailwind app\non the library's default theme.\n\nProject\n [name] project directory (prompted if omitted)\n -f, --framework next | vite\n --tailwind wire the Tailwind v4 bridge (default yes)\n --no-tailwind plain CSS setup\n --pm npm | pnpm | yarn | bun (default: whoever invoked us)\n --library version spec for @forte-ui/react \u2014 exact\n (\"1.0.0-alpha.4\"), a dist-tag (\"alpha\"), or a range.\n Default: latest.\n --no-install write files only; skip installing dependencies\n and the agent skill\n --no-skill skip installing the forte-ui agent skill\n (skills.sh \u2014 .agents/skills plus .claude/skills)\n -y, --yes accept the defaults for everything not passed\n\nTheme \u2014 every skipped value keeps the library default and writes NOTHING,\nso the app keeps following the library when defaults are tuned.\n --seed, --accent accent seed, hex (\"#6d43d4\")\n --secondary secondary seed, hex\n --tint neutral tint, 0 (pure grey) to 1 (default)\n --radius none | soft | pill\n --density compact | spacious\n --motion system (default) | reduce | full\n \"full\" overrides the OS reduced-motion preference\n for everyone \u2014 prefer leaving it unset.\n --scheme system (default) | light | dark\n \"light\" / \"dark\" pin one palette on <html> and\n leave the theme toggle and its replay script out.\n --font-sans a catalogue name (\"Inter\", \"DM Sans\", ...)\n --font-mono a catalogue name (\"JetBrains Mono\", ...)\n\nDesign the theme visually instead: https://forte-ui.com/theme\n";
package/dist/args.js CHANGED
@@ -11,7 +11,7 @@
11
11
  import { parseArgs } from "node:util";
12
12
  import { SANS_FONTS, MONO_FONTS } from "./fonts.js";
13
13
  import { hexToOklch } from "./color.js";
14
- import { RADIUS, DENSITY, MOTION } from "./theme.js";
14
+ import { RADIUS, DENSITY, MOTION, SCHEME } from "./theme.js";
15
15
  import { PACKAGE_MANAGERS } from "./scaffold.js";
16
16
  export class UsageError extends Error {
17
17
  }
@@ -51,6 +51,7 @@ export function parseCliArgs(argv) {
51
51
  radius: { type: "string" },
52
52
  density: { type: "string" },
53
53
  motion: { type: "string" },
54
+ scheme: { type: "string" },
54
55
  "font-sans": { type: "string" },
55
56
  "font-mono": { type: "string" },
56
57
  library: { type: "string" },
@@ -94,6 +95,8 @@ export function parseCliArgs(argv) {
94
95
  answers.motion =
95
96
  values.motion.toLowerCase() === "system" ? "default" : oneOf("motion", values.motion, MOTION);
96
97
  }
98
+ if (values.scheme !== undefined)
99
+ answers.scheme = oneOf("scheme", values.scheme, SCHEME);
97
100
  if (values["font-sans"] !== undefined) {
98
101
  answers.fontSans = fontByName("font-sans", values["font-sans"], SANS_FONTS);
99
102
  }
@@ -149,7 +152,10 @@ so the app keeps following the library when defaults are tuned.
149
152
  --motion system (default) | reduce | full
150
153
  "full" overrides the OS reduced-motion preference
151
154
  for everyone — prefer leaving it unset.
152
- --font-sans a catalogue name ("Inter", "DM Sans", ...)
155
+ --scheme system (default) | light | dark
156
+ "light" / "dark" pin one palette on <html> and
157
+ leave the theme toggle and its replay script out.
158
+ --font-sans a catalogue name ("Inter", "DM Sans", ...)
153
159
  --font-mono a catalogue name ("JetBrains Mono", ...)
154
160
 
155
161
  Design the theme visually instead: https://forte-ui.com/theme
package/dist/index.js CHANGED
@@ -41,7 +41,19 @@ function warnAboutSeeds(plan) {
41
41
  }
42
42
  function scaffold(plan, pm) {
43
43
  if (plan.framework === "vite") {
44
- return runScaffolder(pm, "create-vite@latest", [plan.name, "--template", "react-ts"], process.cwd());
44
+ return runScaffolder(pm, "create-vite@latest", [
45
+ plan.name,
46
+ "--template",
47
+ "react-ts",
48
+ /* create-vite 9 goes interactive whenever stdin is a TTY, template
49
+ * or not: it asks which linter to use and then "Install and start
50
+ * now?". A yes to that starts the dev server INSIDE our child
51
+ * process, so control never comes back and the overlay is never
52
+ * applied — the user ends up with a stock Vite app and no forte-ui.
53
+ * Non-interactive takes its defaults (Oxlint, no install), and the
54
+ * one install happens at the end with the library included. */
55
+ "--no-interactive",
56
+ ], process.cwd());
45
57
  }
46
58
  return runScaffolder(pm, "create-next-app@latest", [
47
59
  plan.name,
package/dist/overlay.js CHANGED
@@ -24,7 +24,7 @@ function applyVite({ name, dir, tailwind, answers }) {
24
24
  written.push("src/index.css");
25
25
  write(dir, "src/main.tsx", viteMainTsx(tailwind));
26
26
  written.push("src/main.tsx");
27
- write(dir, "src/App.tsx", viteAppTsx(name, tailwind));
27
+ write(dir, "src/App.tsx", viteAppTsx(name, tailwind, answers.scheme === "system"));
28
28
  written.push("src/App.tsx");
29
29
  /* The guide's warning covers App.css too: its rules are unlayered and the
30
30
  * new App.tsx no longer imports it. Delete rather than empty, so nobody
@@ -47,6 +47,28 @@ function applyVite({ name, dir, tailwind, answers }) {
47
47
  `the create-vite template may have changed; add the attribute(s) by hand.`);
48
48
  }
49
49
  html = anchored.replace(/<title>[^<]*<\/title>/, `<title>${name}</title>`);
50
+ /* Replay a stored light/dark choice before first paint — the guides'
51
+ * "Light and dark" step. A bundled component cannot run before the bundle,
52
+ * so on Vite the script lives in the document itself; the string must stay
53
+ * byte-identical to `themeInitScript` in @forte-ui/react, which is the
54
+ * other writer of the same localStorage key.
55
+ *
56
+ * Not when a scheme is pinned: `data-theme` is then static in the markup
57
+ * above, and the script would OVERRIDE it with any `forte-theme` record on
58
+ * the origin — which on localhost is shared by every project, so a toggle
59
+ * pressed in another app would flip this one's pinned palette. */
60
+ if (answers.scheme === "system") {
61
+ const themeReplay = " <!-- Replays a stored light/dark choice before first paint\n" +
62
+ " (themeInitScript from @forte-ui/react). -->\n" +
63
+ ' <script>(function(){try{var t=localStorage.getItem("forte-theme");if(t==="light"||t==="dark")document.documentElement.setAttribute("data-theme",t)}catch(e){}})();</script>\n';
64
+ const replayed = html.replace(/[ \t]*<\/head>/, `${themeReplay} </head>`);
65
+ if (replayed === html) {
66
+ throw new Error("could not find '</head>' in index.html to add the theme replay script — " +
67
+ "the create-vite template may have changed; copy the snippet from the " +
68
+ "guide's 'Light and dark' step into <head> by hand.");
69
+ }
70
+ html = replayed;
71
+ }
50
72
  /* Tailwind only: pin the cascade-layer order in the DOCUMENT, not the
51
73
  * stylesheet. The bridge's own `@layer theme, base, forte, components,
52
74
  * utilities;` statement is supposed to do this, but Vite's CSS pipeline
package/dist/prompts.js CHANGED
@@ -95,12 +95,12 @@ export async function collectPlan(opts) {
95
95
  if (!opts.yes && !flagged.has("seed")) {
96
96
  answers.seed = await hexPrompt("Accent color — your brand's hex, the whole palette derives from it")();
97
97
  }
98
- const advancedKeys = ["secondary", "tint", "radius", "density", "motion", "fontSans", "fontMono"];
98
+ const advancedKeys = ["secondary", "tint", "radius", "density", "motion", "scheme", "fontSans", "fontMono"];
99
99
  const remaining = advancedKeys.filter((k) => !flagged.has(k));
100
100
  const customize = !opts.yes &&
101
101
  remaining.length > 0 &&
102
102
  accept(await p.confirm({
103
- message: "Customize further? (secondary, neutrals, radius, density, motion, fonts)",
103
+ message: "Customize further? (secondary, neutrals, radius, density, motion, light/dark, fonts)",
104
104
  initialValue: false,
105
105
  }));
106
106
  if (customize) {
@@ -155,6 +155,17 @@ export async function collectPlan(opts) {
155
155
  ],
156
156
  }));
157
157
  }
158
+ if (remaining.includes("scheme")) {
159
+ answers.scheme = accept(await p.select({
160
+ message: "Light and dark",
161
+ initialValue: "system",
162
+ options: [
163
+ { value: "system", label: "Both", hint: "follows the OS, with a toggle" },
164
+ { value: "light", label: "Light only" },
165
+ { value: "dark", label: "Dark only" },
166
+ ],
167
+ }));
168
+ }
158
169
  if (remaining.includes("fontSans")) {
159
170
  answers.fontSans = await fontPrompt("Sans font", SANS_FONTS);
160
171
  }
@@ -1,7 +1,11 @@
1
1
  import { type ThemeAnswers } from "./theme.js";
2
2
  export declare function viteIndexCss(a: ThemeAnswers, tailwind: boolean): string;
3
3
  export declare function viteMainTsx(tailwind: boolean): string;
4
- export declare function viteAppTsx(name: string, tailwind: boolean): string;
4
+ /** `toggle` is false when the answers pinned a colour scheme: `data-theme`
5
+ * then sits statically on `<html>` and a toggle would be a button whose one
6
+ * job is to fight it. The import goes with the element, so the starter
7
+ * still compiles clean under `noUnusedLocals`. */
8
+ export declare function viteAppTsx(name: string, tailwind: boolean, toggle: boolean): string;
5
9
  export declare const VITE_CONFIG_TW = "import { defineConfig } from \"vite\";\nimport react from \"@vitejs/plugin-react\";\nimport tailwindcss from \"@tailwindcss/vite\";\n\nexport default defineConfig({\n plugins: [react(), tailwindcss()],\n});\n";
6
10
  export declare function nextGlobalsCss(a: ThemeAnswers, tailwind: boolean): string;
7
11
  export declare function nextLayoutTsx(name: string, a: ThemeAnswers, tailwind: boolean): string;
package/dist/templates.js CHANGED
@@ -18,6 +18,28 @@ const BODY_RULE = `body {
18
18
  function joinBlocks(...blocks) {
19
19
  return blocks.filter(Boolean).join("\n") + "\n";
20
20
  }
21
+ /* The starter's one piece of chrome, and the working half of the guides'
22
+ * "Light and dark" step: the theme the questionnaire just seeded can be
23
+ * flipped without writing a line. Two spellings of the same corner, because
24
+ * the Tailwind paths have utilities and the plain ones do not.
25
+ *
26
+ * `fixed` rather than in flow, so the page still centres the component it is
27
+ * there to demonstrate — and the offsets are space tokens rather than `1rem`,
28
+ * because this file is the first thing a new app is copied out of and a
29
+ * hardcoded value here is the habit it teaches. */
30
+ const TOGGLE_TW = `<ThemeToggle className="fixed top-4 right-4" />`;
31
+ const TOGGLE_STYLE = `<ThemeToggle
32
+ style={{ position: "fixed", top: "var(--forte-space-4)", right: "var(--forte-space-4)" }}
33
+ />`;
34
+ /** `TOGGLE_STYLE` re-indented to sit at `indent` spaces (its first line is
35
+ * placed by the caller, the continuation lines by this). */
36
+ function indented(block, indent) {
37
+ const pad = " ".repeat(indent);
38
+ return block
39
+ .split("\n")
40
+ .map((line, i) => (i === 0 ? line : pad + line))
41
+ .join("\n");
42
+ }
21
43
  /* -------------------------------------------------------------------------
22
44
  * Vite
23
45
  * ---------------------------------------------------------------------- */
@@ -56,14 +78,19 @@ createRoot(document.getElementById("root")!).render(
56
78
  );
57
79
  `;
58
80
  }
59
- export function viteAppTsx(name, tailwind) {
81
+ /** `toggle` is false when the answers pinned a colour scheme: `data-theme`
82
+ * then sits statically on `<html>` and a toggle would be a button whose one
83
+ * job is to fight it. The import goes with the element, so the starter
84
+ * still compiles clean under `noUnusedLocals`. */
85
+ export function viteAppTsx(name, tailwind, toggle) {
60
86
  if (tailwind) {
61
- return `import { Button, Card } from "@forte-ui/react";
87
+ const toggleLine = toggle ? ` ${TOGGLE_TW}\n` : "";
88
+ return `import { Button, Card${toggle ? ", ThemeToggle" : ""} } from "@forte-ui/react";
62
89
 
63
90
  export default function App() {
64
91
  return (
65
92
  <main className="grid min-h-dvh place-items-center bg-background text-foreground">
66
- <Card.Root variant="elevated" className="items-start gap-5">
93
+ ${toggleLine} <Card.Root variant="elevated" className="items-start gap-5">
67
94
  <h1 className="text-5 font-semibold">${name}</h1>
68
95
  <p className="text-2 text-foreground-muted">
69
96
  Utilities and components, one theme.
@@ -75,12 +102,13 @@ export default function App() {
75
102
  }
76
103
  `;
77
104
  }
78
- return `import { Button } from "@forte-ui/react";
105
+ const toggleLine = toggle ? ` ${indented(TOGGLE_STYLE, 6)}\n` : "";
106
+ return `import { Button${toggle ? ", ThemeToggle" : ""} } from "@forte-ui/react";
79
107
 
80
108
  export default function App() {
81
109
  return (
82
110
  <main style={{ display: "grid", placeItems: "center", minHeight: "100dvh" }}>
83
- <Button>It works</Button>
111
+ ${toggleLine} <Button>It works</Button>
84
112
  </main>
85
113
  );
86
114
  }
@@ -113,8 +141,28 @@ export function nextLayoutTsx(name, a, tailwind) {
113
141
  * globals.css imports it after the bridge (see the guide's ordering note),
114
142
  * and a second import here would pin the `forte` layer before `base`. */
115
143
  const themeImport = tailwind ? "" : `import "@forte-ui/react/theme.css";\n`;
144
+ /* ThemeScript + suppressHydrationWarning are the guides' "Light and dark"
145
+ * step: the script replays a stored light/dark choice onto <html> before
146
+ * first paint, and the suppression covers the attribute it legitimately
147
+ * adds. Harmless with nothing stored — the page just follows the OS. */
148
+ /* The toggle that writes what the script replays. It goes in the LAYOUT,
149
+ * not the page, because it is chrome: it then survives every route the app
150
+ * grows, and the page stays the one file a reader edits first. Being a flat
151
+ * export it needs no `"use client"` here — the boundary the compound
152
+ * components force on `page.tsx` does not reach the layout. */
153
+ /* A pinned scheme drops all three. The attribute is static, so there is
154
+ * nothing for the script to replay and nothing for the suppression to
155
+ * cover — and the script is actively wrong here, not merely idle: it
156
+ * replays whatever `forte-theme` record is on the origin, and on
157
+ * localhost every project shares one origin, so a toggle pressed in some
158
+ * OTHER app would flip this one's pinned palette on load. */
159
+ const pinned = a.scheme !== "system";
160
+ const toggle = tailwind ? TOGGLE_TW : indented(TOGGLE_STYLE, 8);
161
+ const libImport = pinned ? "" : `import { ThemeScript, ThemeToggle } from "@forte-ui/react";\n`;
162
+ const head = pinned ? "" : ` <head>\n <ThemeScript />\n </head>\n`;
163
+ const toggleLine = pinned ? "" : ` ${toggle}\n`;
116
164
  return `import type { Metadata } from "next";
117
- ${font.importLine ? font.importLine + "\n" : ""}${themeImport}import "./globals.css";
165
+ ${libImport}${font.importLine ? font.importLine + "\n" : ""}${themeImport}import "./globals.css";
118
166
 
119
167
  ${font.consts ? font.consts + "\n\n" : ""}export const metadata: Metadata = {
120
168
  title: "${name}",
@@ -127,8 +175,10 @@ export default function RootLayout({
127
175
  children: React.ReactNode;
128
176
  }>) {
129
177
  return (
130
- <html lang="en"${htmlAttrs(a)}${font.htmlClassAttr}>
131
- <body>{children}</body>
178
+ <html lang="en"${htmlAttrs(a)}${font.htmlClassAttr}${pinned ? "" : " suppressHydrationWarning"}>
179
+ ${head} <body>
180
+ ${toggleLine} {children}
181
+ </body>
132
182
  </html>
133
183
  );
134
184
  }
package/dist/theme.d.ts CHANGED
@@ -1,9 +1,11 @@
1
1
  export declare const RADIUS: readonly ["none", "default", "soft", "pill"];
2
2
  export declare const DENSITY: readonly ["compact", "default", "spacious"];
3
3
  export declare const MOTION: readonly ["full", "default", "reduce"];
4
+ export declare const SCHEME: readonly ["system", "light", "dark"];
4
5
  export type Radius = (typeof RADIUS)[number];
5
6
  export type Density = (typeof DENSITY)[number];
6
7
  export type Motion = (typeof MOTION)[number];
8
+ export type Scheme = (typeof SCHEME)[number];
7
9
  export type ThemeAnswers = {
8
10
  /** Accent seed hex, or null to keep the library default (write nothing). */
9
11
  seed: string | null;
@@ -13,6 +15,9 @@ export type ThemeAnswers = {
13
15
  radius: Radius;
14
16
  density: Density;
15
17
  motion: Motion;
18
+ /** "system" follows the OS and scaffolds the toggle; "light" / "dark" pin
19
+ * one palette on `<html>` and leave the toggle and its replay script out. */
20
+ scheme: Scheme;
16
21
  /** Catalogue names. "System" writes nothing. */
17
22
  fontSans: string;
18
23
  fontMono: string;
@@ -33,7 +38,7 @@ export declare function rootBlock(a: ThemeAnswers, fontMode: FontMode): string;
33
38
  export declare function fontImports(a: ThemeAnswers): string[];
34
39
  /** ` data-forte-radius="pill" ...` — leading space included, "" when all
35
40
  * defaults. Default modes stay UNSET so the app keeps following the OS
36
- * (motion) and the library's own defaults. */
41
+ * (motion, colour scheme) and the library's own defaults. */
37
42
  export declare function htmlAttrs(a: ThemeAnswers): string;
38
43
  export type NextFontSetup = {
39
44
  /** `import { Inter, JetBrains_Mono } from "next/font/google";` or "". */
package/dist/theme.js CHANGED
@@ -14,6 +14,13 @@ import { hexToOklch, bestOnColor } from "./color.js";
14
14
  export const RADIUS = ["none", "default", "soft", "pill"];
15
15
  export const DENSITY = ["compact", "default", "spacious"];
16
16
  export const MOTION = ["full", "default", "reduce"];
17
+ /* "system" is spelled out rather than being the "default" the other presets
18
+ * use, because it is also the flag value and the studio's strip label: the
19
+ * one mode that is not a mode. Pinning one writes `data-theme` on `<html>`,
20
+ * and that ATTRIBUTE is the whole mechanism — the stylesheet resolves every
21
+ * `light-dark()` through the `color-scheme` it sets, so a pinned app ships
22
+ * both palettes and simply never shows the other one. */
23
+ export const SCHEME = ["system", "light", "dark"];
17
24
  export const DEFAULT_ANSWERS = {
18
25
  seed: null,
19
26
  secondary: null,
@@ -21,6 +28,7 @@ export const DEFAULT_ANSWERS = {
21
28
  radius: "default",
22
29
  density: "default",
23
30
  motion: "default",
31
+ scheme: "system",
24
32
  fontSans: "System",
25
33
  fontMono: "System",
26
34
  };
@@ -80,12 +88,13 @@ export function fontImports(a) {
80
88
  }
81
89
  /** ` data-forte-radius="pill" ...` — leading space included, "" when all
82
90
  * defaults. Default modes stay UNSET so the app keeps following the OS
83
- * (motion) and the library's own defaults. */
91
+ * (motion, colour scheme) and the library's own defaults. */
84
92
  export function htmlAttrs(a) {
85
93
  const attrs = [
86
94
  a.radius !== "default" && `data-forte-radius="${a.radius}"`,
87
95
  a.density !== "default" && `data-forte-density="${a.density}"`,
88
96
  a.motion !== "default" && `data-forte-motion="${a.motion}"`,
97
+ a.scheme !== "system" && `data-theme="${a.scheme}"`,
89
98
  ].filter(Boolean);
90
99
  return attrs.length ? " " + attrs.join(" ") : "";
91
100
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "create-forte-ui",
3
- "version": "1.0.0-alpha.5",
3
+ "version": "1.0.0-alpha.7",
4
4
  "description": "Scaffold a new app wired up with forte-ui — Vite or Next.js, with or without Tailwind, themed from your answers.",
5
5
  "license": "MIT",
6
6
  "type": "module",
@@ -24,13 +24,6 @@
24
24
  "publishConfig": {
25
25
  "access": "public"
26
26
  },
27
- "scripts": {
28
- "build": "tsc -p tsconfig.json",
29
- "dev": "tsc -p tsconfig.json --watch",
30
- "typecheck": "tsc -p tsconfig.json --noEmit",
31
- "smoke": "node scripts/smoke.mjs",
32
- "clean": "rm -rf dist .turbo"
33
- },
34
27
  "dependencies": {
35
28
  "@clack/prompts": "^0.11.0",
36
29
  "picocolors": "^1.1.1"
@@ -41,5 +34,12 @@
41
34
  "engines": {
42
35
  "node": ">=20"
43
36
  },
44
- "homepage": "https://forte-ui.com"
45
- }
37
+ "homepage": "https://forte-ui.com",
38
+ "scripts": {
39
+ "build": "tsc -p tsconfig.json",
40
+ "dev": "tsc -p tsconfig.json --watch",
41
+ "typecheck": "tsc -p tsconfig.json --noEmit",
42
+ "smoke": "node scripts/smoke.mjs",
43
+ "clean": "rm -rf dist .turbo"
44
+ }
45
+ }