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.
- package/README.md +73 -0
- package/dist/auth/index.js +40 -0
- package/dist/cli/commands.js +389 -0
- package/dist/cli/index.js +403 -0
- package/dist/cli/mcp.js +209 -0
- package/dist/db/index.js +164 -0
- package/dist/index.js +17626 -0
- package/package.json +105 -0
- package/scripts/fix-api-paths.mjs +32 -0
- package/scripts/fix-imports.mjs +38 -0
- package/scripts/prefix-css.mjs +45 -0
- package/src/api/lib/cache.ts +45 -0
- package/src/api/lib/response.ts +40 -0
- package/src/api/middlewares/auth.ts +186 -0
- package/src/api/middlewares/cors.ts +10 -0
- package/src/api/middlewares/rbac.ts +85 -0
- package/src/api/routes/auth.ts +377 -0
- package/src/api/routes/collections.ts +205 -0
- package/src/api/routes/content.ts +175 -0
- package/src/api/routes/device.ts +160 -0
- package/src/api/routes/magic.ts +150 -0
- package/src/api/routes/mcp.ts +273 -0
- package/src/api/routes/oauth.ts +160 -0
- package/src/api/routes/settings.ts +43 -0
- package/src/api/routes/setup.ts +307 -0
- package/src/api/routes/tokens.ts +80 -0
- package/src/api/schemas/auth.ts +15 -0
- package/src/api/schemas/index.ts +51 -0
- package/src/api/schemas/tokens.ts +24 -0
- package/src/auth/index.ts +28 -0
- package/src/cli/commands.ts +217 -0
- package/src/cli/index.ts +21 -0
- package/src/cli/mcp.ts +210 -0
- package/src/cli/tests/cli.test.ts +40 -0
- package/src/cli/tests/create.test.ts +87 -0
- package/src/client/FlareAdminRouter.tsx +47 -0
- package/src/client/app.tsx +175 -0
- package/src/client/components/app-sidebar.tsx +227 -0
- package/src/client/components/collection-modal.tsx +215 -0
- package/src/client/components/content-list.tsx +247 -0
- package/src/client/components/dynamic-form.tsx +190 -0
- package/src/client/components/field-modal.tsx +221 -0
- package/src/client/components/settings/api-token-section.tsx +400 -0
- package/src/client/components/settings/general-section.tsx +224 -0
- package/src/client/components/settings/security-section.tsx +154 -0
- package/src/client/components/settings/seo-section.tsx +200 -0
- package/src/client/components/settings/signup-section.tsx +257 -0
- package/src/client/components/ui/accordion.tsx +78 -0
- package/src/client/components/ui/avatar.tsx +107 -0
- package/src/client/components/ui/badge.tsx +52 -0
- package/src/client/components/ui/button.tsx +60 -0
- package/src/client/components/ui/card.tsx +103 -0
- package/src/client/components/ui/checkbox.tsx +27 -0
- package/src/client/components/ui/collapsible.tsx +19 -0
- package/src/client/components/ui/dialog.tsx +162 -0
- package/src/client/components/ui/icon-picker.tsx +485 -0
- package/src/client/components/ui/icons-data.ts +8476 -0
- package/src/client/components/ui/input.tsx +20 -0
- package/src/client/components/ui/label.tsx +20 -0
- package/src/client/components/ui/popover.tsx +91 -0
- package/src/client/components/ui/select.tsx +204 -0
- package/src/client/components/ui/separator.tsx +23 -0
- package/src/client/components/ui/sheet.tsx +141 -0
- package/src/client/components/ui/sidebar.tsx +722 -0
- package/src/client/components/ui/skeleton.tsx +13 -0
- package/src/client/components/ui/sonner.tsx +47 -0
- package/src/client/components/ui/switch.tsx +30 -0
- package/src/client/components/ui/table.tsx +116 -0
- package/src/client/components/ui/tabs.tsx +80 -0
- package/src/client/components/ui/textarea.tsx +18 -0
- package/src/client/components/ui/tooltip.tsx +68 -0
- package/src/client/hooks/use-mobile.ts +19 -0
- package/src/client/index.css +149 -0
- package/src/client/index.ts +7 -0
- package/src/client/layouts/admin-layout.tsx +93 -0
- package/src/client/layouts/settings-layout.tsx +104 -0
- package/src/client/lib/api.ts +72 -0
- package/src/client/lib/utils.ts +6 -0
- package/src/client/main.tsx +10 -0
- package/src/client/pages/collection-detail.tsx +634 -0
- package/src/client/pages/collections.tsx +180 -0
- package/src/client/pages/dashboard.tsx +133 -0
- package/src/client/pages/device.tsx +66 -0
- package/src/client/pages/document-detail-page.tsx +139 -0
- package/src/client/pages/documents-page.tsx +103 -0
- package/src/client/pages/login.tsx +345 -0
- package/src/client/pages/settings.tsx +65 -0
- package/src/client/pages/setup.tsx +129 -0
- package/src/client/pages/signup.tsx +188 -0
- package/src/client/store/auth.ts +30 -0
- package/src/client/store/collections.ts +13 -0
- package/src/client/store/config.ts +12 -0
- package/src/client/store/fetcher.ts +30 -0
- package/src/client/store/router.ts +95 -0
- package/src/client/store/schema.ts +39 -0
- package/src/client/store/settings.ts +31 -0
- package/src/client/types.ts +34 -0
- package/src/db/dynamic.ts +70 -0
- package/src/db/index.ts +16 -0
- package/src/db/migrations/001_initial_schema.ts +57 -0
- package/src/db/migrations/002_auth_tables.ts +84 -0
- package/src/db/migrator.ts +61 -0
- package/src/db/schema.ts +142 -0
- package/src/index.ts +12 -0
- package/src/server/index.ts +66 -0
- package/src/types.ts +20 -0
- package/style.css.d.ts +8 -0
- package/tests/css.test.ts +21 -0
- package/tests/modular.test.ts +29 -0
- package/tsconfig.json +10 -0
|
@@ -0,0 +1,403 @@
|
|
|
1
|
+
#!/usr/bin/env bun
|
|
2
|
+
// @bun
|
|
3
|
+
var __defProp = Object.defineProperty;
|
|
4
|
+
var __returnValue = (v) => v;
|
|
5
|
+
function __exportSetter(name, newValue) {
|
|
6
|
+
this[name] = __returnValue.bind(null, newValue);
|
|
7
|
+
}
|
|
8
|
+
var __export = (target, all) => {
|
|
9
|
+
for (var name in all)
|
|
10
|
+
__defProp(target, name, {
|
|
11
|
+
get: all[name],
|
|
12
|
+
enumerable: true,
|
|
13
|
+
configurable: true,
|
|
14
|
+
set: __exportSetter.bind(all, name)
|
|
15
|
+
});
|
|
16
|
+
};
|
|
17
|
+
var __esm = (fn, res) => () => (fn && (res = fn(fn = 0)), res);
|
|
18
|
+
|
|
19
|
+
// src/cli/mcp.ts
|
|
20
|
+
var TOOLS = [
|
|
21
|
+
{
|
|
22
|
+
name: "list_collections",
|
|
23
|
+
description: "Fetches all defined content schema collections available in FlareCMS.",
|
|
24
|
+
inputSchema: { type: "object", properties: {} }
|
|
25
|
+
},
|
|
26
|
+
{
|
|
27
|
+
name: "get_collection_schema",
|
|
28
|
+
description: "Fetches the full field structure and metadata of a specific FlareCMS collection.",
|
|
29
|
+
inputSchema: {
|
|
30
|
+
type: "object",
|
|
31
|
+
properties: {
|
|
32
|
+
collection: { type: "string", description: "The slug of the collection to inspect" }
|
|
33
|
+
},
|
|
34
|
+
required: ["collection"]
|
|
35
|
+
}
|
|
36
|
+
},
|
|
37
|
+
{
|
|
38
|
+
name: "read_content",
|
|
39
|
+
description: "Reads the paginated content of a specific FlareCMS collection dynamically.",
|
|
40
|
+
inputSchema: {
|
|
41
|
+
type: "object",
|
|
42
|
+
properties: {
|
|
43
|
+
collection: { type: "string", description: "The slug of the collection to read" },
|
|
44
|
+
limit: { type: "number", description: "Number of records to fetch (default 10)" }
|
|
45
|
+
},
|
|
46
|
+
required: ["collection"]
|
|
47
|
+
}
|
|
48
|
+
},
|
|
49
|
+
{
|
|
50
|
+
name: "create_document",
|
|
51
|
+
description: "Creates a new document in a specified collection.",
|
|
52
|
+
inputSchema: {
|
|
53
|
+
type: "object",
|
|
54
|
+
properties: {
|
|
55
|
+
collection: { type: "string", description: "The collection slug" },
|
|
56
|
+
data: {
|
|
57
|
+
type: "object",
|
|
58
|
+
description: "The document data (e.g. { title: 'Hello', status: 'published' })"
|
|
59
|
+
}
|
|
60
|
+
},
|
|
61
|
+
required: ["collection", "data"]
|
|
62
|
+
}
|
|
63
|
+
},
|
|
64
|
+
{
|
|
65
|
+
name: "update_document",
|
|
66
|
+
description: "Updates an existing document in a specified collection.",
|
|
67
|
+
inputSchema: {
|
|
68
|
+
type: "object",
|
|
69
|
+
properties: {
|
|
70
|
+
collection: { type: "string", description: "The collection slug" },
|
|
71
|
+
id: { type: "string", description: "The document ID to update" },
|
|
72
|
+
data: {
|
|
73
|
+
type: "object",
|
|
74
|
+
description: "The data fields to update"
|
|
75
|
+
}
|
|
76
|
+
},
|
|
77
|
+
required: ["collection", "id", "data"]
|
|
78
|
+
}
|
|
79
|
+
},
|
|
80
|
+
{
|
|
81
|
+
name: "create_collection",
|
|
82
|
+
description: "Creates a new content collection and its physical table.",
|
|
83
|
+
inputSchema: {
|
|
84
|
+
type: "object",
|
|
85
|
+
properties: {
|
|
86
|
+
slug: { type: "string", description: "Unique URL slug for the collection" },
|
|
87
|
+
label: { type: "string", description: "Display label" },
|
|
88
|
+
labelSingular: { type: "string", description: "Singular display label" },
|
|
89
|
+
description: { type: "string", description: "Optional description" },
|
|
90
|
+
isPublic: { type: "boolean", description: "Whether it is publicly readable" }
|
|
91
|
+
},
|
|
92
|
+
required: ["slug", "label"]
|
|
93
|
+
}
|
|
94
|
+
},
|
|
95
|
+
{
|
|
96
|
+
name: "update_collection",
|
|
97
|
+
description: "Updates collection metadata.",
|
|
98
|
+
inputSchema: {
|
|
99
|
+
type: "object",
|
|
100
|
+
properties: {
|
|
101
|
+
id: { type: "string", description: "The collection ID (ULID)" },
|
|
102
|
+
data: { type: "object", description: "Metadata fields to update" }
|
|
103
|
+
},
|
|
104
|
+
required: ["id", "data"]
|
|
105
|
+
}
|
|
106
|
+
},
|
|
107
|
+
{
|
|
108
|
+
name: "add_field",
|
|
109
|
+
description: "Adds a new field to an existing collection.",
|
|
110
|
+
inputSchema: {
|
|
111
|
+
type: "object",
|
|
112
|
+
properties: {
|
|
113
|
+
collection_id: { type: "string", description: "The collection ID (ULID)" },
|
|
114
|
+
field: {
|
|
115
|
+
type: "object",
|
|
116
|
+
properties: {
|
|
117
|
+
slug: { type: "string" },
|
|
118
|
+
label: { type: "string" },
|
|
119
|
+
type: { type: "string", enum: ["text", "number", "boolean", "date", "richtext"] },
|
|
120
|
+
required: { type: "boolean" }
|
|
121
|
+
},
|
|
122
|
+
required: ["slug", "label", "type"]
|
|
123
|
+
}
|
|
124
|
+
},
|
|
125
|
+
required: ["collection_id", "field"]
|
|
126
|
+
}
|
|
127
|
+
}
|
|
128
|
+
];
|
|
129
|
+
async function runMcpBridge(options) {
|
|
130
|
+
const { url, token } = options;
|
|
131
|
+
const executeUrl = `${url.replace(/\/$/, "")}/api/mcp/execute`;
|
|
132
|
+
async function callFlareCMS(tool, args) {
|
|
133
|
+
if (!token) {
|
|
134
|
+
throw new Error("No API Token provided. Use --token or FLARE_API_TOKEN environment variable.");
|
|
135
|
+
}
|
|
136
|
+
const response = await fetch(executeUrl, {
|
|
137
|
+
method: "POST",
|
|
138
|
+
headers: {
|
|
139
|
+
"Content-Type": "application/json",
|
|
140
|
+
Authorization: `Bearer ${token}`
|
|
141
|
+
},
|
|
142
|
+
body: JSON.stringify({ tool, arguments: args })
|
|
143
|
+
});
|
|
144
|
+
if (!response.ok) {
|
|
145
|
+
const errorText = await response.text();
|
|
146
|
+
throw new Error(`FlareCMS API Error (${response.status}): ${errorText}`);
|
|
147
|
+
}
|
|
148
|
+
return await response.json();
|
|
149
|
+
}
|
|
150
|
+
async function handleRequest(request) {
|
|
151
|
+
const { method, params } = request;
|
|
152
|
+
switch (method) {
|
|
153
|
+
case "initialize":
|
|
154
|
+
return {
|
|
155
|
+
protocolVersion: "2024-11-05",
|
|
156
|
+
capabilities: { tools: {} },
|
|
157
|
+
serverInfo: { name: "@flarecms/cli", version: "0.1.0" }
|
|
158
|
+
};
|
|
159
|
+
case "notifications/initialized":
|
|
160
|
+
return null;
|
|
161
|
+
case "tools/list":
|
|
162
|
+
return { tools: TOOLS };
|
|
163
|
+
case "tools/call":
|
|
164
|
+
try {
|
|
165
|
+
return await callFlareCMS(params.name, params.arguments);
|
|
166
|
+
} catch (error) {
|
|
167
|
+
return {
|
|
168
|
+
content: [{ type: "text", text: `Error executing tool '${params.name}': ${error.message}` }],
|
|
169
|
+
isError: true
|
|
170
|
+
};
|
|
171
|
+
}
|
|
172
|
+
default:
|
|
173
|
+
return { error: { code: -32601, message: `Method not found: ${method}` } };
|
|
174
|
+
}
|
|
175
|
+
}
|
|
176
|
+
const decoder = new TextDecoder;
|
|
177
|
+
let buffer = "";
|
|
178
|
+
for await (const chunk of Bun.stdin.stream()) {
|
|
179
|
+
buffer += decoder.decode(chunk);
|
|
180
|
+
let lines = buffer.split(`
|
|
181
|
+
`);
|
|
182
|
+
buffer = lines.pop() || "";
|
|
183
|
+
for (const line of lines) {
|
|
184
|
+
if (!line.trim())
|
|
185
|
+
continue;
|
|
186
|
+
try {
|
|
187
|
+
const request = JSON.parse(line);
|
|
188
|
+
const result = await handleRequest(request);
|
|
189
|
+
if (result === null)
|
|
190
|
+
continue;
|
|
191
|
+
process.stdout.write(JSON.stringify({
|
|
192
|
+
jsonrpc: "2.0",
|
|
193
|
+
id: request.id,
|
|
194
|
+
result: result.error ? undefined : result,
|
|
195
|
+
error: result.error
|
|
196
|
+
}) + `
|
|
197
|
+
`);
|
|
198
|
+
} catch (e) {
|
|
199
|
+
process.stdout.write(JSON.stringify({
|
|
200
|
+
jsonrpc: "2.0",
|
|
201
|
+
error: { code: -32700, message: "Parse error" }
|
|
202
|
+
}) + `
|
|
203
|
+
`);
|
|
204
|
+
}
|
|
205
|
+
}
|
|
206
|
+
}
|
|
207
|
+
}
|
|
208
|
+
|
|
209
|
+
// src/cli/commands.ts
|
|
210
|
+
import { exec } from "child_process";
|
|
211
|
+
import { promisify } from "util";
|
|
212
|
+
import * as p from "@clack/prompts";
|
|
213
|
+
import pc from "picocolors";
|
|
214
|
+
import { downloadTemplate } from "giget";
|
|
215
|
+
import { existsSync, readFileSync, writeFileSync, cpSync, mkdirSync } from "fs";
|
|
216
|
+
import { resolve } from "path";
|
|
217
|
+
import { fileURLToPath } from "url";
|
|
218
|
+
var execAsync = promisify(exec);
|
|
219
|
+
var PROJECT_NAME_PATTERN = /^[a-z0-9-]+$/;
|
|
220
|
+
var TEMPLATES = {
|
|
221
|
+
starter: {
|
|
222
|
+
name: "Starter (Hono + React)",
|
|
223
|
+
description: "Unified single-process FlareCMS starter for Cloudflare Workers",
|
|
224
|
+
dir: "templates/starter"
|
|
225
|
+
},
|
|
226
|
+
blog: {
|
|
227
|
+
name: "Blog (Coming Soon)",
|
|
228
|
+
description: "A pre-configured blog template",
|
|
229
|
+
dir: "templates/blog"
|
|
230
|
+
},
|
|
231
|
+
nextjs: {
|
|
232
|
+
name: "Next.js (App Router)",
|
|
233
|
+
description: "Modern Next.js template with FlareCMS + Hono API",
|
|
234
|
+
dir: "templates/nextjs"
|
|
235
|
+
}
|
|
236
|
+
};
|
|
237
|
+
async function createProjectCommand() {
|
|
238
|
+
console.clear();
|
|
239
|
+
console.log(`
|
|
240
|
+
${pc.bold(pc.yellow("\u2014 F L A R E C M S \u2014"))}
|
|
241
|
+
`);
|
|
242
|
+
p.intro("Create a new FlareCMS project");
|
|
243
|
+
const projectName = await p.text({
|
|
244
|
+
message: "Project name?",
|
|
245
|
+
placeholder: "my-flare-site",
|
|
246
|
+
defaultValue: "my-flare-site",
|
|
247
|
+
validate: (value) => {
|
|
248
|
+
if (!value)
|
|
249
|
+
return "Project name is required";
|
|
250
|
+
if (!PROJECT_NAME_PATTERN.test(value))
|
|
251
|
+
return "Project name can only contain lowercase letters, numbers, and hyphens";
|
|
252
|
+
return;
|
|
253
|
+
}
|
|
254
|
+
});
|
|
255
|
+
if (p.isCancel(projectName)) {
|
|
256
|
+
p.cancel("Operation cancelled.");
|
|
257
|
+
process.exit(0);
|
|
258
|
+
}
|
|
259
|
+
const projectDir = resolve(process.cwd(), projectName);
|
|
260
|
+
if (existsSync(projectDir)) {
|
|
261
|
+
const overwrite = await p.confirm({
|
|
262
|
+
message: `Directory ${projectName} already exists. Overwrite?`,
|
|
263
|
+
initialValue: false
|
|
264
|
+
});
|
|
265
|
+
if (p.isCancel(overwrite) || !overwrite) {
|
|
266
|
+
p.cancel("Operation cancelled.");
|
|
267
|
+
process.exit(0);
|
|
268
|
+
}
|
|
269
|
+
}
|
|
270
|
+
const templateKey = await p.select({
|
|
271
|
+
message: "Which template would you like to use?",
|
|
272
|
+
options: Object.entries(TEMPLATES).map(([key, t]) => ({
|
|
273
|
+
value: key,
|
|
274
|
+
label: t.name,
|
|
275
|
+
hint: t.description
|
|
276
|
+
})),
|
|
277
|
+
initialValue: "starter"
|
|
278
|
+
});
|
|
279
|
+
if (p.isCancel(templateKey)) {
|
|
280
|
+
p.cancel("Operation cancelled.");
|
|
281
|
+
process.exit(0);
|
|
282
|
+
}
|
|
283
|
+
const shouldInstall = await p.confirm({
|
|
284
|
+
message: "Install dependencies with bun?",
|
|
285
|
+
initialValue: true
|
|
286
|
+
});
|
|
287
|
+
if (p.isCancel(shouldInstall)) {
|
|
288
|
+
p.cancel("Operation cancelled.");
|
|
289
|
+
process.exit(0);
|
|
290
|
+
}
|
|
291
|
+
const s = p.spinner();
|
|
292
|
+
s.start("Creating project...");
|
|
293
|
+
try {
|
|
294
|
+
if (templateKey === "nextjs") {
|
|
295
|
+
s.stop("Starting Next.js setup...");
|
|
296
|
+
const nextVersion = "15.1.0";
|
|
297
|
+
p.log.info(`${pc.cyan("Running:")} bun create next-app@${nextVersion} ${projectName} --typescript --tailwind --eslint --app --src-dir false --import-alias "@/*" --use-bun`);
|
|
298
|
+
try {
|
|
299
|
+
await execAsync(`bun create next-app@${nextVersion} ${projectName} --typescript --tailwind --eslint --app --src-dir false --import-alias "@/*" --use-bun --yes`, {
|
|
300
|
+
env: { ...process.env, NEXT_TELEMETRY_DISABLED: "1" }
|
|
301
|
+
});
|
|
302
|
+
} catch (err) {
|
|
303
|
+
p.log.warn("create-next-app failed or was already present. Continuing with injection...");
|
|
304
|
+
}
|
|
305
|
+
s.start("Injecting FlareCMS configuration...");
|
|
306
|
+
}
|
|
307
|
+
const template = TEMPLATES[templateKey];
|
|
308
|
+
const currentFilePath = fileURLToPath(import.meta.url);
|
|
309
|
+
const cliDir = resolve(currentFilePath, "..");
|
|
310
|
+
const localTemplatesRoot = resolve(cliDir, "..", "..", "..", "..", "templates");
|
|
311
|
+
const localTemplatePath = resolve(localTemplatesRoot, template.dir.split("/").pop() || "");
|
|
312
|
+
if (existsSync(localTemplatePath)) {
|
|
313
|
+
if (!existsSync(projectDir)) {
|
|
314
|
+
mkdirSync(projectDir, { recursive: true });
|
|
315
|
+
}
|
|
316
|
+
cpSync(localTemplatePath, projectDir, { recursive: true });
|
|
317
|
+
} else if (templateKey !== "nextjs") {
|
|
318
|
+
const remoteSource = `github:fhorray/flarecms/${template.dir}`;
|
|
319
|
+
await downloadTemplate(remoteSource, {
|
|
320
|
+
dir: projectDir,
|
|
321
|
+
force: true
|
|
322
|
+
});
|
|
323
|
+
}
|
|
324
|
+
const pkgPath = resolve(projectDir, "package.json");
|
|
325
|
+
if (existsSync(pkgPath)) {
|
|
326
|
+
const pkg = JSON.parse(readFileSync(pkgPath, "utf-8"));
|
|
327
|
+
pkg.name = projectName;
|
|
328
|
+
pkg.version = "0.1.0";
|
|
329
|
+
pkg.dependencies = pkg.dependencies || {};
|
|
330
|
+
pkg.dependencies["flarecms"] = "latest";
|
|
331
|
+
pkg.dependencies["hono"] = "latest";
|
|
332
|
+
writeFileSync(pkgPath, JSON.stringify(pkg, null, 2));
|
|
333
|
+
}
|
|
334
|
+
const wranglerPath = resolve(projectDir, "wrangler.jsonc");
|
|
335
|
+
if (existsSync(wranglerPath)) {
|
|
336
|
+
let wranglerContent = readFileSync(wranglerPath, "utf-8");
|
|
337
|
+
wranglerContent = wranglerContent.replace(/"name":\s*".*"/, `"name": "${projectName}"`);
|
|
338
|
+
wranglerContent = wranglerContent.replace(/"database_name":\s*".*"/, `"database_name": "${projectName}-db"`);
|
|
339
|
+
writeFileSync(wranglerPath, wranglerContent);
|
|
340
|
+
}
|
|
341
|
+
s.stop("Project created!");
|
|
342
|
+
if (shouldInstall) {
|
|
343
|
+
s.start(`Installing dependencies with ${pc.cyan("bun")}...`);
|
|
344
|
+
try {
|
|
345
|
+
await execAsync("bun install", { cwd: projectDir });
|
|
346
|
+
s.stop("Dependencies installed!");
|
|
347
|
+
s.start("Generating TypeScript bindings...");
|
|
348
|
+
try {
|
|
349
|
+
await execAsync("bun wrangler types", { cwd: projectDir });
|
|
350
|
+
s.stop("TypeScript bindings generated!");
|
|
351
|
+
} catch (err) {
|
|
352
|
+
s.stop("Failed to generate bindings (this is normal if wrangler.jsonc is incomplete)");
|
|
353
|
+
}
|
|
354
|
+
} catch (err) {
|
|
355
|
+
s.stop("Failed to install dependencies");
|
|
356
|
+
p.log.warn(`Run ${pc.cyan(`cd ${projectName} && bun install && bun cf-typegen`)} manually`);
|
|
357
|
+
}
|
|
358
|
+
}
|
|
359
|
+
const steps = [`cd ${projectName}`, "bun run dev"];
|
|
360
|
+
p.note(steps.join(`
|
|
361
|
+
`), "Next steps");
|
|
362
|
+
p.outro(`${pc.green("Done!")} Your FlareCMS project is ready at ${pc.cyan(projectName)}`);
|
|
363
|
+
} catch (error) {
|
|
364
|
+
s.stop("Failed to create project");
|
|
365
|
+
p.log.error(error instanceof Error ? error.message : String(error));
|
|
366
|
+
process.exit(1);
|
|
367
|
+
}
|
|
368
|
+
}
|
|
369
|
+
async function mcpCommand(args) {
|
|
370
|
+
let url = process.env.FLARE_API_URL || "http://localhost:8787";
|
|
371
|
+
let token = process.env.FLARE_API_TOKEN;
|
|
372
|
+
for (let i = 0;i < args.length; i++) {
|
|
373
|
+
if (args[i] === "--url" && i + 1 < args.length) {
|
|
374
|
+
url = args[i + 1];
|
|
375
|
+
i++;
|
|
376
|
+
} else if (args[i] === "--token" && i + 1 < args.length) {
|
|
377
|
+
token = args[i + 1];
|
|
378
|
+
i++;
|
|
379
|
+
}
|
|
380
|
+
}
|
|
381
|
+
if (!url) {
|
|
382
|
+
console.error(pc.red("Error: --url is required or must be set via FLARE_API_URL"));
|
|
383
|
+
process.exit(1);
|
|
384
|
+
}
|
|
385
|
+
await runMcpBridge({ url, token });
|
|
386
|
+
}
|
|
387
|
+
|
|
388
|
+
// src/cli/index.ts
|
|
389
|
+
async function main() {
|
|
390
|
+
const args = process.argv.slice(2);
|
|
391
|
+
if (args[0] === "mcp") {
|
|
392
|
+
await mcpCommand(args.slice(1));
|
|
393
|
+
} else if (args.length === 0 || args[0] === "create") {
|
|
394
|
+
await createProjectCommand();
|
|
395
|
+
} else {
|
|
396
|
+
console.log("Unknown command. Available commands: create, mcp");
|
|
397
|
+
process.exit(1);
|
|
398
|
+
}
|
|
399
|
+
}
|
|
400
|
+
main().catch((err) => {
|
|
401
|
+
console.error(err);
|
|
402
|
+
process.exit(1);
|
|
403
|
+
});
|
package/dist/cli/mcp.js
ADDED
|
@@ -0,0 +1,209 @@
|
|
|
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
|
+
export {
|
|
208
|
+
runMcpBridge
|
|
209
|
+
};
|