fastmode-mcp 1.0.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 (67) hide show
  1. package/README.md +561 -0
  2. package/bin/run.js +50 -0
  3. package/dist/index.d.ts +3 -0
  4. package/dist/index.d.ts.map +1 -0
  5. package/dist/index.js +802 -0
  6. package/dist/lib/api-client.d.ts +81 -0
  7. package/dist/lib/api-client.d.ts.map +1 -0
  8. package/dist/lib/api-client.js +237 -0
  9. package/dist/lib/auth-state.d.ts +13 -0
  10. package/dist/lib/auth-state.d.ts.map +1 -0
  11. package/dist/lib/auth-state.js +24 -0
  12. package/dist/lib/context-fetcher.d.ts +67 -0
  13. package/dist/lib/context-fetcher.d.ts.map +1 -0
  14. package/dist/lib/context-fetcher.js +190 -0
  15. package/dist/lib/credentials.d.ts +52 -0
  16. package/dist/lib/credentials.d.ts.map +1 -0
  17. package/dist/lib/credentials.js +196 -0
  18. package/dist/lib/device-flow.d.ts +14 -0
  19. package/dist/lib/device-flow.d.ts.map +1 -0
  20. package/dist/lib/device-flow.js +244 -0
  21. package/dist/tools/cms-items.d.ts +56 -0
  22. package/dist/tools/cms-items.d.ts.map +1 -0
  23. package/dist/tools/cms-items.js +376 -0
  24. package/dist/tools/create-site.d.ts +9 -0
  25. package/dist/tools/create-site.d.ts.map +1 -0
  26. package/dist/tools/create-site.js +202 -0
  27. package/dist/tools/deploy-package.d.ts +9 -0
  28. package/dist/tools/deploy-package.d.ts.map +1 -0
  29. package/dist/tools/deploy-package.js +434 -0
  30. package/dist/tools/generate-samples.d.ts +19 -0
  31. package/dist/tools/generate-samples.d.ts.map +1 -0
  32. package/dist/tools/generate-samples.js +272 -0
  33. package/dist/tools/get-conversion-guide.d.ts +7 -0
  34. package/dist/tools/get-conversion-guide.d.ts.map +1 -0
  35. package/dist/tools/get-conversion-guide.js +1323 -0
  36. package/dist/tools/get-example.d.ts +7 -0
  37. package/dist/tools/get-example.d.ts.map +1 -0
  38. package/dist/tools/get-example.js +1568 -0
  39. package/dist/tools/get-field-types.d.ts +30 -0
  40. package/dist/tools/get-field-types.d.ts.map +1 -0
  41. package/dist/tools/get-field-types.js +154 -0
  42. package/dist/tools/get-schema.d.ts +5 -0
  43. package/dist/tools/get-schema.d.ts.map +1 -0
  44. package/dist/tools/get-schema.js +320 -0
  45. package/dist/tools/get-started.d.ts +21 -0
  46. package/dist/tools/get-started.d.ts.map +1 -0
  47. package/dist/tools/get-started.js +624 -0
  48. package/dist/tools/get-tenant-schema.d.ts +18 -0
  49. package/dist/tools/get-tenant-schema.d.ts.map +1 -0
  50. package/dist/tools/get-tenant-schema.js +158 -0
  51. package/dist/tools/list-projects.d.ts +5 -0
  52. package/dist/tools/list-projects.d.ts.map +1 -0
  53. package/dist/tools/list-projects.js +101 -0
  54. package/dist/tools/sync-schema.d.ts +41 -0
  55. package/dist/tools/sync-schema.d.ts.map +1 -0
  56. package/dist/tools/sync-schema.js +483 -0
  57. package/dist/tools/validate-manifest.d.ts +5 -0
  58. package/dist/tools/validate-manifest.d.ts.map +1 -0
  59. package/dist/tools/validate-manifest.js +311 -0
  60. package/dist/tools/validate-package.d.ts +5 -0
  61. package/dist/tools/validate-package.d.ts.map +1 -0
  62. package/dist/tools/validate-package.js +337 -0
  63. package/dist/tools/validate-template.d.ts +12 -0
  64. package/dist/tools/validate-template.d.ts.map +1 -0
  65. package/dist/tools/validate-template.js +790 -0
  66. package/package.json +54 -0
  67. package/scripts/postinstall.js +129 -0
@@ -0,0 +1,434 @@
1
+ "use strict";
2
+ var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
3
+ if (k2 === undefined) k2 = k;
4
+ var desc = Object.getOwnPropertyDescriptor(m, k);
5
+ if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
6
+ desc = { enumerable: true, get: function() { return m[k]; } };
7
+ }
8
+ Object.defineProperty(o, k2, desc);
9
+ }) : (function(o, m, k, k2) {
10
+ if (k2 === undefined) k2 = k;
11
+ o[k2] = m[k];
12
+ }));
13
+ var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
14
+ Object.defineProperty(o, "default", { enumerable: true, value: v });
15
+ }) : function(o, v) {
16
+ o["default"] = v;
17
+ });
18
+ var __importStar = (this && this.__importStar) || (function () {
19
+ var ownKeys = function(o) {
20
+ ownKeys = Object.getOwnPropertyNames || function (o) {
21
+ var ar = [];
22
+ for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
23
+ return ar;
24
+ };
25
+ return ownKeys(o);
26
+ };
27
+ return function (mod) {
28
+ if (mod && mod.__esModule) return mod;
29
+ var result = {};
30
+ if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
31
+ __setModuleDefault(result, mod);
32
+ return result;
33
+ };
34
+ })();
35
+ var __importDefault = (this && this.__importDefault) || function (mod) {
36
+ return (mod && mod.__esModule) ? mod : { "default": mod };
37
+ };
38
+ Object.defineProperty(exports, "__esModule", { value: true });
39
+ exports.deployPackage = deployPackage;
40
+ const fs = __importStar(require("fs"));
41
+ const path = __importStar(require("path"));
42
+ const adm_zip_1 = __importDefault(require("adm-zip"));
43
+ const api_client_1 = require("../lib/api-client");
44
+ const device_flow_1 = require("../lib/device-flow");
45
+ const credentials_1 = require("../lib/credentials");
46
+ const validate_package_1 = require("./validate-package");
47
+ /**
48
+ * List existing projects for the authenticated user
49
+ */
50
+ async function listExistingProjects() {
51
+ const response = await (0, api_client_1.apiRequest)('/api/tenants');
52
+ if ((0, api_client_1.isApiError)(response)) {
53
+ return [];
54
+ }
55
+ return response.data;
56
+ }
57
+ /**
58
+ * Check if a project has GitHub connected
59
+ */
60
+ async function checkGitHubConnection(tenantId) {
61
+ const response = await (0, api_client_1.apiRequest)('/api/github/site-connection', { tenantId });
62
+ if ((0, api_client_1.isApiError)(response)) {
63
+ // If we can't check, assume not connected (fail open for better UX)
64
+ return null;
65
+ }
66
+ return response.data;
67
+ }
68
+ /**
69
+ * Format the GitHub blocking message
70
+ */
71
+ function formatGitHubBlockMessage(repo, branch, subdomain) {
72
+ return `# ⚠️ GitHub Sync Detected
73
+
74
+ This project has GitHub connected and will auto-deploy from:
75
+ - **Repository:** ${repo}
76
+ - **Branch:** ${branch}
77
+
78
+ ## Why This Matters
79
+
80
+ Deploying via MCP while GitHub is connected can cause conflicts:
81
+ - Your MCP changes could be overwritten on the next GitHub push
82
+ - Or this deploy could overwrite changes from GitHub
83
+
84
+ ## Options
85
+
86
+ ### Option 1: Push to GitHub Instead (Recommended)
87
+ Push your changes to the connected repository and let GitHub handle the deploy:
88
+ \`\`\`bash
89
+ git push origin ${branch}
90
+ \`\`\`
91
+
92
+ ### Option 2: Disconnect GitHub First
93
+ Go to **app.fastmode.ai** → Settings → GitHub and disconnect the repository.
94
+
95
+ ### Option 3: Force Deploy Anyway
96
+ If you understand the risks and want to proceed:
97
+ \`\`\`
98
+ deploy_package(
99
+ packagePath: "./your-site.zip",
100
+ projectId: "...",
101
+ force: true
102
+ )
103
+ \`\`\`
104
+
105
+ ---
106
+
107
+ **Dashboard:** https://app.fastmode.ai
108
+ **Site:** https://${subdomain}.fastmode.ai
109
+ `;
110
+ }
111
+ /**
112
+ * Upload a package to a specific tenant
113
+ */
114
+ async function uploadPackage(tenantId, zipBuffer) {
115
+ const credentials = await (0, credentials_1.getValidCredentials)();
116
+ if (!credentials) {
117
+ return { error: 'Not authenticated' };
118
+ }
119
+ const apiUrl = (0, api_client_1.getApiUrl)();
120
+ const url = `${apiUrl}/api/upload/package`;
121
+ // Generate a unique boundary for multipart form data
122
+ const boundary = `----FormBoundary${Date.now().toString(16)}`;
123
+ // Build multipart form data manually
124
+ const header = Buffer.from(`--${boundary}\r\n` +
125
+ `Content-Disposition: form-data; name="package"; filename="package.zip"\r\n` +
126
+ `Content-Type: application/zip\r\n\r\n`);
127
+ const footer = Buffer.from(`\r\n--${boundary}--\r\n`);
128
+ const body = Buffer.concat([header, zipBuffer, footer]);
129
+ try {
130
+ const response = await fetch(url, {
131
+ method: 'POST',
132
+ headers: {
133
+ 'Authorization': `Bearer ${credentials.accessToken}`,
134
+ 'X-Tenant-Id': tenantId,
135
+ 'Content-Type': `multipart/form-data; boundary=${boundary}`,
136
+ },
137
+ body: body,
138
+ });
139
+ const data = await response.json();
140
+ if (!response.ok) {
141
+ return { error: data.error || `Upload failed with status ${response.status}` };
142
+ }
143
+ return data.data;
144
+ }
145
+ catch (error) {
146
+ return { error: error instanceof Error ? error.message : 'Upload failed' };
147
+ }
148
+ }
149
+ /**
150
+ * Read a package from path (zip file or directory)
151
+ */
152
+ async function readPackage(packagePath) {
153
+ const absolutePath = path.resolve(packagePath);
154
+ // Check if path exists
155
+ if (!fs.existsSync(absolutePath)) {
156
+ return { error: `Path not found: ${absolutePath}` };
157
+ }
158
+ const stats = fs.statSync(absolutePath);
159
+ if (stats.isFile()) {
160
+ // It's a file - check if it's a zip
161
+ if (!absolutePath.endsWith('.zip')) {
162
+ return { error: 'File must be a .zip archive' };
163
+ }
164
+ return fs.readFileSync(absolutePath);
165
+ }
166
+ // It's a directory - we need to zip it
167
+ // For now, require a zip file
168
+ return { error: 'Please provide a .zip file. Directory upload is not yet supported.' };
169
+ }
170
+ /**
171
+ * Format project list for user selection
172
+ */
173
+ function formatProjectChoice(projects, packagePath) {
174
+ let output = `# Project Required
175
+
176
+ You need to specify which project to deploy to.
177
+
178
+ ## Your Projects
179
+
180
+ `;
181
+ if (projects.length > 0) {
182
+ projects.forEach((project, index) => {
183
+ const url = project.customDomain || `${project.subdomain}.fastmode.ai`;
184
+ output += `${index + 1}. **${project.name}**
185
+ - ID: \`${project.id}\`
186
+ - URL: ${url}
187
+ - Status: ${project.site?.status || 'pending'}
188
+
189
+ `;
190
+ });
191
+ output += `---
192
+
193
+ ## ACTION REQUIRED
194
+
195
+ **Ask the user:** "Which project should I deploy to? You can choose from the list above, or I can create a new project for you."
196
+
197
+ ### To deploy to an existing project:
198
+ \`\`\`
199
+ deploy_package(
200
+ packagePath: "${packagePath}",
201
+ projectId: "${projects[0]?.id || 'project-id-here'}"
202
+ )
203
+ \`\`\`
204
+
205
+ ### To create a NEW project first:
206
+ \`\`\`
207
+ create_site(name: "Project Name")
208
+ \`\`\`
209
+ Then deploy with the returned project ID.
210
+ `;
211
+ }
212
+ else {
213
+ output += `You don't have any projects yet.
214
+
215
+ ---
216
+
217
+ ## ACTION REQUIRED
218
+
219
+ **Ask the user:** "What would you like to name your new project?"
220
+
221
+ Then create the project:
222
+ \`\`\`
223
+ create_site(name: "User's Project Name")
224
+ \`\`\`
225
+
226
+ And deploy with the returned project ID:
227
+ \`\`\`
228
+ deploy_package(
229
+ packagePath: "${packagePath}",
230
+ projectId: "returned-project-id"
231
+ )
232
+ \`\`\`
233
+ `;
234
+ }
235
+ return output;
236
+ }
237
+ /**
238
+ * Deploy a website package to Fast Mode
239
+ *
240
+ * @param packagePath - Path to the zip file
241
+ * @param projectId - Project ID to deploy to (use list_projects to find, or create_site to create new)
242
+ * @param force - Optional: Skip GitHub connection check and deploy anyway
243
+ */
244
+ async function deployPackage(packagePath, projectId, force) {
245
+ // Check authentication
246
+ if (await (0, api_client_1.needsAuthentication)()) {
247
+ const authResult = await (0, device_flow_1.ensureAuthenticated)();
248
+ if (!authResult.authenticated) {
249
+ return authResult.message;
250
+ }
251
+ }
252
+ // If no projectId specified, list projects and prompt for selection
253
+ if (!projectId) {
254
+ const projects = await listExistingProjects();
255
+ return formatProjectChoice(projects, packagePath);
256
+ }
257
+ // Read the package first
258
+ const packageResult = await readPackage(packagePath);
259
+ if ('error' in packageResult) {
260
+ return `# Package Error
261
+
262
+ ${packageResult.error}
263
+
264
+ Please provide a valid .zip file path.
265
+ `;
266
+ }
267
+ const zipBuffer = packageResult;
268
+ // Get subdomain for the project
269
+ const projects = await listExistingProjects();
270
+ const project = projects.find(p => p.id === projectId);
271
+ const subdomain = project?.subdomain || '';
272
+ if (!project) {
273
+ return `# Project Not Found
274
+
275
+ Could not find project with ID: \`${projectId}\`
276
+
277
+ Use \`list_projects\` to see available projects, or \`create_site\` to create a new one.
278
+ `;
279
+ }
280
+ // Check for GitHub connection (unless force is true)
281
+ if (!force) {
282
+ const githubStatus = await checkGitHubConnection(projectId);
283
+ if (githubStatus?.connected && githubStatus.repo) {
284
+ // GitHub is connected - block deployment
285
+ return formatGitHubBlockMessage(githubStatus.repo, githubStatus.branch || 'main', subdomain);
286
+ }
287
+ }
288
+ // ============ PRE-DEPLOY VALIDATION GATE ============
289
+ // Validate the package BEFORE uploading to catch errors early
290
+ try {
291
+ const zip = new adm_zip_1.default(zipBuffer);
292
+ const entries = zip.getEntries();
293
+ // Build file list from zip
294
+ const fileList = entries.map(e => e.entryName);
295
+ // Find and read manifest.json
296
+ const manifestEntry = entries.find(e => e.entryName === 'manifest.json' ||
297
+ e.entryName.endsWith('/manifest.json'));
298
+ if (!manifestEntry) {
299
+ return `# DEPLOYMENT BLOCKED
300
+
301
+ **Reason:** Missing manifest.json
302
+
303
+ Every Fast Mode package requires a manifest.json at the root.
304
+
305
+ Run \`get_example("manifest_basic")\` to see the required format.
306
+
307
+ ## Expected Package Structure
308
+
309
+ \`\`\`
310
+ your-site.zip
311
+ ├── manifest.json <- REQUIRED
312
+ ├── pages/
313
+ │ ├── index.html
314
+ │ └── ...
315
+ ├── public/
316
+ │ └── css/, js/, images/
317
+ └── templates/ (optional, for CMS)
318
+ \`\`\`
319
+ `;
320
+ }
321
+ const manifestContent = manifestEntry.getData().toString('utf8');
322
+ // Collect template contents for validation
323
+ const templateContents = {};
324
+ for (const entry of entries) {
325
+ if (entry.entryName.endsWith('.html') && !entry.isDirectory) {
326
+ try {
327
+ templateContents[entry.entryName] = entry.getData().toString('utf8');
328
+ }
329
+ catch {
330
+ // Skip files that can't be read
331
+ }
332
+ }
333
+ }
334
+ // Run full package validation
335
+ const validationResult = await (0, validate_package_1.validatePackage)(fileList, manifestContent, templateContents);
336
+ // Only block on actual ERRORS, not warnings
337
+ if (validationResult.includes('HAS ERRORS') || validationResult.includes('INVALID')) {
338
+ return `# DEPLOYMENT BLOCKED
339
+
340
+ Your package has validation errors that must be fixed before deployment.
341
+
342
+ ---
343
+
344
+ ${validationResult}
345
+
346
+ ---
347
+
348
+ ## Next Steps
349
+
350
+ 1. Fix the errors listed above
351
+ 2. Rebuild your .zip package
352
+ 3. Run \`deploy_package\` again
353
+
354
+ Use \`validate_template\` and \`validate_manifest\` to check individual files.
355
+ `;
356
+ }
357
+ // Validation passed - include summary in success response later
358
+ console.error('Pre-deploy validation passed');
359
+ }
360
+ catch (validationError) {
361
+ // If validation itself fails (e.g., corrupt zip), log but continue
362
+ // The upload will fail with a more specific error if the package is truly broken
363
+ console.error('Pre-deploy validation error:', validationError);
364
+ }
365
+ // Upload the package
366
+ console.error(`Deploying to ${subdomain}.fastmode.ai...`);
367
+ const uploadResult = await uploadPackage(projectId, zipBuffer);
368
+ if ('error' in uploadResult) {
369
+ // Check if it's an auth error
370
+ if ((0, api_client_1.needsAuthError)({ success: false, error: uploadResult.error, statusCode: 401 })) {
371
+ const authResult = await (0, device_flow_1.ensureAuthenticated)();
372
+ if (!authResult.authenticated) {
373
+ return authResult.message;
374
+ }
375
+ // Retry upload
376
+ const retryResult = await uploadPackage(projectId, zipBuffer);
377
+ if ('error' in retryResult) {
378
+ return `# Upload Failed
379
+
380
+ ${retryResult.error}
381
+
382
+ Please check:
383
+ 1. The package is a valid .zip file
384
+ 2. It contains manifest.json, pages/, and public/ folders
385
+ 3. Try again or upload manually at app.fastmode.ai
386
+ `;
387
+ }
388
+ return formatSuccess(subdomain, retryResult);
389
+ }
390
+ return `# Upload Failed
391
+
392
+ ${uploadResult.error}
393
+
394
+ Please check:
395
+ 1. The package is a valid .zip file
396
+ 2. It contains manifest.json, pages/, and public/ folders
397
+ 3. Try again or upload manually at app.fastmode.ai
398
+ `;
399
+ }
400
+ return formatSuccess(subdomain, uploadResult);
401
+ }
402
+ /**
403
+ * Format success message
404
+ */
405
+ function formatSuccess(subdomain, result) {
406
+ return `# Deployment Successful!
407
+
408
+ **Package deployed!**
409
+
410
+ ## Live Site
411
+
412
+ **URL:** https://${subdomain}.fastmode.ai
413
+
414
+ ## Details
415
+
416
+ - **Files Uploaded:** ${result.filesUploaded}
417
+ - **Pages:** ${result.pages}
418
+ - **Version:** ${result.site.packageVersion}
419
+ - **Status:** ${result.site.status}
420
+
421
+ ## Next Steps
422
+
423
+ 1. **View your site:** https://${subdomain}.fastmode.ai
424
+ 2. **Edit content:** Go to app.fastmode.ai to manage your CMS
425
+ 3. **Connect GitHub:** For automatic deploys on push
426
+
427
+ ---
428
+
429
+ To update this site later:
430
+ \`\`\`
431
+ deploy_package(packagePath: "./updated-site.zip", projectId: "${result.site.id}")
432
+ \`\`\`
433
+ `;
434
+ }
@@ -0,0 +1,19 @@
1
+ /**
2
+ * Generate Sample Items Tool
3
+ *
4
+ * Generates sample items for collections with automatic dependency ordering.
5
+ * Handles relation fields by generating parent collections first.
6
+ * Requires authentication.
7
+ */
8
+ interface GenerateSamplesInput {
9
+ projectId: string;
10
+ collectionSlugs?: string[];
11
+ }
12
+ /**
13
+ * Generate sample items for collections
14
+ *
15
+ * @param input - The input with projectId and optional collectionSlugs
16
+ */
17
+ export declare function generateSampleItems(input: GenerateSamplesInput): Promise<string>;
18
+ export {};
19
+ //# sourceMappingURL=generate-samples.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"generate-samples.d.ts","sourceRoot":"","sources":["../../src/tools/generate-samples.ts"],"names":[],"mappings":"AAAA;;;;;;GAMG;AAgCH,UAAU,oBAAoB;IAC5B,SAAS,EAAE,MAAM,CAAC;IAClB,eAAe,CAAC,EAAE,MAAM,EAAE,CAAC;CAC5B;AAmID;;;;GAIG;AACH,wBAAsB,mBAAmB,CAAC,KAAK,EAAE,oBAAoB,GAAG,OAAO,CAAC,MAAM,CAAC,CAoLtF"}