multisite-cms-mcp 1.0.24 → 1.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/dist/index.js +48 -0
- package/dist/tools/create-site.d.ts +8 -0
- package/dist/tools/create-site.d.ts.map +1 -0
- package/dist/tools/create-site.js +127 -0
- package/dist/tools/deploy-package.d.ts +9 -0
- package/dist/tools/deploy-package.d.ts.map +1 -0
- package/dist/tools/deploy-package.js +335 -0
- package/dist/tools/get-conversion-guide.d.ts +1 -1
- package/dist/tools/get-conversion-guide.d.ts.map +1 -1
- package/dist/tools/get-conversion-guide.js +64 -0
- package/dist/tools/get-example.d.ts +1 -1
- package/dist/tools/get-example.d.ts.map +1 -1
- package/dist/tools/get-example.js +138 -0
- package/dist/tools/get-schema.d.ts.map +1 -1
- package/dist/tools/get-schema.js +127 -2
- package/dist/tools/validate-manifest.d.ts.map +1 -1
- package/dist/tools/validate-manifest.js +50 -6
- package/package.json +2 -2
- package/README.md +0 -310
package/dist/index.js
CHANGED
|
@@ -12,6 +12,8 @@ const get_example_1 = require("./tools/get-example");
|
|
|
12
12
|
const get_conversion_guide_1 = require("./tools/get-conversion-guide");
|
|
13
13
|
const get_tenant_schema_1 = require("./tools/get-tenant-schema");
|
|
14
14
|
const list_projects_1 = require("./tools/list-projects");
|
|
15
|
+
const create_site_1 = require("./tools/create-site");
|
|
16
|
+
const deploy_package_1 = require("./tools/deploy-package");
|
|
15
17
|
const server = new index_js_1.Server({
|
|
16
18
|
name: 'multisite-cms',
|
|
17
19
|
version: '1.0.0',
|
|
@@ -164,6 +166,46 @@ const TOOLS = [
|
|
|
164
166
|
required: ['projectId'],
|
|
165
167
|
},
|
|
166
168
|
},
|
|
169
|
+
{
|
|
170
|
+
name: 'create_site',
|
|
171
|
+
description: 'Create a new Fast Mode site/project. Opens browser for authentication if not already logged in. After creation, use deploy_package to upload your website.',
|
|
172
|
+
inputSchema: {
|
|
173
|
+
type: 'object',
|
|
174
|
+
properties: {
|
|
175
|
+
name: {
|
|
176
|
+
type: 'string',
|
|
177
|
+
description: 'The name for your new project',
|
|
178
|
+
},
|
|
179
|
+
subdomain: {
|
|
180
|
+
type: 'string',
|
|
181
|
+
description: 'Optional custom subdomain (auto-generated from name if not provided)',
|
|
182
|
+
},
|
|
183
|
+
},
|
|
184
|
+
required: ['name'],
|
|
185
|
+
},
|
|
186
|
+
},
|
|
187
|
+
{
|
|
188
|
+
name: 'deploy_package',
|
|
189
|
+
description: 'Deploy a website package (.zip file) to Fast Mode. Can deploy to an existing project or create a new one. Opens browser for authentication if not already logged in. Use list_projects first to see existing projects.',
|
|
190
|
+
inputSchema: {
|
|
191
|
+
type: 'object',
|
|
192
|
+
properties: {
|
|
193
|
+
packagePath: {
|
|
194
|
+
type: 'string',
|
|
195
|
+
description: 'Path to the .zip file containing the website package',
|
|
196
|
+
},
|
|
197
|
+
projectId: {
|
|
198
|
+
type: 'string',
|
|
199
|
+
description: 'Optional: Deploy to an existing project with this ID. Use list_projects to get project IDs.',
|
|
200
|
+
},
|
|
201
|
+
projectName: {
|
|
202
|
+
type: 'string',
|
|
203
|
+
description: 'Optional: Create a new project with this name and deploy to it. Required if projectId is not provided and no projects exist.',
|
|
204
|
+
},
|
|
205
|
+
},
|
|
206
|
+
required: ['packagePath'],
|
|
207
|
+
},
|
|
208
|
+
},
|
|
167
209
|
];
|
|
168
210
|
// Handle list tools request
|
|
169
211
|
server.setRequestHandler(types_js_1.ListToolsRequestSchema, async () => {
|
|
@@ -210,6 +252,12 @@ server.setRequestHandler(types_js_1.CallToolRequestSchema, async (request) => {
|
|
|
210
252
|
case 'get_tenant_schema':
|
|
211
253
|
result = await (0, get_tenant_schema_1.getTenantSchema)(params.projectId);
|
|
212
254
|
break;
|
|
255
|
+
case 'create_site':
|
|
256
|
+
result = await (0, create_site_1.createSite)(params.name, params.subdomain);
|
|
257
|
+
break;
|
|
258
|
+
case 'deploy_package':
|
|
259
|
+
result = await (0, deploy_package_1.deployPackage)(params.packagePath, params.projectId, params.projectName);
|
|
260
|
+
break;
|
|
213
261
|
default:
|
|
214
262
|
return {
|
|
215
263
|
content: [{ type: 'text', text: `Unknown tool: ${name}` }],
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Create a new Fast Mode site/project
|
|
3
|
+
*
|
|
4
|
+
* @param name - The name of the project
|
|
5
|
+
* @param subdomain - Optional: Custom subdomain (auto-generated from name if not provided)
|
|
6
|
+
*/
|
|
7
|
+
export declare function createSite(name: string, subdomain?: string): Promise<string>;
|
|
8
|
+
//# sourceMappingURL=create-site.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"create-site.d.ts","sourceRoot":"","sources":["../../src/tools/create-site.ts"],"names":[],"mappings":"AAgBA;;;;;GAKG;AACH,wBAAsB,UAAU,CAAC,IAAI,EAAE,MAAM,EAAE,SAAS,CAAC,EAAE,MAAM,GAAG,OAAO,CAAC,MAAM,CAAC,CAyFlF"}
|
|
@@ -0,0 +1,127 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.createSite = createSite;
|
|
4
|
+
const api_client_1 = require("../lib/api-client");
|
|
5
|
+
const device_flow_1 = require("../lib/device-flow");
|
|
6
|
+
/**
|
|
7
|
+
* Create a new Fast Mode site/project
|
|
8
|
+
*
|
|
9
|
+
* @param name - The name of the project
|
|
10
|
+
* @param subdomain - Optional: Custom subdomain (auto-generated from name if not provided)
|
|
11
|
+
*/
|
|
12
|
+
async function createSite(name, subdomain) {
|
|
13
|
+
// Check authentication
|
|
14
|
+
if (await (0, api_client_1.needsAuthentication)()) {
|
|
15
|
+
const authResult = await (0, device_flow_1.ensureAuthenticated)();
|
|
16
|
+
if (!authResult.authenticated) {
|
|
17
|
+
return authResult.message;
|
|
18
|
+
}
|
|
19
|
+
}
|
|
20
|
+
// Validate name
|
|
21
|
+
if (!name || name.trim().length === 0) {
|
|
22
|
+
return `# Invalid Name
|
|
23
|
+
|
|
24
|
+
Please provide a name for your project.
|
|
25
|
+
|
|
26
|
+
Example:
|
|
27
|
+
\`\`\`
|
|
28
|
+
create_site(name: "My Awesome Website")
|
|
29
|
+
\`\`\`
|
|
30
|
+
`;
|
|
31
|
+
}
|
|
32
|
+
// Generate subdomain from name if not provided
|
|
33
|
+
const finalSubdomain = subdomain || name
|
|
34
|
+
.toLowerCase()
|
|
35
|
+
.replace(/[^a-z0-9]+/g, '-')
|
|
36
|
+
.replace(/^-|-$/g, '')
|
|
37
|
+
.slice(0, 30);
|
|
38
|
+
// Create the site
|
|
39
|
+
const response = await (0, api_client_1.apiRequest)('/api/tenants', {
|
|
40
|
+
method: 'POST',
|
|
41
|
+
body: {
|
|
42
|
+
name: name.trim(),
|
|
43
|
+
subdomain: finalSubdomain,
|
|
44
|
+
},
|
|
45
|
+
});
|
|
46
|
+
if ((0, api_client_1.isApiError)(response)) {
|
|
47
|
+
// Check for common errors
|
|
48
|
+
if (response.error.includes('subdomain') && response.error.includes('taken')) {
|
|
49
|
+
return `# Subdomain Already Taken
|
|
50
|
+
|
|
51
|
+
The subdomain "${finalSubdomain}" is already in use.
|
|
52
|
+
|
|
53
|
+
Try creating with a different subdomain:
|
|
54
|
+
\`\`\`
|
|
55
|
+
create_site(name: "${name}", subdomain: "${finalSubdomain}-2")
|
|
56
|
+
\`\`\`
|
|
57
|
+
`;
|
|
58
|
+
}
|
|
59
|
+
if (response.statusCode === 401) {
|
|
60
|
+
// Try to re-authenticate
|
|
61
|
+
const authResult = await (0, device_flow_1.ensureAuthenticated)();
|
|
62
|
+
if (!authResult.authenticated) {
|
|
63
|
+
return authResult.message;
|
|
64
|
+
}
|
|
65
|
+
// Retry
|
|
66
|
+
const retryResponse = await (0, api_client_1.apiRequest)('/api/tenants', {
|
|
67
|
+
method: 'POST',
|
|
68
|
+
body: {
|
|
69
|
+
name: name.trim(),
|
|
70
|
+
subdomain: finalSubdomain,
|
|
71
|
+
},
|
|
72
|
+
});
|
|
73
|
+
if ((0, api_client_1.isApiError)(retryResponse)) {
|
|
74
|
+
return `# Failed to Create Site
|
|
75
|
+
|
|
76
|
+
${retryResponse.error}
|
|
77
|
+
|
|
78
|
+
Please try again or create manually at app.fastmode.ai
|
|
79
|
+
`;
|
|
80
|
+
}
|
|
81
|
+
return formatSuccess(retryResponse.data);
|
|
82
|
+
}
|
|
83
|
+
return `# Failed to Create Site
|
|
84
|
+
|
|
85
|
+
${response.error}
|
|
86
|
+
|
|
87
|
+
Please try again or create manually at app.fastmode.ai
|
|
88
|
+
`;
|
|
89
|
+
}
|
|
90
|
+
return formatSuccess(response.data);
|
|
91
|
+
}
|
|
92
|
+
/**
|
|
93
|
+
* Format success message
|
|
94
|
+
*/
|
|
95
|
+
function formatSuccess(site) {
|
|
96
|
+
return `# Site Created Successfully! 🎉
|
|
97
|
+
|
|
98
|
+
## Your New Site
|
|
99
|
+
|
|
100
|
+
- **Name:** ${site.name}
|
|
101
|
+
- **URL:** https://${site.subdomain}.fastmode.ai
|
|
102
|
+
- **Project ID:** \`${site.id}\`
|
|
103
|
+
|
|
104
|
+
## Next Steps
|
|
105
|
+
|
|
106
|
+
**Option 1: Deploy via MCP**
|
|
107
|
+
\`\`\`
|
|
108
|
+
deploy_package(
|
|
109
|
+
packagePath: "./your-site.zip",
|
|
110
|
+
projectId: "${site.id}"
|
|
111
|
+
)
|
|
112
|
+
\`\`\`
|
|
113
|
+
|
|
114
|
+
**Option 2: Connect GitHub**
|
|
115
|
+
1. Go to app.fastmode.ai
|
|
116
|
+
2. Open Settings → Connected Git Repository
|
|
117
|
+
3. Connect your GitHub account and select a repo
|
|
118
|
+
|
|
119
|
+
**Option 3: Manual Upload**
|
|
120
|
+
1. Go to app.fastmode.ai
|
|
121
|
+
2. Upload your website package in the Editor
|
|
122
|
+
|
|
123
|
+
---
|
|
124
|
+
|
|
125
|
+
Your site is ready at: https://${site.subdomain}.fastmode.ai
|
|
126
|
+
`;
|
|
127
|
+
}
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Deploy a website package to Fast Mode
|
|
3
|
+
*
|
|
4
|
+
* @param packagePath - Path to the zip file
|
|
5
|
+
* @param projectId - Optional: Deploy to existing project with this ID
|
|
6
|
+
* @param projectName - Optional: Create new project with this name
|
|
7
|
+
*/
|
|
8
|
+
export declare function deployPackage(packagePath: string, projectId?: string, projectName?: string): Promise<string>;
|
|
9
|
+
//# sourceMappingURL=deploy-package.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"deploy-package.d.ts","sourceRoot":"","sources":["../../src/tools/deploy-package.ts"],"names":[],"mappings":"AA+MA;;;;;;GAMG;AACH,wBAAsB,aAAa,CACjC,WAAW,EAAE,MAAM,EACnB,SAAS,CAAC,EAAE,MAAM,EAClB,WAAW,CAAC,EAAE,MAAM,GACnB,OAAO,CAAC,MAAM,CAAC,CA0IjB"}
|
|
@@ -0,0 +1,335 @@
|
|
|
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
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
36
|
+
exports.deployPackage = deployPackage;
|
|
37
|
+
const fs = __importStar(require("fs"));
|
|
38
|
+
const path = __importStar(require("path"));
|
|
39
|
+
const api_client_1 = require("../lib/api-client");
|
|
40
|
+
const device_flow_1 = require("../lib/device-flow");
|
|
41
|
+
const credentials_1 = require("../lib/credentials");
|
|
42
|
+
/**
|
|
43
|
+
* List existing projects for the authenticated user
|
|
44
|
+
*/
|
|
45
|
+
async function listExistingProjects() {
|
|
46
|
+
const response = await (0, api_client_1.apiRequest)('/api/tenants');
|
|
47
|
+
if ((0, api_client_1.isApiError)(response)) {
|
|
48
|
+
return [];
|
|
49
|
+
}
|
|
50
|
+
return response.data;
|
|
51
|
+
}
|
|
52
|
+
/**
|
|
53
|
+
* Create a new site/project
|
|
54
|
+
*/
|
|
55
|
+
async function createNewSite(name, subdomain) {
|
|
56
|
+
// Generate subdomain from name if not provided
|
|
57
|
+
const finalSubdomain = subdomain || name
|
|
58
|
+
.toLowerCase()
|
|
59
|
+
.replace(/[^a-z0-9]+/g, '-')
|
|
60
|
+
.replace(/^-|-$/g, '')
|
|
61
|
+
.slice(0, 30);
|
|
62
|
+
const response = await (0, api_client_1.apiRequest)('/api/tenants', {
|
|
63
|
+
method: 'POST',
|
|
64
|
+
body: {
|
|
65
|
+
name,
|
|
66
|
+
subdomain: finalSubdomain,
|
|
67
|
+
},
|
|
68
|
+
});
|
|
69
|
+
if ((0, api_client_1.isApiError)(response)) {
|
|
70
|
+
return { error: response.error };
|
|
71
|
+
}
|
|
72
|
+
return response.data;
|
|
73
|
+
}
|
|
74
|
+
/**
|
|
75
|
+
* Upload a package to a specific tenant
|
|
76
|
+
*/
|
|
77
|
+
async function uploadPackage(tenantId, zipBuffer) {
|
|
78
|
+
const credentials = await (0, credentials_1.getValidCredentials)();
|
|
79
|
+
if (!credentials) {
|
|
80
|
+
return { error: 'Not authenticated' };
|
|
81
|
+
}
|
|
82
|
+
const apiUrl = (0, api_client_1.getApiUrl)();
|
|
83
|
+
const url = `${apiUrl}/api/upload/package`;
|
|
84
|
+
// Generate a unique boundary for multipart form data
|
|
85
|
+
const boundary = `----FormBoundary${Date.now().toString(16)}`;
|
|
86
|
+
// Build multipart form data manually
|
|
87
|
+
const header = Buffer.from(`--${boundary}\r\n` +
|
|
88
|
+
`Content-Disposition: form-data; name="package"; filename="package.zip"\r\n` +
|
|
89
|
+
`Content-Type: application/zip\r\n\r\n`);
|
|
90
|
+
const footer = Buffer.from(`\r\n--${boundary}--\r\n`);
|
|
91
|
+
const body = Buffer.concat([header, zipBuffer, footer]);
|
|
92
|
+
try {
|
|
93
|
+
const response = await fetch(url, {
|
|
94
|
+
method: 'POST',
|
|
95
|
+
headers: {
|
|
96
|
+
'Authorization': `Bearer ${credentials.accessToken}`,
|
|
97
|
+
'X-Tenant-Id': tenantId,
|
|
98
|
+
'Content-Type': `multipart/form-data; boundary=${boundary}`,
|
|
99
|
+
},
|
|
100
|
+
body: body,
|
|
101
|
+
});
|
|
102
|
+
const data = await response.json();
|
|
103
|
+
if (!response.ok) {
|
|
104
|
+
return { error: data.error || `Upload failed with status ${response.status}` };
|
|
105
|
+
}
|
|
106
|
+
return data.data;
|
|
107
|
+
}
|
|
108
|
+
catch (error) {
|
|
109
|
+
return { error: error instanceof Error ? error.message : 'Upload failed' };
|
|
110
|
+
}
|
|
111
|
+
}
|
|
112
|
+
/**
|
|
113
|
+
* Read a package from path (zip file or directory)
|
|
114
|
+
*/
|
|
115
|
+
async function readPackage(packagePath) {
|
|
116
|
+
const absolutePath = path.resolve(packagePath);
|
|
117
|
+
// Check if path exists
|
|
118
|
+
if (!fs.existsSync(absolutePath)) {
|
|
119
|
+
return { error: `Path not found: ${absolutePath}` };
|
|
120
|
+
}
|
|
121
|
+
const stats = fs.statSync(absolutePath);
|
|
122
|
+
if (stats.isFile()) {
|
|
123
|
+
// It's a file - check if it's a zip
|
|
124
|
+
if (!absolutePath.endsWith('.zip')) {
|
|
125
|
+
return { error: 'File must be a .zip archive' };
|
|
126
|
+
}
|
|
127
|
+
return fs.readFileSync(absolutePath);
|
|
128
|
+
}
|
|
129
|
+
// It's a directory - we need to zip it
|
|
130
|
+
// For now, require a zip file
|
|
131
|
+
return { error: 'Please provide a .zip file. Directory upload is not yet supported.' };
|
|
132
|
+
}
|
|
133
|
+
/**
|
|
134
|
+
* Format project list for user selection
|
|
135
|
+
*/
|
|
136
|
+
function formatProjectChoice(projects) {
|
|
137
|
+
let output = `# Select a Project
|
|
138
|
+
|
|
139
|
+
Found ${projects.length} existing project${projects.length > 1 ? 's' : ''}:
|
|
140
|
+
|
|
141
|
+
`;
|
|
142
|
+
projects.forEach((project, index) => {
|
|
143
|
+
const url = project.customDomain || `${project.subdomain}.fastmode.ai`;
|
|
144
|
+
output += `${index + 1}. **${project.name}**
|
|
145
|
+
- ID: \`${project.id}\`
|
|
146
|
+
- URL: ${url}
|
|
147
|
+
- Status: ${project.site?.status || 'pending'}
|
|
148
|
+
|
|
149
|
+
`;
|
|
150
|
+
});
|
|
151
|
+
output += `---
|
|
152
|
+
|
|
153
|
+
## How to Deploy
|
|
154
|
+
|
|
155
|
+
**To an existing project:**
|
|
156
|
+
\`\`\`
|
|
157
|
+
deploy_package(
|
|
158
|
+
packagePath: "./your-site.zip",
|
|
159
|
+
projectId: "${projects[0]?.id || 'project-id-here'}"
|
|
160
|
+
)
|
|
161
|
+
\`\`\`
|
|
162
|
+
|
|
163
|
+
**To a new project:**
|
|
164
|
+
\`\`\`
|
|
165
|
+
deploy_package(
|
|
166
|
+
packagePath: "./your-site.zip",
|
|
167
|
+
projectName: "My New Site"
|
|
168
|
+
)
|
|
169
|
+
\`\`\`
|
|
170
|
+
`;
|
|
171
|
+
return output;
|
|
172
|
+
}
|
|
173
|
+
/**
|
|
174
|
+
* Deploy a website package to Fast Mode
|
|
175
|
+
*
|
|
176
|
+
* @param packagePath - Path to the zip file
|
|
177
|
+
* @param projectId - Optional: Deploy to existing project with this ID
|
|
178
|
+
* @param projectName - Optional: Create new project with this name
|
|
179
|
+
*/
|
|
180
|
+
async function deployPackage(packagePath, projectId, projectName) {
|
|
181
|
+
// Check authentication
|
|
182
|
+
if (await (0, api_client_1.needsAuthentication)()) {
|
|
183
|
+
const authResult = await (0, device_flow_1.ensureAuthenticated)();
|
|
184
|
+
if (!authResult.authenticated) {
|
|
185
|
+
return authResult.message;
|
|
186
|
+
}
|
|
187
|
+
}
|
|
188
|
+
// If no projectId specified, help user choose
|
|
189
|
+
if (!projectId && !projectName) {
|
|
190
|
+
const projects = await listExistingProjects();
|
|
191
|
+
if (projects.length > 0) {
|
|
192
|
+
return formatProjectChoice(projects);
|
|
193
|
+
}
|
|
194
|
+
// No projects exist - need a projectName
|
|
195
|
+
return `# No Projects Found
|
|
196
|
+
|
|
197
|
+
You don't have any FastMode projects yet.
|
|
198
|
+
|
|
199
|
+
To create a new project and deploy, provide a project name:
|
|
200
|
+
|
|
201
|
+
\`\`\`
|
|
202
|
+
deploy_package(
|
|
203
|
+
packagePath: "${packagePath}",
|
|
204
|
+
projectName: "My Website"
|
|
205
|
+
)
|
|
206
|
+
\`\`\`
|
|
207
|
+
`;
|
|
208
|
+
}
|
|
209
|
+
// Read the package first
|
|
210
|
+
const packageResult = await readPackage(packagePath);
|
|
211
|
+
if ('error' in packageResult) {
|
|
212
|
+
return `# Package Error
|
|
213
|
+
|
|
214
|
+
${packageResult.error}
|
|
215
|
+
|
|
216
|
+
Please provide a valid .zip file path.
|
|
217
|
+
`;
|
|
218
|
+
}
|
|
219
|
+
const zipBuffer = packageResult;
|
|
220
|
+
let targetProjectId = projectId;
|
|
221
|
+
let subdomain = '';
|
|
222
|
+
// Create new project if needed
|
|
223
|
+
if (!targetProjectId && projectName) {
|
|
224
|
+
const createResult = await createNewSite(projectName);
|
|
225
|
+
if ('error' in createResult) {
|
|
226
|
+
// Check if it's an auth error
|
|
227
|
+
if (createResult.error.includes('401') || createResult.error.includes('auth')) {
|
|
228
|
+
const authResult = await (0, device_flow_1.ensureAuthenticated)();
|
|
229
|
+
if (!authResult.authenticated) {
|
|
230
|
+
return authResult.message;
|
|
231
|
+
}
|
|
232
|
+
// Retry
|
|
233
|
+
const retryResult = await createNewSite(projectName);
|
|
234
|
+
if ('error' in retryResult) {
|
|
235
|
+
return `# Failed to Create Project
|
|
236
|
+
|
|
237
|
+
${retryResult.error}
|
|
238
|
+
|
|
239
|
+
Please try again or create the project manually at app.fastmode.ai
|
|
240
|
+
`;
|
|
241
|
+
}
|
|
242
|
+
targetProjectId = retryResult.id;
|
|
243
|
+
subdomain = retryResult.subdomain;
|
|
244
|
+
}
|
|
245
|
+
else {
|
|
246
|
+
return `# Failed to Create Project
|
|
247
|
+
|
|
248
|
+
${createResult.error}
|
|
249
|
+
|
|
250
|
+
Please try again or create the project manually at app.fastmode.ai
|
|
251
|
+
`;
|
|
252
|
+
}
|
|
253
|
+
}
|
|
254
|
+
else {
|
|
255
|
+
targetProjectId = createResult.id;
|
|
256
|
+
subdomain = createResult.subdomain;
|
|
257
|
+
console.error(`Created new project: ${projectName} (${subdomain}.fastmode.ai)`);
|
|
258
|
+
}
|
|
259
|
+
}
|
|
260
|
+
// Get subdomain for existing project
|
|
261
|
+
if (!subdomain && targetProjectId) {
|
|
262
|
+
const projects = await listExistingProjects();
|
|
263
|
+
const project = projects.find(p => p.id === targetProjectId);
|
|
264
|
+
subdomain = project?.subdomain || '';
|
|
265
|
+
}
|
|
266
|
+
// Upload the package
|
|
267
|
+
console.error(`Deploying to ${subdomain}.fastmode.ai...`);
|
|
268
|
+
const uploadResult = await uploadPackage(targetProjectId, zipBuffer);
|
|
269
|
+
if ('error' in uploadResult) {
|
|
270
|
+
// Check if it's an auth error
|
|
271
|
+
if ((0, api_client_1.needsAuthError)({ success: false, error: uploadResult.error, statusCode: 401 })) {
|
|
272
|
+
const authResult = await (0, device_flow_1.ensureAuthenticated)();
|
|
273
|
+
if (!authResult.authenticated) {
|
|
274
|
+
return authResult.message;
|
|
275
|
+
}
|
|
276
|
+
// Retry upload
|
|
277
|
+
const retryResult = await uploadPackage(targetProjectId, zipBuffer);
|
|
278
|
+
if ('error' in retryResult) {
|
|
279
|
+
return `# Upload Failed
|
|
280
|
+
|
|
281
|
+
${retryResult.error}
|
|
282
|
+
|
|
283
|
+
Please check:
|
|
284
|
+
1. The package is a valid .zip file
|
|
285
|
+
2. It contains manifest.json, pages/, and public/ folders
|
|
286
|
+
3. Try again or upload manually at app.fastmode.ai
|
|
287
|
+
`;
|
|
288
|
+
}
|
|
289
|
+
return formatSuccess(subdomain, retryResult, !!projectName);
|
|
290
|
+
}
|
|
291
|
+
return `# Upload Failed
|
|
292
|
+
|
|
293
|
+
${uploadResult.error}
|
|
294
|
+
|
|
295
|
+
Please check:
|
|
296
|
+
1. The package is a valid .zip file
|
|
297
|
+
2. It contains manifest.json, pages/, and public/ folders
|
|
298
|
+
3. Try again or upload manually at app.fastmode.ai
|
|
299
|
+
`;
|
|
300
|
+
}
|
|
301
|
+
return formatSuccess(subdomain, uploadResult, !!projectName);
|
|
302
|
+
}
|
|
303
|
+
/**
|
|
304
|
+
* Format success message
|
|
305
|
+
*/
|
|
306
|
+
function formatSuccess(subdomain, result, isNewProject) {
|
|
307
|
+
return `# Deployment Successful! 🚀
|
|
308
|
+
|
|
309
|
+
${isNewProject ? '**New project created and deployed!**' : '**Package deployed!**'}
|
|
310
|
+
|
|
311
|
+
## Live Site
|
|
312
|
+
|
|
313
|
+
**URL:** https://${subdomain}.fastmode.ai
|
|
314
|
+
|
|
315
|
+
## Details
|
|
316
|
+
|
|
317
|
+
- **Files Uploaded:** ${result.filesUploaded}
|
|
318
|
+
- **Pages:** ${result.pages}
|
|
319
|
+
- **Version:** ${result.site.packageVersion}
|
|
320
|
+
- **Status:** ${result.site.status}
|
|
321
|
+
|
|
322
|
+
## Next Steps
|
|
323
|
+
|
|
324
|
+
1. **View your site:** https://${subdomain}.fastmode.ai
|
|
325
|
+
2. **Edit content:** Go to app.fastmode.ai to manage your CMS
|
|
326
|
+
3. **Connect GitHub:** For automatic deploys on push
|
|
327
|
+
|
|
328
|
+
---
|
|
329
|
+
|
|
330
|
+
To update this site later:
|
|
331
|
+
\`\`\`
|
|
332
|
+
deploy_package(packagePath: "./updated-site.zip", projectId: "${result.site.id}")
|
|
333
|
+
\`\`\`
|
|
334
|
+
`;
|
|
335
|
+
}
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
type Section = 'full' | 'analysis' | 'structure' | 'manifest' | 'templates' | 'tokens' | 'forms' | 'assets' | 'checklist';
|
|
1
|
+
type Section = 'full' | 'analysis' | 'structure' | 'seo' | 'manifest' | 'templates' | 'tokens' | 'forms' | 'assets' | 'checklist';
|
|
2
2
|
/**
|
|
3
3
|
* Returns the conversion guide, either full or a specific section
|
|
4
4
|
*/
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"get-conversion-guide.d.ts","sourceRoot":"","sources":["../../src/tools/get-conversion-guide.ts"],"names":[],"mappings":"AAAA,KAAK,OAAO,GAAG,MAAM,GAAG,UAAU,GAAG,WAAW,GAAG,UAAU,GAAG,WAAW,GAAG,QAAQ,GAAG,OAAO,GAAG,QAAQ,GAAG,WAAW,CAAC;
|
|
1
|
+
{"version":3,"file":"get-conversion-guide.d.ts","sourceRoot":"","sources":["../../src/tools/get-conversion-guide.ts"],"names":[],"mappings":"AAAA,KAAK,OAAO,GAAG,MAAM,GAAG,UAAU,GAAG,WAAW,GAAG,KAAK,GAAG,UAAU,GAAG,WAAW,GAAG,QAAQ,GAAG,OAAO,GAAG,QAAQ,GAAG,WAAW,CAAC;AA24BlI;;GAEG;AACH,wBAAsB,kBAAkB,CAAC,OAAO,EAAE,OAAO,GAAG,OAAO,CAAC,MAAM,CAAC,CAsD1E"}
|
|
@@ -120,6 +120,58 @@ repo/
|
|
|
120
120
|
- CMS-powered pages
|
|
121
121
|
- Use {{tokens}} for dynamic content
|
|
122
122
|
- Named by collection type`,
|
|
123
|
+
seo: `# SEO Tags (CRITICAL - Do NOT Include)
|
|
124
|
+
|
|
125
|
+
Fast Mode automatically manages all SEO meta tags. Do NOT include these in your HTML templates:
|
|
126
|
+
|
|
127
|
+
## Tags to EXCLUDE
|
|
128
|
+
|
|
129
|
+
| Tag | Reason |
|
|
130
|
+
|-----|--------|
|
|
131
|
+
| \`<title>...\` | Managed via Settings → SEO & Design |
|
|
132
|
+
| \`<meta name="description">\` | Managed via Settings → SEO & Design |
|
|
133
|
+
| \`<meta name="keywords">\` | Managed via Settings → SEO & Design |
|
|
134
|
+
| \`<meta property="og:*">\` | Open Graph tags auto-generated |
|
|
135
|
+
| \`<meta name="twitter:*">\` | Twitter cards auto-generated |
|
|
136
|
+
| \`<link rel="icon">\` | Favicon managed in settings |
|
|
137
|
+
| \`<link rel="shortcut icon">\` | Favicon managed in settings |
|
|
138
|
+
| \`<link rel="apple-touch-icon">\` | Managed by Fast Mode |
|
|
139
|
+
| \`<meta name="google-site-verification">\` | Managed in settings |
|
|
140
|
+
|
|
141
|
+
## Correct \`<head>\` Structure
|
|
142
|
+
|
|
143
|
+
\`\`\`html
|
|
144
|
+
<head>
|
|
145
|
+
<meta charset="UTF-8">
|
|
146
|
+
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
|
147
|
+
<!-- SEO managed by Fast Mode -->
|
|
148
|
+
<link rel="stylesheet" href="/public/css/style.css">
|
|
149
|
+
<!-- Font imports, external scripts, etc. are OK -->
|
|
150
|
+
<link href="https://fonts.googleapis.com/css2?family=Inter:wght@400;600&display=swap" rel="stylesheet">
|
|
151
|
+
</head>
|
|
152
|
+
\`\`\`
|
|
153
|
+
|
|
154
|
+
## How Fast Mode SEO Works
|
|
155
|
+
|
|
156
|
+
1. **Global SEO** (Settings → SEO & Design)
|
|
157
|
+
- Site-wide defaults: title, description, OG image, favicon
|
|
158
|
+
- Applied to all pages
|
|
159
|
+
|
|
160
|
+
2. **Page-Level SEO** (Editor → click page → SEO tab)
|
|
161
|
+
- Override specific pages with custom title/description
|
|
162
|
+
- Page-level settings take priority over global
|
|
163
|
+
|
|
164
|
+
3. **Automatic Injection**
|
|
165
|
+
- Fast Mode strips any hardcoded SEO tags from templates
|
|
166
|
+
- Injects correct SEO from settings at render time
|
|
167
|
+
- Guarantees single source of truth
|
|
168
|
+
|
|
169
|
+
## Why This Matters
|
|
170
|
+
|
|
171
|
+
- No duplicate meta tags (bad for SEO ranking)
|
|
172
|
+
- Content managers can update SEO without touching code
|
|
173
|
+
- Consistent SEO across all pages
|
|
174
|
+
- Works retroactively on existing templates`,
|
|
123
175
|
manifest: `# manifest.json Configuration
|
|
124
176
|
|
|
125
177
|
## Basic Structure
|
|
@@ -783,6 +835,14 @@ Keep external URLs unchanged:
|
|
|
783
835
|
- [ ] Assets in public/ folder
|
|
784
836
|
- [ ] Templates in templates/ (or pages/ if configuring via Settings UI)
|
|
785
837
|
|
|
838
|
+
## ✓ SEO (CRITICAL)
|
|
839
|
+
- [ ] NO \`<title>\` tags in HTML
|
|
840
|
+
- [ ] NO \`<meta name="description">\` tags
|
|
841
|
+
- [ ] NO \`<meta property="og:*">\` tags
|
|
842
|
+
- [ ] NO \`<meta name="twitter:*">\` tags
|
|
843
|
+
- [ ] NO \`<link rel="icon">\` tags
|
|
844
|
+
- [ ] Include \`<!-- SEO managed by Fast Mode -->\` comment in head
|
|
845
|
+
|
|
786
846
|
## ✓ manifest.json
|
|
787
847
|
- [ ] All static pages listed in pages array
|
|
788
848
|
- [ ] Each page has path, file, title
|
|
@@ -851,6 +911,10 @@ ${SECTIONS.structure}
|
|
|
851
911
|
|
|
852
912
|
---
|
|
853
913
|
|
|
914
|
+
${SECTIONS.seo}
|
|
915
|
+
|
|
916
|
+
---
|
|
917
|
+
|
|
854
918
|
${SECTIONS.manifest}
|
|
855
919
|
|
|
856
920
|
---
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
type ExampleType = 'manifest_basic' | 'manifest_custom_paths' | 'manifest_minimal_with_ui' | 'blog_index_template' | 'blog_post_template' | 'team_template' | 'downloads_template' | 'authors_template' | 'author_detail_template' | 'custom_collection_template' | 'form_handling' | 'asset_paths' | 'image_handling' | 'data_edit_keys' | 'each_loop' | 'conditional_if' | 'nested_fields' | 'featured_posts' | 'parent_context' | 'equality_comparison';
|
|
1
|
+
type ExampleType = 'manifest_basic' | 'manifest_custom_paths' | 'manifest_minimal_with_ui' | 'blog_index_template' | 'blog_post_template' | 'team_template' | 'downloads_template' | 'authors_template' | 'author_detail_template' | 'custom_collection_template' | 'form_handling' | 'asset_paths' | 'image_handling' | 'relation_fields' | 'data_edit_keys' | 'each_loop' | 'conditional_if' | 'nested_fields' | 'featured_posts' | 'parent_context' | 'equality_comparison';
|
|
2
2
|
/**
|
|
3
3
|
* Returns example code for a specific pattern
|
|
4
4
|
*/
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"get-example.d.ts","sourceRoot":"","sources":["../../src/tools/get-example.ts"],"names":[],"mappings":"AAAA,KAAK,WAAW,GACZ,gBAAgB,GAChB,uBAAuB,GACvB,0BAA0B,GAC1B,qBAAqB,GACrB,oBAAoB,GACpB,eAAe,GACf,oBAAoB,GACpB,kBAAkB,GAClB,wBAAwB,GACxB,4BAA4B,GAC5B,eAAe,GACf,aAAa,GACb,gBAAgB,GAChB,gBAAgB,GAChB,WAAW,GACX,gBAAgB,GAChB,eAAe,GACf,gBAAgB,GAChB,gBAAgB,GAChB,qBAAqB,CAAC;
|
|
1
|
+
{"version":3,"file":"get-example.d.ts","sourceRoot":"","sources":["../../src/tools/get-example.ts"],"names":[],"mappings":"AAAA,KAAK,WAAW,GACZ,gBAAgB,GAChB,uBAAuB,GACvB,0BAA0B,GAC1B,qBAAqB,GACrB,oBAAoB,GACpB,eAAe,GACf,oBAAoB,GACpB,kBAAkB,GAClB,wBAAwB,GACxB,4BAA4B,GAC5B,eAAe,GACf,aAAa,GACb,gBAAgB,GAChB,iBAAiB,GACjB,gBAAgB,GAChB,WAAW,GACX,gBAAgB,GAChB,eAAe,GACf,gBAAgB,GAChB,gBAAgB,GAChB,qBAAqB,CAAC;AA0pC1B;;GAEG;AACH,wBAAsB,UAAU,CAAC,WAAW,EAAE,WAAW,GAAG,OAAO,CAAC,MAAM,CAAC,CAE1E"}
|