create-cronus-app 0.6.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (56) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +173 -0
  3. package/dist/compose.d.ts +56 -0
  4. package/dist/compose.js +92 -0
  5. package/dist/index.d.ts +33 -0
  6. package/dist/index.js +255 -0
  7. package/dist/scaffold.d.ts +31 -0
  8. package/dist/scaffold.js +118 -0
  9. package/dist/utils.d.ts +121 -0
  10. package/dist/utils.js +285 -0
  11. package/dist/version.d.ts +3 -0
  12. package/dist/version.js +3 -0
  13. package/package.json +56 -0
  14. package/templates/dashboard/README.md +61 -0
  15. package/templates/dashboard/app/globals.css +16 -0
  16. package/templates/dashboard/app/layout.tsx +40 -0
  17. package/templates/dashboard/app/page.tsx +77 -0
  18. package/templates/dashboard/app/settings/page.tsx +21 -0
  19. package/templates/dashboard/components/dashboard-shell.tsx +120 -0
  20. package/templates/dashboard/components/orders-table.tsx +132 -0
  21. package/templates/dashboard/components/revenue-chart.tsx +50 -0
  22. package/templates/dashboard/components/settings-form.tsx +167 -0
  23. package/templates/dashboard/cronus-ui.json +17 -0
  24. package/templates/dashboard/gitignore +34 -0
  25. package/templates/dashboard/next.config.mjs +11 -0
  26. package/templates/dashboard/package.json +30 -0
  27. package/templates/dashboard/postcss.config.mjs +7 -0
  28. package/templates/dashboard/tsconfig.json +23 -0
  29. package/templates/default/README.md +46 -0
  30. package/templates/default/app/globals.css +16 -0
  31. package/templates/default/app/layout.tsx +39 -0
  32. package/templates/default/app/page.tsx +112 -0
  33. package/templates/default/cronus-ui.json +17 -0
  34. package/templates/default/gitignore +34 -0
  35. package/templates/default/next.config.mjs +11 -0
  36. package/templates/default/package.json +27 -0
  37. package/templates/default/postcss.config.mjs +7 -0
  38. package/templates/default/tsconfig.json +23 -0
  39. package/templates/marketing/README.md +65 -0
  40. package/templates/marketing/app/globals.css +16 -0
  41. package/templates/marketing/app/layout.tsx +39 -0
  42. package/templates/marketing/app/page.tsx +25 -0
  43. package/templates/marketing/components/faq.tsx +62 -0
  44. package/templates/marketing/components/feature-grid.tsx +67 -0
  45. package/templates/marketing/components/hero.tsx +61 -0
  46. package/templates/marketing/components/pricing.tsx +116 -0
  47. package/templates/marketing/components/site-footer.tsx +91 -0
  48. package/templates/marketing/components/site-header.tsx +47 -0
  49. package/templates/marketing/components/testimonials.tsx +91 -0
  50. package/templates/marketing/components/waitlist-cta.tsx +93 -0
  51. package/templates/marketing/cronus-ui.json +17 -0
  52. package/templates/marketing/gitignore +34 -0
  53. package/templates/marketing/next.config.mjs +11 -0
  54. package/templates/marketing/package.json +28 -0
  55. package/templates/marketing/postcss.config.mjs +7 -0
  56. package/templates/marketing/tsconfig.json +23 -0
@@ -0,0 +1,118 @@
1
+ import { spawnSync } from "node:child_process";
2
+ import { cpSync, existsSync, mkdirSync, readdirSync, readFileSync, statSync, writeFileSync, } from "node:fs";
3
+ import { dirname, join } from "node:path";
4
+ import { fileURLToPath } from "node:url";
5
+ import { DEFAULT_MODE, DEFAULT_TEMPLATE, DEFAULT_THEME, templateBaseDir, } from "./utils.js";
6
+ const HERE = dirname(fileURLToPath(import.meta.url));
7
+ /**
8
+ * Locate the bundled dir for `template` under `templates/`. In the published
9
+ * package, sources live in `dist/` and templates are a sibling (`../templates`).
10
+ * When running from source (tests/ts-node), they're one level up from `src/` as
11
+ * well.
12
+ */
13
+ function templateRoot(template) {
14
+ const candidates = [
15
+ join(HERE, "..", "templates", template),
16
+ join(HERE, "..", "..", "templates", template),
17
+ ];
18
+ const found = candidates.find((p) => existsSync(p));
19
+ if (!found) {
20
+ throw new Error(`Could not locate the bundled "${template}" template (looked in: ${candidates.join(", ")}).`);
21
+ }
22
+ return found;
23
+ }
24
+ /**
25
+ * Files whose dot-prefixed name npm strips when packing a template. We store
26
+ * them undotted in the template and restore the leading "." on scaffold.
27
+ * `gitignore` → `.gitignore`; a `_`-prefixed file → its dotted form.
28
+ */
29
+ const DOTFILE_RENAMES = {
30
+ gitignore: ".gitignore",
31
+ npmrc: ".npmrc",
32
+ };
33
+ function restoreDotfileName(base) {
34
+ if (base in DOTFILE_RENAMES)
35
+ return DOTFILE_RENAMES[base];
36
+ if (base.startsWith("_") && base.length > 1)
37
+ return `.${base.slice(1)}`;
38
+ return base;
39
+ }
40
+ /** Text extensions that get token replacement. Everything else is copied raw. */
41
+ const TEXT_EXTENSIONS = new Set([
42
+ ".ts",
43
+ ".tsx",
44
+ ".js",
45
+ ".jsx",
46
+ ".mjs",
47
+ ".cjs",
48
+ ".json",
49
+ ".css",
50
+ ".md",
51
+ ".txt",
52
+ ".html",
53
+ ]);
54
+ function isTextFile(path) {
55
+ const dot = path.lastIndexOf(".");
56
+ return dot !== -1 && TEXT_EXTENSIONS.has(path.slice(dot));
57
+ }
58
+ /**
59
+ * Replace template tokens. `__APP_NAME__` is the package name; `__THEME__` /
60
+ * `__MODE__` are the chosen theme preset + color mode (baked into layout.tsx and
61
+ * cronus-ui.json). Theme/mode default to the ship defaults so existing callers
62
+ * (and tests) that pass only a name keep working.
63
+ */
64
+ export function applyTokens(content, name, theme = DEFAULT_THEME, mode = DEFAULT_MODE) {
65
+ return content
66
+ .replaceAll("__APP_NAME__", name)
67
+ .replaceAll("__THEME__", theme)
68
+ .replaceAll("__MODE__", mode);
69
+ }
70
+ /**
71
+ * Recursively copy the chosen template into `targetDir`, replacing tokens in
72
+ * text files and restoring stripped dotfile names. Returns the file count.
73
+ */
74
+ export function scaffold(options) {
75
+ // Composed templates (store/landing) have no bundled dir — copy the base they
76
+ // build on (`default`), then the post-scaffold composeApp() step generates the
77
+ // pages/chrome from validated blocks.
78
+ const src = templateRoot(templateBaseDir(options.template ?? DEFAULT_TEMPLATE));
79
+ const { targetDir, name, theme = DEFAULT_THEME, mode = DEFAULT_MODE } = options;
80
+ mkdirSync(targetDir, { recursive: true });
81
+ const fileCount = copyDir(src, targetDir, name, theme, mode);
82
+ return { fileCount };
83
+ }
84
+ function copyDir(srcDir, destDir, name, theme, mode) {
85
+ mkdirSync(destDir, { recursive: true });
86
+ let count = 0;
87
+ for (const entry of readdirSync(srcDir)) {
88
+ const srcPath = join(srcDir, entry);
89
+ const destPath = join(destDir, restoreDotfileName(entry));
90
+ if (statSync(srcPath).isDirectory()) {
91
+ count += copyDir(srcPath, destPath, name, theme, mode);
92
+ continue;
93
+ }
94
+ if (isTextFile(srcPath)) {
95
+ const content = applyTokens(readFileSync(srcPath, "utf8"), name, theme, mode);
96
+ writeFileSync(destPath, content);
97
+ }
98
+ else {
99
+ cpSync(srcPath, destPath);
100
+ }
101
+ count += 1;
102
+ }
103
+ return count;
104
+ }
105
+ /** Run the package manager's install in `cwd`. Throws if it exits non-zero. */
106
+ export function runInstall(pm, cwd) {
107
+ const result = spawnSync(pm, ["install"], {
108
+ cwd,
109
+ stdio: "inherit",
110
+ shell: process.platform === "win32",
111
+ });
112
+ if (result.error)
113
+ throw result.error;
114
+ if (typeof result.status === "number" && result.status !== 0) {
115
+ throw new Error(`${pm} install exited with code ${result.status}`);
116
+ }
117
+ }
118
+ //# sourceMappingURL=scaffold.js.map
@@ -0,0 +1,121 @@
1
+ /** Supported package managers, in detection-fallback order. */
2
+ export declare const PACKAGE_MANAGERS: readonly ["bun", "npm", "pnpm", "yarn"];
3
+ export type PackageManager = (typeof PACKAGE_MANAGERS)[number];
4
+ export declare const c: {
5
+ bold: (s: string) => string;
6
+ dim: (s: string) => string;
7
+ green: (s: string) => string;
8
+ yellow: (s: string) => string;
9
+ red: (s: string) => string;
10
+ cyan: (s: string) => string;
11
+ magenta: (s: string) => string;
12
+ };
13
+ /**
14
+ * Next-step lines printed after a successful scaffold. Pure so tests can assert
15
+ * the copy without scraping stdout. `template` is optional for back-compat: omit
16
+ * it (or pass a bundled template) to keep the component-add path; composed
17
+ * templates (`saas`/`store`/`landing`) get add-page / theme / upgrade instead.
18
+ */
19
+ export declare function outroLines(name: string, pm: PackageManager, installed: boolean, template?: TemplateName): string[];
20
+ export declare const log: {
21
+ intro(): void;
22
+ step(msg: string): void;
23
+ ok(msg: string): void;
24
+ warn(msg: string): void;
25
+ error(msg: string): void;
26
+ outro(name: string, pm: PackageManager, installed: boolean, template?: TemplateName): void;
27
+ };
28
+ /**
29
+ * The starter templates a user can pick. `default`/`dashboard`/`marketing` copy
30
+ * a bundled directory under `templates/`; `store`/`landing`/`saas` have NO
31
+ * template dir — they scaffold the `default` base and then compose validated
32
+ * blocks on top (see {@link TEMPLATE_BASE_DIR} and the post-scaffold
33
+ * `composeApp()` step).
34
+ */
35
+ /**
36
+ * Product landing flavors (compose apps). Each is a distinctive stack of
37
+ * validated marketing blocks + a theme/mode default — not a bundled directory.
38
+ * `landing` stays the generic marketing page; these are the named flavors.
39
+ */
40
+ export declare const LANDING_FLAVORS: readonly ["landing-studio", "landing-ops", "landing-secure", "landing-care", "landing-shop", "landing-docs", "landing-premium", "landing-agents", "landing-coverage", "landing-broadcast", "landing-agency", "landing-glass"];
41
+ export type LandingFlavor = (typeof LANDING_FLAVORS)[number];
42
+ /**
43
+ * Pro compose apps. Additive product surfaces (mail, chat, finance) — not
44
+ * landings. OSS keeps saas/store/landing; these are the Pro pack.
45
+ */
46
+ export declare const PRO_APPS: readonly ["mail", "chat", "finance"];
47
+ export type ProApp = (typeof PRO_APPS)[number];
48
+ export declare const TEMPLATES: readonly ["default", "dashboard", "marketing", "store", "landing", "saas", "landing-studio", "landing-ops", "landing-secure", "landing-care", "landing-shop", "landing-docs", "landing-premium", "landing-agents", "landing-coverage", "landing-broadcast", "landing-agency", "landing-glass", "mail", "chat", "finance"];
49
+ export type TemplateName = (typeof TEMPLATES)[number];
50
+ export declare const DEFAULT_TEMPLATE: TemplateName;
51
+ /**
52
+ * The template names generated by the Cronus Compose app generator (no bundled
53
+ * dir): they scaffold the `default` base, then `composeApp()` from the bundled
54
+ * manifest renders the pages + chrome from validated blocks. The value is the
55
+ * manifest/template name passed to `composeApp`.
56
+ */
57
+ export declare const COMPOSED_TEMPLATES: {
58
+ readonly store: "store";
59
+ readonly landing: "landing";
60
+ readonly saas: "saas";
61
+ readonly "landing-studio": "landing-studio";
62
+ readonly "landing-ops": "landing-ops";
63
+ readonly "landing-secure": "landing-secure";
64
+ readonly "landing-care": "landing-care";
65
+ readonly "landing-shop": "landing-shop";
66
+ readonly "landing-docs": "landing-docs";
67
+ readonly "landing-premium": "landing-premium";
68
+ readonly "landing-agents": "landing-agents";
69
+ readonly "landing-coverage": "landing-coverage";
70
+ readonly "landing-broadcast": "landing-broadcast";
71
+ readonly "landing-agency": "landing-agency";
72
+ readonly "landing-glass": "landing-glass";
73
+ readonly mail: "mail";
74
+ readonly chat: "chat";
75
+ readonly finance: "finance";
76
+ };
77
+ export type ComposedTemplateName = keyof typeof COMPOSED_TEMPLATES;
78
+ /** True when `template` is generated by the composer (no bundled dir). */
79
+ export declare function isComposedTemplate(template: TemplateName): template is ComposedTemplateName;
80
+ /**
81
+ * The bundled base directory each template copies before any composition.
82
+ * Composed templates (`store`/`landing`/`saas`) reuse the `default` base; the
83
+ * others are their own name.
84
+ */
85
+ export declare function templateBaseDir(template: TemplateName): TemplateName;
86
+ /** One-line summary per template, shown in the picker. */
87
+ export declare const TEMPLATE_HINTS: Record<TemplateName, string>;
88
+ /** The five shipped theme presets (must match @cronus-ui/tokens `ThemeName`). */
89
+ export declare const THEMES: readonly ["aurora", "neutral", "midnight", "sunset", "emerald"];
90
+ export type Theme = (typeof THEMES)[number];
91
+ export declare const DEFAULT_THEME: Theme;
92
+ /** One-line vibe per theme, shown in the picker. */
93
+ export declare const THEME_HINTS: Record<Theme, string>;
94
+ /** Color modes the provider supports natively (no built-in "system"). */
95
+ export declare const MODES: readonly ["dark", "light"];
96
+ export type ModeName = (typeof MODES)[number];
97
+ export declare const DEFAULT_MODE: ModeName;
98
+ /** Theme/mode baked when the user picks a landing flavor without `--theme`/`--mode`. */
99
+ export declare const TEMPLATE_APPEARANCE: Partial<Record<TemplateName, {
100
+ theme: Theme;
101
+ mode: ModeName;
102
+ }>>;
103
+ /**
104
+ * A single-select TTY prompt: prints a numbered list and returns the option the
105
+ * user picks (by number or name), the default on empty input, or the default
106
+ * verbatim when stdin is not a TTY (piped/CI) so scaffolding never blocks.
107
+ */
108
+ export declare function promptSelect<T extends string>(label: string, options: readonly T[], defaultValue: T, hints?: Partial<Record<T, string>>): Promise<T>;
109
+ /**
110
+ * A yes/no TTY prompt. Returns `defaultValue` on empty input or when stdin is
111
+ * not a TTY (piped/CI), so scaffolding never blocks on a confirmation.
112
+ */
113
+ export declare function promptConfirm(label: string, defaultValue?: boolean): Promise<boolean>;
114
+ /** Validate that `name` is safe to use as an npm package name and a dir name. */
115
+ export declare function isValidProjectName(name: string): boolean;
116
+ /**
117
+ * Derive the on-disk directory name from a (possibly scoped) package name:
118
+ * "@acme/widget" → "widget", "my-app" → "my-app".
119
+ */
120
+ export declare function dirNameFromProjectName(name: string): string;
121
+ //# sourceMappingURL=utils.d.ts.map
package/dist/utils.js ADDED
@@ -0,0 +1,285 @@
1
+ /** Supported package managers, in detection-fallback order. */
2
+ export const PACKAGE_MANAGERS = ["bun", "npm", "pnpm", "yarn"];
3
+ // Minimal ANSI helpers — no dependency needed. Colors are dropped when stdout
4
+ // is not a TTY or NO_COLOR is set, so piped/CI output stays clean.
5
+ const useColor = Boolean(process.stdout.isTTY) && !process.env.NO_COLOR;
6
+ const wrap = (open, close) => (s) => useColor ? `[${open}m${s}[${close}m` : s;
7
+ export const c = {
8
+ bold: wrap(1, 22),
9
+ dim: wrap(2, 22),
10
+ green: wrap(32, 39),
11
+ yellow: wrap(33, 39),
12
+ red: wrap(31, 39),
13
+ cyan: wrap(36, 39),
14
+ magenta: wrap(35, 39),
15
+ };
16
+ /**
17
+ * Next-step lines printed after a successful scaffold. Pure so tests can assert
18
+ * the copy without scraping stdout. `template` is optional for back-compat: omit
19
+ * it (or pass a bundled template) to keep the component-add path; composed
20
+ * templates (`saas`/`store`/`landing`) get add-page / theme / upgrade instead.
21
+ */
22
+ export function outroLines(name, pm, installed, template) {
23
+ const dev = pm === "npm" ? "npm run dev" : `${pm} dev`;
24
+ const install = pm === "yarn" ? "yarn" : `${pm} install`;
25
+ const dollar = c.dim("$");
26
+ const composed = template !== undefined && isComposedTemplate(template);
27
+ const grow = composed
28
+ ? [
29
+ "Grow the app anytime:",
30
+ ` ${dollar} npx cronus-ui add-page --route /pricing --blocks pricing,cta --nav Pricing`,
31
+ ` ${dollar} npx cronus-ui theme set aurora --mode dark`,
32
+ ` ${dollar} npx cronus-ui upgrade --all --dry-run`,
33
+ ]
34
+ : [
35
+ `Add more components anytime: ${dollar} npx cronus-ui add dialog table tabs`,
36
+ `Want a multi-page app? ${dollar} npx cronus-ui compose saas`,
37
+ ];
38
+ return [
39
+ "",
40
+ `${c.green(c.bold("Done!"))} Your Cronus UI app is ready in ${c.cyan(name)}.`,
41
+ "",
42
+ "Next steps:",
43
+ ` ${dollar} cd ${name}`,
44
+ ...(installed ? [] : [` ${dollar} ${install}`]),
45
+ ` ${dollar} ${dev}`,
46
+ "",
47
+ `Then open ${c.cyan("http://localhost:3000")}.`,
48
+ "",
49
+ ...grow,
50
+ "",
51
+ ];
52
+ }
53
+ export const log = {
54
+ intro() {
55
+ process.stdout.write(`\n${c.magenta(c.bold("create-cronus-app"))}\n\n`);
56
+ },
57
+ step(msg) {
58
+ process.stdout.write(`${c.cyan("›")} ${msg}\n`);
59
+ },
60
+ ok(msg) {
61
+ process.stdout.write(`${c.green("✓")} ${msg}\n`);
62
+ },
63
+ warn(msg) {
64
+ process.stdout.write(`${c.yellow("!")} ${msg}\n`);
65
+ },
66
+ error(msg) {
67
+ process.stderr.write(`${c.red("✗")} ${msg}\n`);
68
+ },
69
+ outro(name, pm, installed, template) {
70
+ process.stdout.write(`${outroLines(name, pm, installed, template).join("\n")}\n`);
71
+ },
72
+ };
73
+ /**
74
+ * The starter templates a user can pick. `default`/`dashboard`/`marketing` copy
75
+ * a bundled directory under `templates/`; `store`/`landing`/`saas` have NO
76
+ * template dir — they scaffold the `default` base and then compose validated
77
+ * blocks on top (see {@link TEMPLATE_BASE_DIR} and the post-scaffold
78
+ * `composeApp()` step).
79
+ */
80
+ /**
81
+ * Product landing flavors (compose apps). Each is a distinctive stack of
82
+ * validated marketing blocks + a theme/mode default — not a bundled directory.
83
+ * `landing` stays the generic marketing page; these are the named flavors.
84
+ */
85
+ export const LANDING_FLAVORS = [
86
+ "landing-studio",
87
+ "landing-ops",
88
+ "landing-secure",
89
+ "landing-care",
90
+ "landing-shop",
91
+ "landing-docs",
92
+ "landing-premium",
93
+ "landing-agents",
94
+ "landing-coverage",
95
+ "landing-broadcast",
96
+ "landing-agency",
97
+ "landing-glass",
98
+ ];
99
+ /**
100
+ * Pro compose apps. Additive product surfaces (mail, chat, finance) — not
101
+ * landings. OSS keeps saas/store/landing; these are the Pro pack.
102
+ */
103
+ export const PRO_APPS = ["mail", "chat", "finance"];
104
+ export const TEMPLATES = [
105
+ "default",
106
+ "dashboard",
107
+ "marketing",
108
+ "store",
109
+ "landing",
110
+ "saas",
111
+ ...LANDING_FLAVORS,
112
+ ...PRO_APPS,
113
+ ];
114
+ export const DEFAULT_TEMPLATE = "default";
115
+ /**
116
+ * The template names generated by the Cronus Compose app generator (no bundled
117
+ * dir): they scaffold the `default` base, then `composeApp()` from the bundled
118
+ * manifest renders the pages + chrome from validated blocks. The value is the
119
+ * manifest/template name passed to `composeApp`.
120
+ */
121
+ export const COMPOSED_TEMPLATES = {
122
+ store: "store",
123
+ landing: "landing",
124
+ saas: "saas",
125
+ "landing-studio": "landing-studio",
126
+ "landing-ops": "landing-ops",
127
+ "landing-secure": "landing-secure",
128
+ "landing-care": "landing-care",
129
+ "landing-shop": "landing-shop",
130
+ "landing-docs": "landing-docs",
131
+ "landing-premium": "landing-premium",
132
+ "landing-agents": "landing-agents",
133
+ "landing-coverage": "landing-coverage",
134
+ "landing-broadcast": "landing-broadcast",
135
+ "landing-agency": "landing-agency",
136
+ "landing-glass": "landing-glass",
137
+ mail: "mail",
138
+ chat: "chat",
139
+ finance: "finance",
140
+ };
141
+ /** True when `template` is generated by the composer (no bundled dir). */
142
+ export function isComposedTemplate(template) {
143
+ return template in COMPOSED_TEMPLATES;
144
+ }
145
+ /**
146
+ * The bundled base directory each template copies before any composition.
147
+ * Composed templates (`store`/`landing`/`saas`) reuse the `default` base; the
148
+ * others are their own name.
149
+ */
150
+ export function templateBaseDir(template) {
151
+ return isComposedTemplate(template) ? "default" : template;
152
+ }
153
+ /** One-line summary per template, shown in the picker. */
154
+ export const TEMPLATE_HINTS = {
155
+ default: "single-page starter — metrics, table & cards (default)",
156
+ dashboard: "multi-page app — sidebar shell, KPIs, chart, data table, settings",
157
+ marketing: "landing site — hero, features, pricing, testimonials, FAQ, waitlist",
158
+ store: "generated storefront — 9 navigable pages, real nav, from validated blocks",
159
+ landing: "generated landing page — hero, features, pricing, testimonials, FAQ, CTA",
160
+ saas: "recommended for a full product — split auth + shell dashboard, team, billing, settings",
161
+ "landing-studio": "dark AI studio landing — atmosphere hero, marquee, bento, stats, pricing",
162
+ "landing-ops": "ops/workflow landing — split hero, logos, features, integrations",
163
+ "landing-secure": "security/infra landing — compact hero, metrics, usage pricing, split FAQ",
164
+ "landing-care": "healthcare/conversion landing — waitlist hero, bento, proof (emerald light)",
165
+ "landing-shop": "storefront landing — compact hero + editorial product showcase (sunset light)",
166
+ "landing-docs": "developer-tool landing — compact hero, logos, features, integrations",
167
+ "landing-premium": "full SaaS marketing page — split hero, toggle pricing, FAQ (aurora light)",
168
+ "landing-agents": "automation landing — split hero, marquee, bento, stats (emerald light)",
169
+ "landing-coverage": "services landing — hero, stats, testimonial grid, FAQ (sunset light)",
170
+ "landing-broadcast": "studio/show landing — atmosphere hero, marquee logos, features, pricing",
171
+ "landing-agency": "agency landing — split hero, about, services, stats (midnight dark)",
172
+ "landing-glass": "glass-dark landing — atmosphere hero, bento, split FAQ, split CTA",
173
+ mail: "Pro inbox — notification panel, activity feed, compose, preferences (midnight dark)",
174
+ chat: "Pro assistant — chat thread, prompt box, replies, settings (aurora dark)",
175
+ finance: "Pro money app — payouts, invoices, billing, analytics (emerald light)",
176
+ };
177
+ /** The five shipped theme presets (must match @cronus-ui/tokens `ThemeName`). */
178
+ export const THEMES = ["aurora", "neutral", "midnight", "sunset", "emerald"];
179
+ export const DEFAULT_THEME = "aurora";
180
+ /** One-line vibe per theme, shown in the picker. */
181
+ export const THEME_HINTS = {
182
+ aurora: "sky → cyan premium gradient (default)",
183
+ neutral: "clean black & white — docs-site chrome, achromatic",
184
+ midnight: "deep indigo / violet",
185
+ sunset: "warm amber / rose",
186
+ emerald: "fresh green / teal",
187
+ };
188
+ /** Color modes the provider supports natively (no built-in "system"). */
189
+ export const MODES = ["dark", "light"];
190
+ export const DEFAULT_MODE = "dark";
191
+ /** Theme/mode baked when the user picks a landing flavor without `--theme`/`--mode`. */
192
+ export const TEMPLATE_APPEARANCE = {
193
+ "landing-studio": { theme: "midnight", mode: "dark" },
194
+ "landing-ops": { theme: "aurora", mode: "dark" },
195
+ "landing-secure": { theme: "midnight", mode: "dark" },
196
+ "landing-care": { theme: "emerald", mode: "light" },
197
+ "landing-shop": { theme: "sunset", mode: "light" },
198
+ "landing-docs": { theme: "aurora", mode: "dark" },
199
+ "landing-premium": { theme: "aurora", mode: "light" },
200
+ "landing-agents": { theme: "emerald", mode: "light" },
201
+ "landing-coverage": { theme: "sunset", mode: "light" },
202
+ "landing-broadcast": { theme: "aurora", mode: "dark" },
203
+ "landing-agency": { theme: "midnight", mode: "dark" },
204
+ "landing-glass": { theme: "midnight", mode: "dark" },
205
+ mail: { theme: "midnight", mode: "dark" },
206
+ chat: { theme: "aurora", mode: "dark" },
207
+ finance: { theme: "emerald", mode: "light" },
208
+ };
209
+ /**
210
+ * A single-select TTY prompt: prints a numbered list and returns the option the
211
+ * user picks (by number or name), the default on empty input, or the default
212
+ * verbatim when stdin is not a TTY (piped/CI) so scaffolding never blocks.
213
+ */
214
+ export async function promptSelect(label, options, defaultValue, hints) {
215
+ if (!process.stdin.isTTY)
216
+ return defaultValue;
217
+ const { createInterface } = await import("node:readline/promises");
218
+ const rl = createInterface({ input: process.stdin, output: process.stdout });
219
+ try {
220
+ process.stdout.write(`${c.bold(label)}\n`);
221
+ options.forEach((opt, i) => {
222
+ const marker = opt === defaultValue ? c.green("●") : c.dim("○");
223
+ const hint = hints?.[opt] ? ` ${c.dim(hints[opt])}` : "";
224
+ process.stdout.write(` ${marker} ${c.cyan(String(i + 1))} ${opt}${hint}\n`);
225
+ });
226
+ const answer = (await rl.question(`${c.dim(`(1-${options.length} or name, default ${defaultValue})`)} `)).trim();
227
+ if (!answer)
228
+ return defaultValue;
229
+ const asNum = Number.parseInt(answer, 10);
230
+ if (Number.isInteger(asNum) && asNum >= 1 && asNum <= options.length) {
231
+ return options[asNum - 1];
232
+ }
233
+ const byName = options.find((o) => o === answer.toLowerCase());
234
+ return byName ?? defaultValue;
235
+ }
236
+ finally {
237
+ rl.close();
238
+ }
239
+ }
240
+ /**
241
+ * A yes/no TTY prompt. Returns `defaultValue` on empty input or when stdin is
242
+ * not a TTY (piped/CI), so scaffolding never blocks on a confirmation.
243
+ */
244
+ export async function promptConfirm(label, defaultValue = true) {
245
+ if (!process.stdin.isTTY)
246
+ return defaultValue;
247
+ const { createInterface } = await import("node:readline/promises");
248
+ const rl = createInterface({ input: process.stdin, output: process.stdout });
249
+ try {
250
+ const hint = defaultValue ? "Y/n" : "y/N";
251
+ const answer = (await rl.question(`${c.bold(label)} ${c.dim(`(${hint})`)} `))
252
+ .trim()
253
+ .toLowerCase();
254
+ if (!answer)
255
+ return defaultValue;
256
+ return answer === "y" || answer === "yes";
257
+ }
258
+ finally {
259
+ rl.close();
260
+ }
261
+ }
262
+ // npm's package-name rules (the spec subset we care about): optionally scoped,
263
+ // lowercase, URL-safe, may not start with "." or "_", <= 214 chars.
264
+ const SCOPED = /^(?:@[a-z0-9-~][a-z0-9-._~]*\/)?[a-z0-9-~][a-z0-9-._~]*$/;
265
+ /** Validate that `name` is safe to use as an npm package name and a dir name. */
266
+ export function isValidProjectName(name) {
267
+ if (!name || name.length > 214)
268
+ return false;
269
+ if (name.trim() !== name)
270
+ return false;
271
+ // Reject path separators outright (a scoped name's single "/" is allowed by
272
+ // the regex, but the literal dir is the unscoped trailing segment).
273
+ if (name.includes("\\"))
274
+ return false;
275
+ return SCOPED.test(name);
276
+ }
277
+ /**
278
+ * Derive the on-disk directory name from a (possibly scoped) package name:
279
+ * "@acme/widget" → "widget", "my-app" → "my-app".
280
+ */
281
+ export function dirNameFromProjectName(name) {
282
+ const slash = name.lastIndexOf("/");
283
+ return slash === -1 ? name : name.slice(slash + 1);
284
+ }
285
+ //# sourceMappingURL=utils.js.map
@@ -0,0 +1,3 @@
1
+ /** Kept in sync with package.json#version. */
2
+ export declare const CREATE_VERSION = "0.6.0";
3
+ //# sourceMappingURL=version.d.ts.map
@@ -0,0 +1,3 @@
1
+ /** Kept in sync with package.json#version. */
2
+ export const CREATE_VERSION = "0.6.0";
3
+ //# sourceMappingURL=version.js.map
package/package.json ADDED
@@ -0,0 +1,56 @@
1
+ {
2
+ "name": "create-cronus-app",
3
+ "version": "0.6.0",
4
+ "description": "Scaffold a Next.js + Cronus UI app: npx create-cronus-app my-app --template saas.",
5
+ "type": "module",
6
+ "license": "MIT",
7
+ "author": "Cronus",
8
+ "repository": {
9
+ "type": "git",
10
+ "url": "git+https://github.com/pedrogbraz/cronus-ui.git",
11
+ "directory": "packages/create-cronus-app"
12
+ },
13
+ "homepage": "https://aicronus.com",
14
+ "bugs": {
15
+ "url": "https://github.com/pedrogbraz/cronus-ui/issues"
16
+ },
17
+ "keywords": [
18
+ "cronus",
19
+ "create-cronus-app",
20
+ "design-system",
21
+ "next",
22
+ "scaffold",
23
+ "starter",
24
+ "tailwind",
25
+ "ui"
26
+ ],
27
+ "bin": {
28
+ "create-cronus-app": "./dist/index.js"
29
+ },
30
+ "files": [
31
+ "dist",
32
+ "templates",
33
+ "LICENSE",
34
+ "README.md",
35
+ "!dist/**/*.map"
36
+ ],
37
+ "publishConfig": {
38
+ "access": "public"
39
+ },
40
+ "scripts": {
41
+ "build": "tsc -p tsconfig.json",
42
+ "typecheck": "tsc -p tsconfig.json --noEmit",
43
+ "prepublishOnly": "tsc -p tsconfig.json"
44
+ },
45
+ "dependencies": {
46
+ "@cronus-ui/ai-kit": "0.6.0",
47
+ "cronus-ui": "0.6.0"
48
+ },
49
+ "devDependencies": {
50
+ "@types/node": "^22.10.0",
51
+ "typescript": "^6.0.3"
52
+ },
53
+ "engines": {
54
+ "node": ">=20"
55
+ }
56
+ }
@@ -0,0 +1,61 @@
1
+ # __APP_NAME__
2
+
3
+ A multi-page dashboard built with [Next.js](https://nextjs.org) (App Router) and
4
+ [Cronus UI](https://www.npmjs.com/package/@cronus-ui/ui) — themeable, accessible React
5
+ components on Tailwind v4.
6
+
7
+ > **Screenshot placeholder** — run `npm run dev`, open http://localhost:3000, and drop a
8
+ > capture here (e.g. `docs/screenshot.png`) so your repo landing page shows the app.
9
+
10
+ ## Getting started
11
+
12
+ ```sh
13
+ npm install # or: pnpm install / yarn / bun install
14
+ npm run dev
15
+ ```
16
+
17
+ Open [http://localhost:3000](http://localhost:3000) — the pages hot-reload as you save.
18
+
19
+ ## What's inside
20
+
21
+ | Route | File | Contents |
22
+ | --- | --- | --- |
23
+ | `/` | `app/page.tsx` | KPI metric cards, a revenue bar chart, and a searchable, sortable orders `DataTable`. |
24
+ | `/settings` | `app/settings/page.tsx` | Profile, workspace, and notification forms (`Field`, `Input`, `Select`, `Switch`). |
25
+
26
+ The persistent chrome lives in `components/dashboard-shell.tsx`: an `AppShell` with an
27
+ icon-collapsible `Sidebar` (route-aware via `usePathname`) and a sticky topbar with a
28
+ light/dark toggle.
29
+
30
+ ## How it's wired
31
+
32
+ - **`app/globals.css`** imports Tailwind and the Cronus token CSS, plus the required
33
+ `@source "../node_modules/@cronus-ui/ui/dist/**/*.js";` line so Tailwind v4 emits the
34
+ component utility classes (it skips `node_modules` by default).
35
+ - **`app/layout.tsx`** mounts `<CronusUIProvider>` and the anti-flash `<CronusThemeScript>`
36
+ so the right theme is applied before hydration, then wraps every page in the shell.
37
+ - **`components/revenue-chart.tsx`** shows the `ChartContainer` + recharts pattern — the
38
+ chart colors come from your theme tokens (`--color-chart-*`).
39
+ - **`components/orders-table.tsx`** shows the `DataTable` pattern — column defs via
40
+ `@tanstack/react-table`, sortable headers via `DataTableColumnHeader`.
41
+
42
+ ## Add more components
43
+
44
+ This app includes a `cronus-ui.json`, so you can pull any component from the registry:
45
+
46
+ ```sh
47
+ npx cronus-ui add dialog tabs dropdown-menu
48
+ ```
49
+
50
+ ## Scripts
51
+
52
+ | Script | Description |
53
+ | --- | --- |
54
+ | `dev` | Start the dev server. |
55
+ | `build` | Production build. |
56
+ | `start` | Serve the production build. |
57
+
58
+ ## Learn more
59
+
60
+ - [Next.js documentation](https://nextjs.org/docs)
61
+ - [Tailwind CSS v4](https://tailwindcss.com)
@@ -0,0 +1,16 @@
1
+ @import "tailwindcss";
2
+ @import "@cronus-ui/tokens/styles.css";
3
+
4
+ /*
5
+ * REQUIRED on Tailwind v4.
6
+ *
7
+ * Tailwind v4 does not scan node_modules by default, and @cronus-ui/ui ships its
8
+ * Tailwind class strings baked into pre-compiled JS under dist. Without the
9
+ * @source below, none of the component utility classes (bg-primary, rounded-lg,
10
+ * bg-surface-raised, …) would be emitted and the components would render
11
+ * unstyled.
12
+ */
13
+ @source "../node_modules/@cronus-ui/ui/dist/**/*.js";
14
+
15
+ /* Scan this app's own source too. */
16
+ @source "./**/*.{ts,tsx}";