flarecms 0.1.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 (110) hide show
  1. package/README.md +73 -0
  2. package/dist/auth/index.js +40 -0
  3. package/dist/cli/commands.js +389 -0
  4. package/dist/cli/index.js +403 -0
  5. package/dist/cli/mcp.js +209 -0
  6. package/dist/db/index.js +164 -0
  7. package/dist/index.js +17626 -0
  8. package/package.json +105 -0
  9. package/scripts/fix-api-paths.mjs +32 -0
  10. package/scripts/fix-imports.mjs +38 -0
  11. package/scripts/prefix-css.mjs +45 -0
  12. package/src/api/lib/cache.ts +45 -0
  13. package/src/api/lib/response.ts +40 -0
  14. package/src/api/middlewares/auth.ts +186 -0
  15. package/src/api/middlewares/cors.ts +10 -0
  16. package/src/api/middlewares/rbac.ts +85 -0
  17. package/src/api/routes/auth.ts +377 -0
  18. package/src/api/routes/collections.ts +205 -0
  19. package/src/api/routes/content.ts +175 -0
  20. package/src/api/routes/device.ts +160 -0
  21. package/src/api/routes/magic.ts +150 -0
  22. package/src/api/routes/mcp.ts +273 -0
  23. package/src/api/routes/oauth.ts +160 -0
  24. package/src/api/routes/settings.ts +43 -0
  25. package/src/api/routes/setup.ts +307 -0
  26. package/src/api/routes/tokens.ts +80 -0
  27. package/src/api/schemas/auth.ts +15 -0
  28. package/src/api/schemas/index.ts +51 -0
  29. package/src/api/schemas/tokens.ts +24 -0
  30. package/src/auth/index.ts +28 -0
  31. package/src/cli/commands.ts +217 -0
  32. package/src/cli/index.ts +21 -0
  33. package/src/cli/mcp.ts +210 -0
  34. package/src/cli/tests/cli.test.ts +40 -0
  35. package/src/cli/tests/create.test.ts +87 -0
  36. package/src/client/FlareAdminRouter.tsx +47 -0
  37. package/src/client/app.tsx +175 -0
  38. package/src/client/components/app-sidebar.tsx +227 -0
  39. package/src/client/components/collection-modal.tsx +215 -0
  40. package/src/client/components/content-list.tsx +247 -0
  41. package/src/client/components/dynamic-form.tsx +190 -0
  42. package/src/client/components/field-modal.tsx +221 -0
  43. package/src/client/components/settings/api-token-section.tsx +400 -0
  44. package/src/client/components/settings/general-section.tsx +224 -0
  45. package/src/client/components/settings/security-section.tsx +154 -0
  46. package/src/client/components/settings/seo-section.tsx +200 -0
  47. package/src/client/components/settings/signup-section.tsx +257 -0
  48. package/src/client/components/ui/accordion.tsx +78 -0
  49. package/src/client/components/ui/avatar.tsx +107 -0
  50. package/src/client/components/ui/badge.tsx +52 -0
  51. package/src/client/components/ui/button.tsx +60 -0
  52. package/src/client/components/ui/card.tsx +103 -0
  53. package/src/client/components/ui/checkbox.tsx +27 -0
  54. package/src/client/components/ui/collapsible.tsx +19 -0
  55. package/src/client/components/ui/dialog.tsx +162 -0
  56. package/src/client/components/ui/icon-picker.tsx +485 -0
  57. package/src/client/components/ui/icons-data.ts +8476 -0
  58. package/src/client/components/ui/input.tsx +20 -0
  59. package/src/client/components/ui/label.tsx +20 -0
  60. package/src/client/components/ui/popover.tsx +91 -0
  61. package/src/client/components/ui/select.tsx +204 -0
  62. package/src/client/components/ui/separator.tsx +23 -0
  63. package/src/client/components/ui/sheet.tsx +141 -0
  64. package/src/client/components/ui/sidebar.tsx +722 -0
  65. package/src/client/components/ui/skeleton.tsx +13 -0
  66. package/src/client/components/ui/sonner.tsx +47 -0
  67. package/src/client/components/ui/switch.tsx +30 -0
  68. package/src/client/components/ui/table.tsx +116 -0
  69. package/src/client/components/ui/tabs.tsx +80 -0
  70. package/src/client/components/ui/textarea.tsx +18 -0
  71. package/src/client/components/ui/tooltip.tsx +68 -0
  72. package/src/client/hooks/use-mobile.ts +19 -0
  73. package/src/client/index.css +149 -0
  74. package/src/client/index.ts +7 -0
  75. package/src/client/layouts/admin-layout.tsx +93 -0
  76. package/src/client/layouts/settings-layout.tsx +104 -0
  77. package/src/client/lib/api.ts +72 -0
  78. package/src/client/lib/utils.ts +6 -0
  79. package/src/client/main.tsx +10 -0
  80. package/src/client/pages/collection-detail.tsx +634 -0
  81. package/src/client/pages/collections.tsx +180 -0
  82. package/src/client/pages/dashboard.tsx +133 -0
  83. package/src/client/pages/device.tsx +66 -0
  84. package/src/client/pages/document-detail-page.tsx +139 -0
  85. package/src/client/pages/documents-page.tsx +103 -0
  86. package/src/client/pages/login.tsx +345 -0
  87. package/src/client/pages/settings.tsx +65 -0
  88. package/src/client/pages/setup.tsx +129 -0
  89. package/src/client/pages/signup.tsx +188 -0
  90. package/src/client/store/auth.ts +30 -0
  91. package/src/client/store/collections.ts +13 -0
  92. package/src/client/store/config.ts +12 -0
  93. package/src/client/store/fetcher.ts +30 -0
  94. package/src/client/store/router.ts +95 -0
  95. package/src/client/store/schema.ts +39 -0
  96. package/src/client/store/settings.ts +31 -0
  97. package/src/client/types.ts +34 -0
  98. package/src/db/dynamic.ts +70 -0
  99. package/src/db/index.ts +16 -0
  100. package/src/db/migrations/001_initial_schema.ts +57 -0
  101. package/src/db/migrations/002_auth_tables.ts +84 -0
  102. package/src/db/migrator.ts +61 -0
  103. package/src/db/schema.ts +142 -0
  104. package/src/index.ts +12 -0
  105. package/src/server/index.ts +66 -0
  106. package/src/types.ts +20 -0
  107. package/style.css.d.ts +8 -0
  108. package/tests/css.test.ts +21 -0
  109. package/tests/modular.test.ts +29 -0
  110. package/tsconfig.json +10 -0
package/README.md ADDED
@@ -0,0 +1,73 @@
1
+ # FlareCMS: The Lean, Edge-Native CMS for the AI Era
2
+
3
+ FlareCMS is a high-performance, minimalist, and developer-centric Content Management System built for the modern web. Engineered for speed and stability, it leverages the power of Bun, Hono, and Cloudflare D1 to deliver an unparalleled editing experience directly at the edge.
4
+
5
+ ## 🚀 Vision: Design-First & AI-Ready
6
+
7
+ Unlike traditional bloated CMS architectures, FlareCMS follows two core principles:
8
+
9
+ 1. **Zen Minimalism**: A purely "Flat Design" interface. No shadows, no entry animations, and no unnecessary transitions. We value instant response times and visual clarity over decorative UI elements.
10
+ 2. **AI-Native Infrastructure**: Built to be managed by human developers and AI agents alike. Every interface is designed to be lean and high-contrast, facilitating seamless navigation for both eyes and algorithms.
11
+
12
+ ## 🛠️ Technology Stack
13
+
14
+ - **Runtime**: [Bun](https://bun.sh/)
15
+ - **Server Framework**: [Hono](https://hono.dev/)
16
+ - **Database**: [Cloudflare D1](https://developers.cloudflare.com/d1/) (Edge SQL)
17
+ - **Frontend**: React + Vite + Tailwind CSS v4
18
+ - **State Management**: [Nanostores](https://github.com/nanostores/nanostores)
19
+
20
+ ## 📦 Getting Started
21
+
22
+ ### Initialize a New Project
23
+
24
+ The easiest way to start a new FlareCMS project is by using our interactive CLI:
25
+
26
+ ```bash
27
+ bunx flarecms create
28
+ ```
29
+
30
+ This will guide you through:
31
+ - Choosing a project name
32
+ - Selecting a template (e.g., Unified Starter)
33
+ - Installing dependencies automatically
34
+
35
+ ### Development (Monorepo)
36
+
37
+ ### Prerequisites
38
+
39
+ - [Bun](https://bun.sh/) installed on your machine.
40
+ - Cloudflare Wrangler CLI (for D1 interactions).
41
+
42
+ ### Installation
43
+
44
+ ```bash
45
+ bun install
46
+ ```
47
+
48
+ ### Development
49
+
50
+ To start the admin dashboard and the API concurrently:
51
+
52
+ ```bash
53
+ bun run dev
54
+ ```
55
+
56
+ ## 🗺️ Technical Feature Roadmap
57
+
58
+ Inspired by modern CMS best practices and enterprise-grade research, FlareCMS is evolving to support:
59
+
60
+ - **[x] WebAuthn / Passkeys**: Biometric authentication implemented as a stable, passwordless login method.
61
+ - **[x] Automated Deployment Pipeline**: Integrated custom build & deploy orchestration for Cloudflare Workers.
62
+ - **[x] Self-Healing Database**: Automatic runtime migrations and table initialization.
63
+ - **[x] Production Routing**: Native SPA fallback handling for seamless page refreshes at the edge.
64
+ - **[ ] MCP Server Integration**: Full support for the Model Context Protocol to allow AI Agents (Claude, ChatGPT) to manage content, schemas, and mediaLibrary programmatically.
65
+ - **[ ] Portable Text Protocol**: Transitioning from HTML-based storage to structured JSON (Portable Text) for presentation-agnostic content delivery.
66
+ - **[ ] Agentic Developer Skills**: Native skills and instructions to turn FlareCMS into an "autonomous" CMS that can build its own plugins via AI.
67
+ - **[ ] Edge Worker Sandboxing**: A plugin system based on isolated Cloudflare Workers for secure, third-party extensibility.
68
+
69
+ ---
70
+
71
+ ## License
72
+
73
+ MIT © fhorray
@@ -0,0 +1,40 @@
1
+ // @bun
2
+ var __defProp = Object.defineProperty;
3
+ var __returnValue = (v) => v;
4
+ function __exportSetter(name, newValue) {
5
+ this[name] = __returnValue.bind(null, newValue);
6
+ }
7
+ var __export = (target, all) => {
8
+ for (var name in all)
9
+ __defProp(target, name, {
10
+ get: all[name],
11
+ enumerable: true,
12
+ configurable: true,
13
+ set: __exportSetter.bind(all, name)
14
+ });
15
+ };
16
+ var __esm = (fn, res) => () => (fn && (res = fn(fn = 0)), res);
17
+
18
+ // src/auth/index.ts
19
+ import { encodeHexLowerCase } from "@oslojs/encoding";
20
+ function generateSessionToken() {
21
+ const bytes = new Uint8Array(20);
22
+ crypto.getRandomValues(bytes);
23
+ return encodeHexLowerCase(bytes);
24
+ }
25
+ async function hashPassword(password) {
26
+ const encoder = new TextEncoder;
27
+ const data = encoder.encode(password);
28
+ const hashBuffer = await crypto.subtle.digest("SHA-256", data);
29
+ const hashArray = Array.from(new Uint8Array(hashBuffer));
30
+ return hashArray.map((b) => b.toString(16).padStart(2, "0")).join("");
31
+ }
32
+ async function verifyPassword(password, hash) {
33
+ const newHash = await hashPassword(password);
34
+ return newHash === hash;
35
+ }
36
+ export {
37
+ verifyPassword,
38
+ hashPassword,
39
+ generateSessionToken
40
+ };
@@ -0,0 +1,389 @@
1
+ // @bun
2
+ var __defProp = Object.defineProperty;
3
+ var __returnValue = (v) => v;
4
+ function __exportSetter(name, newValue) {
5
+ this[name] = __returnValue.bind(null, newValue);
6
+ }
7
+ var __export = (target, all) => {
8
+ for (var name in all)
9
+ __defProp(target, name, {
10
+ get: all[name],
11
+ enumerable: true,
12
+ configurable: true,
13
+ set: __exportSetter.bind(all, name)
14
+ });
15
+ };
16
+ var __esm = (fn, res) => () => (fn && (res = fn(fn = 0)), res);
17
+
18
+ // src/cli/mcp.ts
19
+ var TOOLS = [
20
+ {
21
+ name: "list_collections",
22
+ description: "Fetches all defined content schema collections available in FlareCMS.",
23
+ inputSchema: { type: "object", properties: {} }
24
+ },
25
+ {
26
+ name: "get_collection_schema",
27
+ description: "Fetches the full field structure and metadata of a specific FlareCMS collection.",
28
+ inputSchema: {
29
+ type: "object",
30
+ properties: {
31
+ collection: { type: "string", description: "The slug of the collection to inspect" }
32
+ },
33
+ required: ["collection"]
34
+ }
35
+ },
36
+ {
37
+ name: "read_content",
38
+ description: "Reads the paginated content of a specific FlareCMS collection dynamically.",
39
+ inputSchema: {
40
+ type: "object",
41
+ properties: {
42
+ collection: { type: "string", description: "The slug of the collection to read" },
43
+ limit: { type: "number", description: "Number of records to fetch (default 10)" }
44
+ },
45
+ required: ["collection"]
46
+ }
47
+ },
48
+ {
49
+ name: "create_document",
50
+ description: "Creates a new document in a specified collection.",
51
+ inputSchema: {
52
+ type: "object",
53
+ properties: {
54
+ collection: { type: "string", description: "The collection slug" },
55
+ data: {
56
+ type: "object",
57
+ description: "The document data (e.g. { title: 'Hello', status: 'published' })"
58
+ }
59
+ },
60
+ required: ["collection", "data"]
61
+ }
62
+ },
63
+ {
64
+ name: "update_document",
65
+ description: "Updates an existing document in a specified collection.",
66
+ inputSchema: {
67
+ type: "object",
68
+ properties: {
69
+ collection: { type: "string", description: "The collection slug" },
70
+ id: { type: "string", description: "The document ID to update" },
71
+ data: {
72
+ type: "object",
73
+ description: "The data fields to update"
74
+ }
75
+ },
76
+ required: ["collection", "id", "data"]
77
+ }
78
+ },
79
+ {
80
+ name: "create_collection",
81
+ description: "Creates a new content collection and its physical table.",
82
+ inputSchema: {
83
+ type: "object",
84
+ properties: {
85
+ slug: { type: "string", description: "Unique URL slug for the collection" },
86
+ label: { type: "string", description: "Display label" },
87
+ labelSingular: { type: "string", description: "Singular display label" },
88
+ description: { type: "string", description: "Optional description" },
89
+ isPublic: { type: "boolean", description: "Whether it is publicly readable" }
90
+ },
91
+ required: ["slug", "label"]
92
+ }
93
+ },
94
+ {
95
+ name: "update_collection",
96
+ description: "Updates collection metadata.",
97
+ inputSchema: {
98
+ type: "object",
99
+ properties: {
100
+ id: { type: "string", description: "The collection ID (ULID)" },
101
+ data: { type: "object", description: "Metadata fields to update" }
102
+ },
103
+ required: ["id", "data"]
104
+ }
105
+ },
106
+ {
107
+ name: "add_field",
108
+ description: "Adds a new field to an existing collection.",
109
+ inputSchema: {
110
+ type: "object",
111
+ properties: {
112
+ collection_id: { type: "string", description: "The collection ID (ULID)" },
113
+ field: {
114
+ type: "object",
115
+ properties: {
116
+ slug: { type: "string" },
117
+ label: { type: "string" },
118
+ type: { type: "string", enum: ["text", "number", "boolean", "date", "richtext"] },
119
+ required: { type: "boolean" }
120
+ },
121
+ required: ["slug", "label", "type"]
122
+ }
123
+ },
124
+ required: ["collection_id", "field"]
125
+ }
126
+ }
127
+ ];
128
+ async function runMcpBridge(options) {
129
+ const { url, token } = options;
130
+ const executeUrl = `${url.replace(/\/$/, "")}/api/mcp/execute`;
131
+ async function callFlareCMS(tool, args) {
132
+ if (!token) {
133
+ throw new Error("No API Token provided. Use --token or FLARE_API_TOKEN environment variable.");
134
+ }
135
+ const response = await fetch(executeUrl, {
136
+ method: "POST",
137
+ headers: {
138
+ "Content-Type": "application/json",
139
+ Authorization: `Bearer ${token}`
140
+ },
141
+ body: JSON.stringify({ tool, arguments: args })
142
+ });
143
+ if (!response.ok) {
144
+ const errorText = await response.text();
145
+ throw new Error(`FlareCMS API Error (${response.status}): ${errorText}`);
146
+ }
147
+ return await response.json();
148
+ }
149
+ async function handleRequest(request) {
150
+ const { method, params } = request;
151
+ switch (method) {
152
+ case "initialize":
153
+ return {
154
+ protocolVersion: "2024-11-05",
155
+ capabilities: { tools: {} },
156
+ serverInfo: { name: "@flarecms/cli", version: "0.1.0" }
157
+ };
158
+ case "notifications/initialized":
159
+ return null;
160
+ case "tools/list":
161
+ return { tools: TOOLS };
162
+ case "tools/call":
163
+ try {
164
+ return await callFlareCMS(params.name, params.arguments);
165
+ } catch (error) {
166
+ return {
167
+ content: [{ type: "text", text: `Error executing tool '${params.name}': ${error.message}` }],
168
+ isError: true
169
+ };
170
+ }
171
+ default:
172
+ return { error: { code: -32601, message: `Method not found: ${method}` } };
173
+ }
174
+ }
175
+ const decoder = new TextDecoder;
176
+ let buffer = "";
177
+ for await (const chunk of Bun.stdin.stream()) {
178
+ buffer += decoder.decode(chunk);
179
+ let lines = buffer.split(`
180
+ `);
181
+ buffer = lines.pop() || "";
182
+ for (const line of lines) {
183
+ if (!line.trim())
184
+ continue;
185
+ try {
186
+ const request = JSON.parse(line);
187
+ const result = await handleRequest(request);
188
+ if (result === null)
189
+ continue;
190
+ process.stdout.write(JSON.stringify({
191
+ jsonrpc: "2.0",
192
+ id: request.id,
193
+ result: result.error ? undefined : result,
194
+ error: result.error
195
+ }) + `
196
+ `);
197
+ } catch (e) {
198
+ process.stdout.write(JSON.stringify({
199
+ jsonrpc: "2.0",
200
+ error: { code: -32700, message: "Parse error" }
201
+ }) + `
202
+ `);
203
+ }
204
+ }
205
+ }
206
+ }
207
+
208
+ // src/cli/commands.ts
209
+ import { exec } from "child_process";
210
+ import { promisify } from "util";
211
+ import * as p from "@clack/prompts";
212
+ import pc from "picocolors";
213
+ import { downloadTemplate } from "giget";
214
+ import { existsSync, readFileSync, writeFileSync, cpSync, mkdirSync } from "fs";
215
+ import { resolve } from "path";
216
+ import { fileURLToPath } from "url";
217
+ var execAsync = promisify(exec);
218
+ var PROJECT_NAME_PATTERN = /^[a-z0-9-]+$/;
219
+ var TEMPLATES = {
220
+ starter: {
221
+ name: "Starter (Hono + React)",
222
+ description: "Unified single-process FlareCMS starter for Cloudflare Workers",
223
+ dir: "templates/starter"
224
+ },
225
+ blog: {
226
+ name: "Blog (Coming Soon)",
227
+ description: "A pre-configured blog template",
228
+ dir: "templates/blog"
229
+ },
230
+ nextjs: {
231
+ name: "Next.js (App Router)",
232
+ description: "Modern Next.js template with FlareCMS + Hono API",
233
+ dir: "templates/nextjs"
234
+ }
235
+ };
236
+ async function createProjectCommand() {
237
+ console.clear();
238
+ console.log(`
239
+ ${pc.bold(pc.yellow("\u2014 F L A R E C M S \u2014"))}
240
+ `);
241
+ p.intro("Create a new FlareCMS project");
242
+ const projectName = await p.text({
243
+ message: "Project name?",
244
+ placeholder: "my-flare-site",
245
+ defaultValue: "my-flare-site",
246
+ validate: (value) => {
247
+ if (!value)
248
+ return "Project name is required";
249
+ if (!PROJECT_NAME_PATTERN.test(value))
250
+ return "Project name can only contain lowercase letters, numbers, and hyphens";
251
+ return;
252
+ }
253
+ });
254
+ if (p.isCancel(projectName)) {
255
+ p.cancel("Operation cancelled.");
256
+ process.exit(0);
257
+ }
258
+ const projectDir = resolve(process.cwd(), projectName);
259
+ if (existsSync(projectDir)) {
260
+ const overwrite = await p.confirm({
261
+ message: `Directory ${projectName} already exists. Overwrite?`,
262
+ initialValue: false
263
+ });
264
+ if (p.isCancel(overwrite) || !overwrite) {
265
+ p.cancel("Operation cancelled.");
266
+ process.exit(0);
267
+ }
268
+ }
269
+ const templateKey = await p.select({
270
+ message: "Which template would you like to use?",
271
+ options: Object.entries(TEMPLATES).map(([key, t]) => ({
272
+ value: key,
273
+ label: t.name,
274
+ hint: t.description
275
+ })),
276
+ initialValue: "starter"
277
+ });
278
+ if (p.isCancel(templateKey)) {
279
+ p.cancel("Operation cancelled.");
280
+ process.exit(0);
281
+ }
282
+ const shouldInstall = await p.confirm({
283
+ message: "Install dependencies with bun?",
284
+ initialValue: true
285
+ });
286
+ if (p.isCancel(shouldInstall)) {
287
+ p.cancel("Operation cancelled.");
288
+ process.exit(0);
289
+ }
290
+ const s = p.spinner();
291
+ s.start("Creating project...");
292
+ try {
293
+ if (templateKey === "nextjs") {
294
+ s.stop("Starting Next.js setup...");
295
+ const nextVersion = "15.1.0";
296
+ p.log.info(`${pc.cyan("Running:")} bun create next-app@${nextVersion} ${projectName} --typescript --tailwind --eslint --app --src-dir false --import-alias "@/*" --use-bun`);
297
+ try {
298
+ await execAsync(`bun create next-app@${nextVersion} ${projectName} --typescript --tailwind --eslint --app --src-dir false --import-alias "@/*" --use-bun --yes`, {
299
+ env: { ...process.env, NEXT_TELEMETRY_DISABLED: "1" }
300
+ });
301
+ } catch (err) {
302
+ p.log.warn("create-next-app failed or was already present. Continuing with injection...");
303
+ }
304
+ s.start("Injecting FlareCMS configuration...");
305
+ }
306
+ const template = TEMPLATES[templateKey];
307
+ const currentFilePath = fileURLToPath(import.meta.url);
308
+ const cliDir = resolve(currentFilePath, "..");
309
+ const localTemplatesRoot = resolve(cliDir, "..", "..", "..", "..", "templates");
310
+ const localTemplatePath = resolve(localTemplatesRoot, template.dir.split("/").pop() || "");
311
+ if (existsSync(localTemplatePath)) {
312
+ if (!existsSync(projectDir)) {
313
+ mkdirSync(projectDir, { recursive: true });
314
+ }
315
+ cpSync(localTemplatePath, projectDir, { recursive: true });
316
+ } else if (templateKey !== "nextjs") {
317
+ const remoteSource = `github:fhorray/flarecms/${template.dir}`;
318
+ await downloadTemplate(remoteSource, {
319
+ dir: projectDir,
320
+ force: true
321
+ });
322
+ }
323
+ const pkgPath = resolve(projectDir, "package.json");
324
+ if (existsSync(pkgPath)) {
325
+ const pkg = JSON.parse(readFileSync(pkgPath, "utf-8"));
326
+ pkg.name = projectName;
327
+ pkg.version = "0.1.0";
328
+ pkg.dependencies = pkg.dependencies || {};
329
+ pkg.dependencies["flarecms"] = "latest";
330
+ pkg.dependencies["hono"] = "latest";
331
+ writeFileSync(pkgPath, JSON.stringify(pkg, null, 2));
332
+ }
333
+ const wranglerPath = resolve(projectDir, "wrangler.jsonc");
334
+ if (existsSync(wranglerPath)) {
335
+ let wranglerContent = readFileSync(wranglerPath, "utf-8");
336
+ wranglerContent = wranglerContent.replace(/"name":\s*".*"/, `"name": "${projectName}"`);
337
+ wranglerContent = wranglerContent.replace(/"database_name":\s*".*"/, `"database_name": "${projectName}-db"`);
338
+ writeFileSync(wranglerPath, wranglerContent);
339
+ }
340
+ s.stop("Project created!");
341
+ if (shouldInstall) {
342
+ s.start(`Installing dependencies with ${pc.cyan("bun")}...`);
343
+ try {
344
+ await execAsync("bun install", { cwd: projectDir });
345
+ s.stop("Dependencies installed!");
346
+ s.start("Generating TypeScript bindings...");
347
+ try {
348
+ await execAsync("bun wrangler types", { cwd: projectDir });
349
+ s.stop("TypeScript bindings generated!");
350
+ } catch (err) {
351
+ s.stop("Failed to generate bindings (this is normal if wrangler.jsonc is incomplete)");
352
+ }
353
+ } catch (err) {
354
+ s.stop("Failed to install dependencies");
355
+ p.log.warn(`Run ${pc.cyan(`cd ${projectName} && bun install && bun cf-typegen`)} manually`);
356
+ }
357
+ }
358
+ const steps = [`cd ${projectName}`, "bun run dev"];
359
+ p.note(steps.join(`
360
+ `), "Next steps");
361
+ p.outro(`${pc.green("Done!")} Your FlareCMS project is ready at ${pc.cyan(projectName)}`);
362
+ } catch (error) {
363
+ s.stop("Failed to create project");
364
+ p.log.error(error instanceof Error ? error.message : String(error));
365
+ process.exit(1);
366
+ }
367
+ }
368
+ async function mcpCommand(args) {
369
+ let url = process.env.FLARE_API_URL || "http://localhost:8787";
370
+ let token = process.env.FLARE_API_TOKEN;
371
+ for (let i = 0;i < args.length; i++) {
372
+ if (args[i] === "--url" && i + 1 < args.length) {
373
+ url = args[i + 1];
374
+ i++;
375
+ } else if (args[i] === "--token" && i + 1 < args.length) {
376
+ token = args[i + 1];
377
+ i++;
378
+ }
379
+ }
380
+ if (!url) {
381
+ console.error(pc.red("Error: --url is required or must be set via FLARE_API_URL"));
382
+ process.exit(1);
383
+ }
384
+ await runMcpBridge({ url, token });
385
+ }
386
+ export {
387
+ mcpCommand,
388
+ createProjectCommand
389
+ };