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
package/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 Cronus
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
package/README.md ADDED
@@ -0,0 +1,173 @@
1
+ # create-cronus-app
2
+
3
+ Scaffold a production-ready **Next.js + Cronus UI** app in one command.
4
+
5
+ ```sh
6
+ npx create-cronus-app my-app --template saas
7
+ ```
8
+
9
+ That is the gold path: a composed multi-page SaaS product — split login/signup,
10
+ sidebar-shell dashboard, analytics, team, billing, and settings — built from
11
+ validated registry blocks (the generator's golden rule: pages only import and
12
+ stack installed blocks).
13
+
14
+ You get Next.js 16 (App Router) + React 19 + Tailwind v4 with
15
+ [`@cronus-ui/ui`](https://www.npmjs.com/package/@cronus-ui/ui), `@cronus-ui/tokens`,
16
+ and `@cronus-ui/theme` already wired — themeable, accessible components and an
17
+ anti-flash theme script.
18
+
19
+ The CLI default remains `--template default` (a single-page starter). Pass
20
+ `--template saas` when you want the full product.
21
+
22
+ ## Usage
23
+
24
+ ```sh
25
+ create-cronus-app [project-name] [options]
26
+ ```
27
+
28
+ If you omit the project name, you are prompted (default: `my-cronus-app`).
29
+ Interactive runs also prompt for template, theme, mode, and the AI Kit.
30
+
31
+ ### Options
32
+
33
+ | Flag | Description |
34
+ | --- | --- |
35
+ | `--template <name>` | Starter template. CLI default: `default`. Bundled: `default`, `dashboard`, `marketing`. Composed: `store`, `landing`, `saas`, plus `landing-*` flavors (studio, ops, secure, care, shop, docs, premium, agents, coverage, broadcast, agency, glass). |
36
+ | `--theme <aurora\|neutral\|midnight\|sunset\|emerald>` | Theme preset to bake in (default: `aurora`). |
37
+ | `--mode <dark\|light>` | Default color mode (default: `dark`). |
38
+ | `--ai` / `--no-ai` | Include or skip the AI Kit (skills, rules & doctrine). |
39
+ | `--assistants <claude,cursor,copilot,windsurf,gemini\|all\|none>` | Assistants to configure (default: all). Codex CLI and Zed read the root `AGENTS.md` natively. |
40
+ | `--preset <standard\|fintech\|saas\|oss\|agency\|none>` | Engineering doctrine to ship (default: `standard`). |
41
+ | `--skills <ui-add,theme,compose,code-review,ship-pr,evidence-check\|all\|none>` | Claude Code skills to include (default: all). |
42
+ | `--pm <bun\|npm\|pnpm\|yarn>` | Package manager to install with (auto-detected otherwise). |
43
+ | `--no-install` | Skip installing dependencies. |
44
+ | `-y`, `--yes` | Accept defaults; skip interactive prompts. |
45
+ | `-h`, `--help` | Show help. |
46
+ | `-v`, `--version` | Show the version. |
47
+
48
+ There are no other flags.
49
+
50
+ ### Templates
51
+
52
+ `default`, `dashboard`, and `marketing` copy a bundled directory.
53
+
54
+ `store`, `landing`, `saas`, the `landing-*` flavors, and the Pro pack
55
+ (`mail`, `chat`, `finance`) have **no** bundled template dir. They scaffold
56
+ the `default` base, then compose pages + chrome from validated registry
57
+ blocks (the same engine as `npx cronus-ui compose <template>`). OSS keeps
58
+ saas/store/landing; the Pro pack is additive.
59
+
60
+ | Template | What you get |
61
+ | --- | --- |
62
+ | `default` | Single-page starter — metrics, table & cards. The CLI default (`--yes` without `--template` lands here). |
63
+ | `dashboard` | Bundled multi-page app — sidebar shell, KPIs, chart, data table, settings. |
64
+ | `marketing` | Bundled landing site — hero, features, pricing, testimonials, FAQ, waitlist. |
65
+ | `store` | Composed storefront — 9 navigable pages, real nav, from validated blocks. |
66
+ | `landing` | Composed marketing page — hero, features, pricing, testimonials, FAQ, CTA. |
67
+ | `saas` | Composed SaaS product — split auth + sidebar-shell dashboard, analytics, team, billing, settings. **Recommended for a full product.** |
68
+ | `landing-studio` | Dark AI studio — atmosphere hero, marquee, bento, stats, pricing. Midnight dark. |
69
+ | `landing-ops` | Workflow product — split hero, logos, features, integrations. |
70
+ | `landing-secure` | Infra/security — compact hero, metrics, usage pricing, split FAQ. Midnight dark. |
71
+ | `landing-care` | Healthcare conversion — waitlist hero, bento, proof. Emerald light. |
72
+ | `landing-shop` | Storefront — compact hero + editorial product showcase. Sunset light. |
73
+ | `landing-docs` | Developer tool — compact hero, logos, features, integrations. |
74
+ | `landing-premium` | Full SaaS marketing — split hero, toggle pricing, FAQ. Aurora light. |
75
+ | `landing-agents` | Automation — split hero, marquee, bento, stats. Emerald light. |
76
+ | `landing-coverage` | Services — hero, stats, testimonial grid, FAQ. Sunset light. |
77
+ | `landing-broadcast` | Studio/show — atmosphere hero, marquee, features, pricing. |
78
+ | `landing-agency` | Agency — split hero, about, services, stats. Midnight dark. |
79
+ | `landing-glass` | Glass dark — atmosphere hero, bento, split FAQ, split CTA. |
80
+ | `mail` | **Pro.** Inbox — notification panel, activity feed, compose, preferences. Midnight dark. |
81
+ | `chat` | **Pro.** Assistant — chat thread, prompt box, replies, settings. Aurora dark. |
82
+ | `finance` | **Pro.** Money — payouts, invoices, billing, analytics. Emerald light. |
83
+
84
+ ### AI Kit
85
+
86
+ `--ai` writes the kit; `--no-ai` skips it. If you pass neither, an interactive
87
+ run prompts (default **yes**). `--yes` accepts that default, so
88
+ `npx create-cronus-app my-app -y` **does** include the kit — pass `--no-ai` to
89
+ skip it. `--assistants`, `--preset`, and `--skills` only take effect when the
90
+ kit is written.
91
+
92
+ ### Examples
93
+
94
+ ```sh
95
+ # Gold path — composed SaaS product.
96
+ npx create-cronus-app my-app --template saas
97
+
98
+ # Same, non-interactive, skip the AI Kit, install with pnpm.
99
+ npx create-cronus-app my-app --template saas --no-ai --pm pnpm --yes
100
+
101
+ # Bundled dashboard starter.
102
+ npx create-cronus-app my-app --template dashboard
103
+
104
+ # Theme + mode (any template).
105
+ npx create-cronus-app my-app --template saas --theme aurora --mode dark
106
+
107
+ # AI Kit for a subset of assistants.
108
+ npx create-cronus-app my-app --template saas --ai --assistants claude,cursor --preset fintech
109
+
110
+ # Files only; install later yourself.
111
+ npx create-cronus-app my-app --template saas --no-install
112
+ ```
113
+
114
+ ## What you get
115
+
116
+ A `--template saas` app (after compose) looks like:
117
+
118
+ ```
119
+ my-app/
120
+ ├─ app/
121
+ │ ├─ globals.css # Tailwind v4 + Cronus tokens + the @source opt-in
122
+ │ ├─ layout.tsx # <CronusUIProvider> + anti-flash <CronusThemeScript>
123
+ │ ├─ (bare)/login # split-variant sign-in
124
+ │ ├─ (bare)/signup
125
+ │ └─ (shell)/ # dashboard (/), analytics, team, billing, settings
126
+ ├─ components/blocks/ # installed registry blocks; pages only stack them
127
+ ├─ cronus-ui.json # so `npx cronus-ui add` / add-page / compose work here
128
+ ├─ next.config.mjs
129
+ ├─ postcss.config.mjs
130
+ ├─ tsconfig.json
131
+ └─ package.json
132
+ ```
133
+
134
+ `default` is the single-page starter (`app/page.tsx` only). `store` and
135
+ `landing` compose the same way as `saas`, with their own routes and chrome.
136
+
137
+ After scaffolding:
138
+
139
+ ```sh
140
+ cd my-app
141
+ npm install # if you used --no-install
142
+ npm run dev
143
+ ```
144
+
145
+ Open [http://localhost:3000](http://localhost:3000).
146
+
147
+ ## How to grow
148
+
149
+ Composed apps (`saas` / `store` / `landing`) grow with `add-page`, not by
150
+ hand-wiring routes. Switch theme or preview upgrades at any time:
151
+
152
+ ```sh
153
+ npx cronus-ui add-page --route /pricing --blocks pricing,cta --nav Pricing
154
+ npx cronus-ui theme set aurora --mode dark
155
+ npx cronus-ui upgrade --all --dry-run
156
+ ```
157
+
158
+ Every app still has a `cronus-ui.json`, so individual components work too:
159
+
160
+ ```sh
161
+ npx cronus-ui add dialog table tabs
162
+ ```
163
+
164
+ Started from `default`, `dashboard`, or `marketing` and want a multi-page
165
+ product? Compose one onto the project:
166
+
167
+ ```sh
168
+ npx cronus-ui compose saas
169
+ ```
170
+
171
+ ## License
172
+
173
+ MIT
@@ -0,0 +1,56 @@
1
+ /**
2
+ * Post-scaffold composition adapter.
3
+ *
4
+ * `store`/`landing` have no bundled template dir: create-cronus-app copies the
5
+ * `default` base, then this module calls `cronus-ui`'s `composeApp()` library
6
+ * entry to generate the pages + chrome from validated registry blocks.
7
+ *
8
+ * The import is dynamic so the rest of the CLI (and its tests) never pay for
9
+ * loading `cronus-ui/compose` — only composed templates reach here.
10
+ *
11
+ * Registry resolution (F1 honest cut — the chicken-and-egg):
12
+ * A freshly scaffolded app's `cronus-ui.json` pins the registry to the release
13
+ * remote (`v<version>/registry`), which only ships the `meta.json` sidecar
14
+ * compose needs from v0.4.0+. `composeApp` hard-requires that sidecar. So:
15
+ * 1. `CRONUS_UI_REGISTRY` (env) wins — the dev/CI escape hatch that points at
16
+ * the local workspace `registry/` dir (which has meta.json today).
17
+ * 2. Otherwise `composeApp` uses the project's own config registry (the
18
+ * release remote — correct once v0.4.0+ publishes meta.json).
19
+ * When the registry lacks meta.json, compose fails gracefully and the caller
20
+ * keeps the valid `default` scaffold with a clear "run compose later" hint.
21
+ */
22
+ /** What create-cronus-app needs from the composer (a subset of `ComposeAppResult`). */
23
+ export interface ComposeTemplateOptions {
24
+ /** The scaffolded project root (contains cronus-ui.json). */
25
+ targetDir: string;
26
+ /** The composed template name (`store` / `landing`). */
27
+ template: string;
28
+ /** Brand string baked into chrome/hero copy (the project name). */
29
+ brand: string;
30
+ /** Skip the composer's own `pm add` (the final create-cronus-app install covers it). */
31
+ skipInstall?: boolean;
32
+ /**
33
+ * Registry override. Defaults to `process.env.CRONUS_UI_REGISTRY` so dev/CI can
34
+ * point at the local workspace registry; falls through to the project config
35
+ * when unset.
36
+ */
37
+ registry?: string;
38
+ }
39
+ /** Structured outcome so the caller can log a page count or a graceful skip. */
40
+ export type ComposeTemplateResult = {
41
+ ok: true;
42
+ pageCount: number;
43
+ blockCount: number;
44
+ files: string[];
45
+ } | {
46
+ ok: false;
47
+ reason: string;
48
+ };
49
+ /**
50
+ * Compose a bundled app template into the freshly scaffolded project. Never
51
+ * throws: a compose failure (e.g. the pinned registry has no meta.json yet)
52
+ * returns `{ ok: false, reason }` so scaffolding still succeeds with the valid
53
+ * `default` base.
54
+ */
55
+ export declare function composeTemplate(options: ComposeTemplateOptions): Promise<ComposeTemplateResult>;
56
+ //# sourceMappingURL=compose.d.ts.map
@@ -0,0 +1,92 @@
1
+ /**
2
+ * Post-scaffold composition adapter.
3
+ *
4
+ * `store`/`landing` have no bundled template dir: create-cronus-app copies the
5
+ * `default` base, then this module calls `cronus-ui`'s `composeApp()` library
6
+ * entry to generate the pages + chrome from validated registry blocks.
7
+ *
8
+ * The import is dynamic so the rest of the CLI (and its tests) never pay for
9
+ * loading `cronus-ui/compose` — only composed templates reach here.
10
+ *
11
+ * Registry resolution (F1 honest cut — the chicken-and-egg):
12
+ * A freshly scaffolded app's `cronus-ui.json` pins the registry to the release
13
+ * remote (`v<version>/registry`), which only ships the `meta.json` sidecar
14
+ * compose needs from v0.4.0+. `composeApp` hard-requires that sidecar. So:
15
+ * 1. `CRONUS_UI_REGISTRY` (env) wins — the dev/CI escape hatch that points at
16
+ * the local workspace `registry/` dir (which has meta.json today).
17
+ * 2. Otherwise `composeApp` uses the project's own config registry (the
18
+ * release remote — correct once v0.4.0+ publishes meta.json).
19
+ * When the registry lacks meta.json, compose fails gracefully and the caller
20
+ * keeps the valid `default` scaffold with a clear "run compose later" hint.
21
+ */
22
+ import { existsSync, rmSync } from "node:fs";
23
+ import { join } from "node:path";
24
+ /**
25
+ * True when `file` is a generated page that owns the root URL "/".
26
+ *
27
+ * The renderer emits the home page as `app/(<chromeGroup>)/page.tsx` (route "/"
28
+ * inside a path-transparent route group), never a bare `app/page.tsx`. So a
29
+ * generated `app/(<group>)/page.tsx` is the compose-authored home page.
30
+ */
31
+ function isGeneratedRootPage(file) {
32
+ return /^app\/\([^/]+\)\/page\.tsx$/.test(file);
33
+ }
34
+ /**
35
+ * Remove the base template's `app/page.tsx` when compose generated its own home
36
+ * page. `create-cronus-app store|landing` scaffolds the `default` base (which
37
+ * ships `app/page.tsx`, the single-page dashboard starter) and THEN composes
38
+ * `app/(<group>)/page.tsx` for the manifest's "/" route. In the Next.js App
39
+ * Router a route group like `(site)` is path-transparent, so `app/page.tsx` and
40
+ * `app/(site)/page.tsx` both own "/". Left together, `next build` either errors
41
+ * ("two parallel pages resolve to /") or — on the Turbopack builder the template
42
+ * uses — silently serves the leftover base starter and drops the composed store
43
+ * home. Deleting the now-superseded base page leaves exactly one "/" page.
44
+ *
45
+ * The base `app/layout.tsx` (the required <html>/<body> root layout) is KEPT:
46
+ * Next allows a root `app/layout.tsx` plus nested route-group layouts
47
+ * (`app/(site)/layout.tsx`) to coexist, so only the PAGE collides.
48
+ */
49
+ function removeSupersededBasePage(targetDir, generatedFiles) {
50
+ // Only delete the base page when compose actually authored a "/" page to
51
+ // replace it — never orphan the app of a home route.
52
+ if (!generatedFiles.some(isGeneratedRootPage))
53
+ return;
54
+ const basePage = join(targetDir, "app", "page.tsx");
55
+ if (existsSync(basePage))
56
+ rmSync(basePage);
57
+ }
58
+ /**
59
+ * Compose a bundled app template into the freshly scaffolded project. Never
60
+ * throws: a compose failure (e.g. the pinned registry has no meta.json yet)
61
+ * returns `{ ok: false, reason }` so scaffolding still succeeds with the valid
62
+ * `default` base.
63
+ */
64
+ export async function composeTemplate(options) {
65
+ const registry = options.registry ?? process.env.CRONUS_UI_REGISTRY;
66
+ try {
67
+ // Dynamic import: only composed templates load the compose library, and a
68
+ // missing/unbuilt `cronus-ui/compose` degrades to a graceful skip rather than
69
+ // crashing the scaffolder.
70
+ const { composeApp } = await import("cronus-ui/compose");
71
+ const result = await composeApp({
72
+ targetDir: options.targetDir,
73
+ template: options.template,
74
+ choices: { brand: options.brand },
75
+ skipInstall: options.skipInstall ?? true,
76
+ ...(registry !== undefined ? { registry } : {}),
77
+ });
78
+ // Compose owns the "/" route via app/(<group>)/page.tsx; drop the base
79
+ // template's colliding app/page.tsx so exactly one page resolves to "/".
80
+ removeSupersededBasePage(options.targetDir, result.generatedFiles);
81
+ return {
82
+ ok: true,
83
+ pageCount: result.generatedFiles.filter((f) => f.endsWith("page.tsx")).length,
84
+ blockCount: result.installedBlocks.length,
85
+ files: result.generatedFiles,
86
+ };
87
+ }
88
+ catch (err) {
89
+ return { ok: false, reason: err.message };
90
+ }
91
+ }
92
+ //# sourceMappingURL=compose.js.map
@@ -0,0 +1,33 @@
1
+ #!/usr/bin/env node
2
+ import { type Assistant, type DoctrinePreset, type Skill } from "@cronus-ui/ai-kit";
3
+ import { type ModeName, type PackageManager, type TemplateName, type Theme } from "./utils.js";
4
+ export declare const HELP: string;
5
+ interface ParsedCli {
6
+ /** Project name from positional arg, or undefined to prompt. */
7
+ name?: string;
8
+ pm?: PackageManager;
9
+ install: boolean;
10
+ /** Template from `--template`, or undefined to prompt. */
11
+ template?: TemplateName;
12
+ /** Theme from `--theme`, or undefined to prompt. */
13
+ theme?: Theme;
14
+ /** Mode from `--mode`, or undefined to prompt. */
15
+ mode?: ModeName;
16
+ /** `--yes`: accept defaults, skip prompts (also implied when not a TTY). */
17
+ yes: boolean;
18
+ /** AI Kit: true (`--ai`) / false (`--no-ai`) / undefined (decide later). */
19
+ ai?: boolean;
20
+ /** Assistants to configure (defaults to all). */
21
+ assistants?: readonly Assistant[];
22
+ /** Doctrine preset (defaults to "standard"). */
23
+ preset?: DoctrinePreset;
24
+ /** Claude Code skills to include (defaults to all). */
25
+ skills?: readonly Skill[];
26
+ }
27
+ /**
28
+ * Parse argv into options. Throws a friendly Error on bad input (unknown flag,
29
+ * invalid `--pm`) so the caller can print and exit non-zero.
30
+ */
31
+ export declare function parseCli(argv: string[]): ParsedCli;
32
+ export {};
33
+ //# sourceMappingURL=index.d.ts.map
package/dist/index.js ADDED
@@ -0,0 +1,255 @@
1
+ #!/usr/bin/env node
2
+ import { existsSync, readdirSync, realpathSync } from "node:fs";
3
+ import { join } from "node:path";
4
+ import { argv } from "node:process";
5
+ import { fileURLToPath } from "node:url";
6
+ import { parseArgs } from "node:util";
7
+ import { ASSISTANTS, DEFAULT_PRESET, DOCTRINE_PRESETS, parseList, SKILLS, writeAiKit, writeDesignDocuments, } from "@cronus-ui/ai-kit";
8
+ import { composeTemplate } from "./compose.js";
9
+ import { runInstall, scaffold } from "./scaffold.js";
10
+ import { c, DEFAULT_MODE, DEFAULT_TEMPLATE, DEFAULT_THEME, dirNameFromProjectName, isComposedTemplate, isValidProjectName, log, MODES, PACKAGE_MANAGERS, promptConfirm, promptSelect, TEMPLATE_APPEARANCE, TEMPLATE_HINTS, TEMPLATES, THEME_HINTS, THEMES, } from "./utils.js";
11
+ import { CREATE_VERSION } from "./version.js";
12
+ export const HELP = `${c.bold("create-cronus-app")} — scaffold a Next.js + Cronus UI app.
13
+
14
+ ${c.bold("Usage")}
15
+ create-cronus-app [project-name] [options]
16
+
17
+ ${c.bold("Options")}
18
+ --template <name> Starter template (default: ${DEFAULT_TEMPLATE}).
19
+ Bundled: default, dashboard, marketing.
20
+ Composed: store, landing, saas, landing-* flavors, and Pro pack (mail, chat, finance).
21
+ --theme <${THEMES.join("|")}>
22
+ Theme preset to bake in (default: ${DEFAULT_THEME})
23
+ --mode <${MODES.join("|")}> Default color mode (default: ${DEFAULT_MODE})
24
+ --ai / --no-ai Include (or skip) the AI Kit: skills, rules & doctrine.
25
+ DESIGN.md still ships with --no-ai.
26
+ --assistants <${ASSISTANTS.join(",")}|all|none>
27
+ Which assistants to configure (default: all).
28
+ Codex CLI and Zed read the root AGENTS.md natively.
29
+ --preset <${DOCTRINE_PRESETS.join("|")}>
30
+ Which engineering doctrine to ship (default: standard)
31
+ --skills <name,...|all|none>
32
+ Which Claude Code skills to include (default: all)
33
+ --pm <bun|npm|pnpm|yarn> Package manager to install with (auto-detected otherwise)
34
+ --no-install Skip installing dependencies
35
+ -y, --yes Accept defaults; skip interactive prompts
36
+ -h, --help Show this help
37
+ -v, --version Show the version
38
+
39
+ ${c.bold("Examples")}
40
+ npx create-cronus-app my-app --template saas
41
+ npx create-cronus-app my-app --template landing-studio
42
+ npx create-cronus-app my-app --template mail
43
+ npx create-cronus-app my-app --template dashboard
44
+ npx create-cronus-app my-app --theme sunset --mode light
45
+ npx create-cronus-app my-app --ai --assistants claude,cursor --preset fintech
46
+ npx create-cronus-app my-app --no-ai --pm pnpm --yes
47
+ `;
48
+ /**
49
+ * Parse argv into options. Throws a friendly Error on bad input (unknown flag,
50
+ * invalid `--pm`) so the caller can print and exit non-zero.
51
+ */
52
+ export function parseCli(argv) {
53
+ // `node:util` parseArgs has no built-in `--no-foo` negation, so we declare an
54
+ // explicit `no-install` flag rather than rely on a `--no-install` convention.
55
+ const { values, positionals } = parseArgs({
56
+ args: argv,
57
+ allowPositionals: true,
58
+ options: {
59
+ template: { type: "string" },
60
+ theme: { type: "string" },
61
+ mode: { type: "string" },
62
+ ai: { type: "boolean", default: false },
63
+ "no-ai": { type: "boolean", default: false },
64
+ assistants: { type: "string" },
65
+ preset: { type: "string" },
66
+ skills: { type: "string" },
67
+ pm: { type: "string" },
68
+ "no-install": { type: "boolean", default: false },
69
+ yes: { type: "boolean", short: "y", default: false },
70
+ help: { type: "boolean", short: "h", default: false },
71
+ version: { type: "boolean", short: "v", default: false },
72
+ },
73
+ });
74
+ if (values.help)
75
+ return { install: true, yes: false, name: "--help" };
76
+ if (values.version)
77
+ return { install: true, yes: false, name: "--version" };
78
+ const pm = values.pm;
79
+ if (pm !== undefined && !PACKAGE_MANAGERS.includes(pm)) {
80
+ throw new Error(`Unknown --pm "${pm}". Use one of: ${PACKAGE_MANAGERS.join(", ")}.`);
81
+ }
82
+ const template = values.template;
83
+ if (template !== undefined && !TEMPLATES.includes(template)) {
84
+ throw new Error(`Unknown --template "${template}". Use one of: ${TEMPLATES.join(", ")}.`);
85
+ }
86
+ const theme = values.theme;
87
+ if (theme !== undefined && !THEMES.includes(theme)) {
88
+ throw new Error(`Unknown --theme "${theme}". Use one of: ${THEMES.join(", ")}.`);
89
+ }
90
+ const mode = values.mode;
91
+ if (mode !== undefined && !MODES.includes(mode)) {
92
+ throw new Error(`Unknown --mode "${mode}". Use one of: ${MODES.join(", ")}.`);
93
+ }
94
+ const preset = values.preset;
95
+ if (preset !== undefined && !DOCTRINE_PRESETS.includes(preset)) {
96
+ throw new Error(`Unknown --preset "${preset}". Use one of: ${DOCTRINE_PRESETS.join(", ")}.`);
97
+ }
98
+ // `parseList` validates each token and expands "all"/empty to the full set.
99
+ const assistants = parseList(values.assistants, ASSISTANTS, "assistant");
100
+ const skills = parseList(values.skills, SKILLS, "skill");
101
+ return {
102
+ name: positionals[0],
103
+ pm: pm,
104
+ install: !values["no-install"],
105
+ template: template,
106
+ theme: theme,
107
+ mode: mode,
108
+ yes: values.yes,
109
+ ai: values["no-ai"] ? false : values.ai ? true : undefined,
110
+ assistants,
111
+ preset: preset ?? DEFAULT_PRESET,
112
+ skills,
113
+ };
114
+ }
115
+ /** Prompt for a project name on the TTY; falls back to the default when piped. */
116
+ async function promptName(defaultName) {
117
+ if (!process.stdin.isTTY)
118
+ return defaultName;
119
+ const { createInterface } = await import("node:readline/promises");
120
+ const rl = createInterface({ input: process.stdin, output: process.stdout });
121
+ try {
122
+ const answer = (await rl.question(`${c.bold("Project name")} ${c.dim(`(${defaultName})`)} `)).trim();
123
+ return answer || defaultName;
124
+ }
125
+ finally {
126
+ rl.close();
127
+ }
128
+ }
129
+ /** Detect a package manager from the npm user-agent (set when run via npx/pnpm dlx). */
130
+ function detectPackageManager() {
131
+ const ua = process.env.npm_config_user_agent ?? "";
132
+ if (ua.startsWith("bun"))
133
+ return "bun";
134
+ if (ua.startsWith("pnpm"))
135
+ return "pnpm";
136
+ if (ua.startsWith("yarn"))
137
+ return "yarn";
138
+ return "npm";
139
+ }
140
+ async function main() {
141
+ const parsed = parseCli(process.argv.slice(2));
142
+ if (parsed.name === "--help") {
143
+ process.stdout.write(`${HELP}\n`);
144
+ return;
145
+ }
146
+ if (parsed.name === "--version") {
147
+ process.stdout.write(`${CREATE_VERSION}\n`);
148
+ return;
149
+ }
150
+ log.intro();
151
+ const name = parsed.name ?? (await promptName("my-cronus-app"));
152
+ if (!isValidProjectName(name)) {
153
+ log.error(`"${name}" is not a valid package name. Use lowercase letters, digits, "-", "_", "." ` +
154
+ `(optionally scoped, e.g. @scope/name).`);
155
+ process.exitCode = 1;
156
+ return;
157
+ }
158
+ const dirName = dirNameFromProjectName(name);
159
+ const targetDir = join(process.cwd(), dirName);
160
+ if (existsSync(targetDir) && readdirSync(targetDir).length > 0) {
161
+ log.error(`Directory "${dirName}" already exists and is not empty.`);
162
+ process.exitCode = 1;
163
+ return;
164
+ }
165
+ // Template + theme + mode: an explicit flag wins; otherwise prompt on a TTY
166
+ // (unless --yes), and fall back to the ship defaults when piped/CI.
167
+ const template = parsed.template ??
168
+ (parsed.yes
169
+ ? DEFAULT_TEMPLATE
170
+ : await promptSelect("Template", TEMPLATES, DEFAULT_TEMPLATE, TEMPLATE_HINTS));
171
+ const appearance = TEMPLATE_APPEARANCE[template];
172
+ const theme = parsed.theme ??
173
+ (parsed.yes
174
+ ? (appearance?.theme ?? DEFAULT_THEME)
175
+ : await promptSelect("Theme", THEMES, appearance?.theme ?? DEFAULT_THEME, THEME_HINTS));
176
+ const mode = parsed.mode ??
177
+ (parsed.yes
178
+ ? (appearance?.mode ?? DEFAULT_MODE)
179
+ : await promptSelect("Default mode", MODES, appearance?.mode ?? DEFAULT_MODE));
180
+ log.step(`Scaffolding into ${c.cyan(dirName)}…`);
181
+ const { fileCount } = scaffold({ targetDir, name, theme, mode, template });
182
+ log.ok(`Created ${fileCount} files (${c.cyan(template)} template, ${c.cyan(theme)} theme, ${mode} mode).`);
183
+ // Cronus Compose: store/landing scaffold the `default` base above, then generate
184
+ // their pages + chrome from validated registry blocks. The blocks pull npm deps
185
+ // (e.g. lucide-react) that the base package.json lacks, so let compose record +
186
+ // install them via `pm add` when installing is enabled; a following `--no-install`
187
+ // scaffold records nothing (matching `cronus-ui add --no-install`).
188
+ if (isComposedTemplate(template)) {
189
+ log.step(`Composing the ${c.cyan(template)} app from validated blocks…`);
190
+ const composed = await composeTemplate({
191
+ targetDir,
192
+ template,
193
+ brand: name,
194
+ skipInstall: !parsed.install,
195
+ });
196
+ if (composed.ok) {
197
+ log.ok(`Composed ${composed.pageCount} page(s) from ${composed.blockCount} validated block(s).`);
198
+ }
199
+ else {
200
+ log.warn(`Skipped composition (${composed.reason}). The ${c.cyan("default")} app was scaffolded; ` +
201
+ `run ${c.cyan(`npx cronus-ui compose ${template}`)} once the registry ships meta.json.`);
202
+ }
203
+ }
204
+ // AI Kit: skills, rules & doctrine for Claude Code / Cursor / Copilot.
205
+ const wantAi = parsed.ai ??
206
+ (parsed.yes
207
+ ? true
208
+ : await promptConfirm("Add the AI Kit (skills, rules & doctrine for AI assistants)?", true));
209
+ if (wantAi) {
210
+ const assistants = parsed.assistants ?? ASSISTANTS;
211
+ const preset = parsed.preset ?? DEFAULT_PRESET;
212
+ const skills = parsed.skills ?? SKILLS;
213
+ const { written } = writeAiKit({
214
+ targetDir,
215
+ name,
216
+ assistants,
217
+ preset,
218
+ skills,
219
+ theme,
220
+ });
221
+ log.ok(`AI Kit added — ${written.length} files for ${assistants.join(", ")} (${preset} doctrine).`);
222
+ }
223
+ else {
224
+ const { written } = writeDesignDocuments(targetDir, { theme });
225
+ if (written.length > 0) {
226
+ log.ok("DESIGN.md added (visual taste).");
227
+ }
228
+ }
229
+ const pm = parsed.pm ?? detectPackageManager();
230
+ if (parsed.install) {
231
+ log.step(`Installing dependencies with ${c.cyan(pm)}…`);
232
+ try {
233
+ runInstall(pm, targetDir);
234
+ log.ok("Dependencies installed.");
235
+ }
236
+ catch (err) {
237
+ log.warn(`Install failed (${err.message}). Install manually later.`);
238
+ }
239
+ }
240
+ log.outro(dirName, pm, parsed.install, template);
241
+ }
242
+ /** True when this module is the process entry point (not imported by a test). */
243
+ function isEntrypoint() {
244
+ const entry = argv[1];
245
+ if (!entry)
246
+ return false;
247
+ return existsSync(entry) && fileURLToPath(import.meta.url) === realpathSync(entry);
248
+ }
249
+ if (isEntrypoint()) {
250
+ main().catch((err) => {
251
+ log.error(err.message);
252
+ process.exitCode = 1;
253
+ });
254
+ }
255
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1,31 @@
1
+ import { type ModeName, type PackageManager, type TemplateName, type Theme } from "./utils.js";
2
+ /**
3
+ * Replace template tokens. `__APP_NAME__` is the package name; `__THEME__` /
4
+ * `__MODE__` are the chosen theme preset + color mode (baked into layout.tsx and
5
+ * cronus-ui.json). Theme/mode default to the ship defaults so existing callers
6
+ * (and tests) that pass only a name keep working.
7
+ */
8
+ export declare function applyTokens(content: string, name: string, theme?: Theme, mode?: ModeName): string;
9
+ export interface ScaffoldOptions {
10
+ /** Absolute path of the directory to create. */
11
+ targetDir: string;
12
+ /** Package name written into package.json / README (may be scoped). */
13
+ name: string;
14
+ /** Theme preset baked into the app. @default "aurora" */
15
+ theme?: Theme;
16
+ /** Default color mode baked into the app. @default "dark" */
17
+ mode?: ModeName;
18
+ /** Starter template to copy. @default "default" */
19
+ template?: TemplateName;
20
+ }
21
+ export interface ScaffoldResult {
22
+ fileCount: number;
23
+ }
24
+ /**
25
+ * Recursively copy the chosen template into `targetDir`, replacing tokens in
26
+ * text files and restoring stripped dotfile names. Returns the file count.
27
+ */
28
+ export declare function scaffold(options: ScaffoldOptions): ScaffoldResult;
29
+ /** Run the package manager's install in `cwd`. Throws if it exits non-zero. */
30
+ export declare function runInstall(pm: PackageManager, cwd: string): void;
31
+ //# sourceMappingURL=scaffold.d.ts.map