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,376 @@
1
+ "use strict";
2
+ /**
3
+ * CMS Item Management Tools
4
+ *
5
+ * CRUD operations for managing items in CMS collections via the dashboard API.
6
+ */
7
+ Object.defineProperty(exports, "__esModule", { value: true });
8
+ exports.createCmsItem = createCmsItem;
9
+ exports.listCmsItems = listCmsItems;
10
+ exports.getCmsItem = getCmsItem;
11
+ exports.updateCmsItem = updateCmsItem;
12
+ exports.deleteCmsItem = deleteCmsItem;
13
+ const api_client_1 = require("../lib/api-client");
14
+ const device_flow_1 = require("../lib/device-flow");
15
+ /**
16
+ * Helper to ensure authentication and resolve project ID
17
+ */
18
+ async function prepareRequest(projectId) {
19
+ // Check if we need to authenticate
20
+ if (await (0, api_client_1.needsAuthentication)()) {
21
+ const authResult = await (0, device_flow_1.ensureAuthenticated)();
22
+ if (!authResult.authenticated) {
23
+ return { success: false, message: authResult.message };
24
+ }
25
+ }
26
+ // Resolve project ID
27
+ const resolved = await (0, api_client_1.resolveProjectId)(projectId);
28
+ if ('error' in resolved) {
29
+ return { success: false, message: `# Project Not Found\n\n${resolved.error}` };
30
+ }
31
+ return { success: true, tenantId: resolved.tenantId };
32
+ }
33
+ /**
34
+ * Helper to get collection ID from slug
35
+ * Dashboard API uses collection IDs, not slugs
36
+ */
37
+ async function getCollectionId(tenantId, collectionSlug) {
38
+ const response = await (0, api_client_1.apiRequest)('/api/collections', { tenantId });
39
+ if ((0, api_client_1.isApiError)(response)) {
40
+ return { error: `Failed to fetch collections: ${response.error}` };
41
+ }
42
+ const collection = response.data.find(c => c.slug === collectionSlug);
43
+ if (!collection) {
44
+ const available = response.data.map(c => `- ${c.slug} (${c.name})`).join('\n');
45
+ return {
46
+ error: `Collection "${collectionSlug}" not found.\n\nAvailable collections:\n${available || 'None'}`
47
+ };
48
+ }
49
+ return { collectionId: collection.id };
50
+ }
51
+ /**
52
+ * Create a new item in a CMS collection
53
+ */
54
+ async function createCmsItem(params) {
55
+ const prep = await prepareRequest(params.projectId);
56
+ if (!prep.success)
57
+ return prep.message;
58
+ // Get collection ID from slug
59
+ const collectionResult = await getCollectionId(prep.tenantId, params.collectionSlug);
60
+ if ('error' in collectionResult) {
61
+ return `# Error\n\n${collectionResult.error}`;
62
+ }
63
+ const response = await (0, api_client_1.apiRequest)(`/api/collections/${collectionResult.collectionId}/items`, {
64
+ tenantId: prep.tenantId,
65
+ method: 'POST',
66
+ body: {
67
+ name: params.name,
68
+ slug: params.slug,
69
+ data: params.data,
70
+ publishedAt: params.publishedAt || new Date().toISOString(),
71
+ },
72
+ });
73
+ if ((0, api_client_1.isApiError)(response)) {
74
+ if ((0, api_client_1.needsAuthError)(response)) {
75
+ const authResult = await (0, device_flow_1.ensureAuthenticated)();
76
+ if (!authResult.authenticated)
77
+ return authResult.message;
78
+ // Retry
79
+ const retry = await (0, api_client_1.apiRequest)(`/api/collections/${collectionResult.collectionId}/items`, {
80
+ tenantId: prep.tenantId,
81
+ method: 'POST',
82
+ body: {
83
+ name: params.name,
84
+ slug: params.slug,
85
+ data: params.data,
86
+ publishedAt: params.publishedAt || new Date().toISOString(),
87
+ },
88
+ });
89
+ if ((0, api_client_1.isApiError)(retry)) {
90
+ return `# Error Creating Item\n\n${retry.error}\n\n**Status:** ${retry.statusCode}`;
91
+ }
92
+ return formatCreatedItem(retry.data, params.collectionSlug);
93
+ }
94
+ return `# Error Creating Item\n\n${response.error}\n\n**Status:** ${response.statusCode}`;
95
+ }
96
+ return formatCreatedItem(response.data, params.collectionSlug);
97
+ }
98
+ function formatCreatedItem(item, collectionSlug) {
99
+ return `# Item Created Successfully
100
+
101
+ **Collection:** ${collectionSlug}
102
+ **Name:** ${item.name}
103
+ **Slug:** ${item.slug || 'N/A'}
104
+ **ID:** ${item.id}
105
+ **Published:** ${item.publishedAt || 'Draft'}
106
+
107
+ ## Item Data
108
+ \`\`\`json
109
+ ${JSON.stringify(item.data, null, 2)}
110
+ \`\`\`
111
+ `;
112
+ }
113
+ /**
114
+ * List items in a CMS collection
115
+ */
116
+ async function listCmsItems(params) {
117
+ const prep = await prepareRequest(params.projectId);
118
+ if (!prep.success)
119
+ return prep.message;
120
+ // Get collection ID from slug
121
+ const collectionResult = await getCollectionId(prep.tenantId, params.collectionSlug);
122
+ if ('error' in collectionResult) {
123
+ return `# Error\n\n${collectionResult.error}`;
124
+ }
125
+ // Build query params
126
+ const queryParams = new URLSearchParams();
127
+ if (params.limit)
128
+ queryParams.set('limit', String(params.limit));
129
+ if (params.sort)
130
+ queryParams.set('sort', params.sort);
131
+ if (params.order)
132
+ queryParams.set('order', params.order);
133
+ const queryString = queryParams.toString();
134
+ const endpoint = `/api/collections/${collectionResult.collectionId}/items${queryString ? '?' + queryString : ''}`;
135
+ const response = await (0, api_client_1.apiRequest)(endpoint, { tenantId: prep.tenantId, method: 'GET' });
136
+ if ((0, api_client_1.isApiError)(response)) {
137
+ if ((0, api_client_1.needsAuthError)(response)) {
138
+ const authResult = await (0, device_flow_1.ensureAuthenticated)();
139
+ if (!authResult.authenticated)
140
+ return authResult.message;
141
+ const retry = await (0, api_client_1.apiRequest)(endpoint, { tenantId: prep.tenantId, method: 'GET' });
142
+ if ((0, api_client_1.isApiError)(retry)) {
143
+ return `# Error Listing Items\n\n${retry.error}\n\n**Status:** ${retry.statusCode}`;
144
+ }
145
+ return formatItemsList(retry.data, params.collectionSlug);
146
+ }
147
+ return `# Error Listing Items\n\n${response.error}\n\n**Status:** ${response.statusCode}`;
148
+ }
149
+ return formatItemsList(response.data, params.collectionSlug);
150
+ }
151
+ function formatItemsList(items, collectionSlug) {
152
+ if (!items || items.length === 0) {
153
+ return `# No Items Found
154
+
155
+ Collection **${collectionSlug}** is empty.
156
+
157
+ Use \`create_cms_item\` to add items to this collection.
158
+ `;
159
+ }
160
+ let output = `# Items in ${collectionSlug}
161
+
162
+ Found ${items.length} item${items.length !== 1 ? 's' : ''}:
163
+
164
+ | Name | Slug | Published | ID |
165
+ |------|------|-----------|-----|
166
+ `;
167
+ for (const item of items) {
168
+ const published = item.publishedAt ? new Date(item.publishedAt).toLocaleDateString() : 'Draft';
169
+ output += `| ${item.name} | ${item.slug || '-'} | ${published} | \`${item.id.slice(0, 8)}...\` |\n`;
170
+ }
171
+ output += `
172
+ ---
173
+
174
+ ## Item Details
175
+
176
+ `;
177
+ for (const item of items) {
178
+ output += `### ${item.name}
179
+ - **Slug:** \`${item.slug || 'N/A'}\`
180
+ - **ID:** \`${item.id}\`
181
+ - **Data:** ${Object.keys(item.data).length} fields
182
+
183
+ `;
184
+ }
185
+ return output;
186
+ }
187
+ /**
188
+ * Get a single item by slug
189
+ */
190
+ async function getCmsItem(params) {
191
+ const prep = await prepareRequest(params.projectId);
192
+ if (!prep.success)
193
+ return prep.message;
194
+ // Get collection ID from slug
195
+ const collectionResult = await getCollectionId(prep.tenantId, params.collectionSlug);
196
+ if ('error' in collectionResult) {
197
+ return `# Error\n\n${collectionResult.error}`;
198
+ }
199
+ // List items and find by slug (dashboard API uses item IDs, not slugs)
200
+ const response = await (0, api_client_1.apiRequest)(`/api/collections/${collectionResult.collectionId}/items`, { tenantId: prep.tenantId, method: 'GET' });
201
+ if ((0, api_client_1.isApiError)(response)) {
202
+ if ((0, api_client_1.needsAuthError)(response)) {
203
+ const authResult = await (0, device_flow_1.ensureAuthenticated)();
204
+ if (!authResult.authenticated)
205
+ return authResult.message;
206
+ const retry = await (0, api_client_1.apiRequest)(`/api/collections/${collectionResult.collectionId}/items`, { tenantId: prep.tenantId, method: 'GET' });
207
+ if ((0, api_client_1.isApiError)(retry)) {
208
+ return `# Error Fetching Item\n\n${retry.error}\n\n**Status:** ${retry.statusCode}`;
209
+ }
210
+ const item = retry.data.find(i => i.slug === params.itemSlug);
211
+ if (!item) {
212
+ return `# Item Not Found\n\nNo item with slug "${params.itemSlug}" in collection "${params.collectionSlug}".`;
213
+ }
214
+ return formatSingleItem(item, params.collectionSlug);
215
+ }
216
+ return `# Error Fetching Item\n\n${response.error}\n\n**Status:** ${response.statusCode}`;
217
+ }
218
+ const item = response.data.find(i => i.slug === params.itemSlug);
219
+ if (!item) {
220
+ return `# Item Not Found\n\nNo item with slug "${params.itemSlug}" in collection "${params.collectionSlug}".`;
221
+ }
222
+ return formatSingleItem(item, params.collectionSlug);
223
+ }
224
+ function formatSingleItem(item, collectionSlug) {
225
+ return `# ${item.name}
226
+
227
+ **Collection:** ${collectionSlug}
228
+ **Slug:** \`${item.slug || 'N/A'}\`
229
+ **ID:** \`${item.id}\`
230
+ **Published:** ${item.publishedAt ? new Date(item.publishedAt).toLocaleString() : 'Draft'}
231
+ **Created:** ${new Date(item.createdAt).toLocaleString()}
232
+ **Updated:** ${new Date(item.updatedAt).toLocaleString()}
233
+
234
+ ## Item Data
235
+
236
+ \`\`\`json
237
+ ${JSON.stringify(item.data, null, 2)}
238
+ \`\`\`
239
+ `;
240
+ }
241
+ /**
242
+ * Update an existing item
243
+ */
244
+ async function updateCmsItem(params) {
245
+ const prep = await prepareRequest(params.projectId);
246
+ if (!prep.success)
247
+ return prep.message;
248
+ // Get collection ID from slug
249
+ const collectionResult = await getCollectionId(prep.tenantId, params.collectionSlug);
250
+ if ('error' in collectionResult) {
251
+ return `# Error\n\n${collectionResult.error}`;
252
+ }
253
+ // Find item ID by slug
254
+ const itemsResponse = await (0, api_client_1.apiRequest)(`/api/collections/${collectionResult.collectionId}/items`, { tenantId: prep.tenantId, method: 'GET' });
255
+ if ((0, api_client_1.isApiError)(itemsResponse)) {
256
+ return `# Error Finding Item\n\n${itemsResponse.error}\n\n**Status:** ${itemsResponse.statusCode}`;
257
+ }
258
+ const existingItem = itemsResponse.data.find(i => i.slug === params.itemSlug);
259
+ if (!existingItem) {
260
+ return `# Item Not Found\n\nNo item with slug "${params.itemSlug}" in collection "${params.collectionSlug}".`;
261
+ }
262
+ // Build the update body with only provided fields
263
+ // IMPORTANT: Merge new data with existing data to preserve fields not being updated
264
+ const updateBody = {};
265
+ if (params.name !== undefined)
266
+ updateBody.name = params.name;
267
+ if (params.data !== undefined) {
268
+ // Merge with existing data - new fields override, existing fields preserved
269
+ const existingData = existingItem.data;
270
+ updateBody.data = { ...existingData, ...params.data };
271
+ }
272
+ if (params.publishedAt !== undefined)
273
+ updateBody.publishedAt = params.publishedAt;
274
+ if (Object.keys(updateBody).length === 0) {
275
+ return `# No Updates Provided
276
+
277
+ You must provide at least one of: \`name\`, \`data\`, or \`publishedAt\` to update.
278
+ `;
279
+ }
280
+ const response = await (0, api_client_1.apiRequest)(`/api/collections/${collectionResult.collectionId}/items/${existingItem.id}`, {
281
+ tenantId: prep.tenantId,
282
+ method: 'PUT',
283
+ body: updateBody,
284
+ });
285
+ if ((0, api_client_1.isApiError)(response)) {
286
+ if ((0, api_client_1.needsAuthError)(response)) {
287
+ const authResult = await (0, device_flow_1.ensureAuthenticated)();
288
+ if (!authResult.authenticated)
289
+ return authResult.message;
290
+ const retry = await (0, api_client_1.apiRequest)(`/api/collections/${collectionResult.collectionId}/items/${existingItem.id}`, {
291
+ tenantId: prep.tenantId,
292
+ method: 'PUT',
293
+ body: updateBody,
294
+ });
295
+ if ((0, api_client_1.isApiError)(retry)) {
296
+ return `# Error Updating Item\n\n${retry.error}\n\n**Status:** ${retry.statusCode}`;
297
+ }
298
+ return formatUpdatedItem(retry.data, params.collectionSlug);
299
+ }
300
+ return `# Error Updating Item\n\n${response.error}\n\n**Status:** ${response.statusCode}`;
301
+ }
302
+ return formatUpdatedItem(response.data, params.collectionSlug);
303
+ }
304
+ function formatUpdatedItem(item, collectionSlug) {
305
+ return `# Item Updated Successfully
306
+
307
+ **Collection:** ${collectionSlug}
308
+ **Name:** ${item.name}
309
+ **Slug:** ${item.slug || 'N/A'}
310
+ **ID:** ${item.id}
311
+ **Updated:** ${new Date(item.updatedAt).toLocaleString()}
312
+
313
+ ## Updated Data
314
+ \`\`\`json
315
+ ${JSON.stringify(item.data, null, 2)}
316
+ \`\`\`
317
+ `;
318
+ }
319
+ /**
320
+ * Delete an item from a collection
321
+ * REQUIRES confirmDelete: true to execute
322
+ */
323
+ async function deleteCmsItem(params) {
324
+ // Safety check: require explicit confirmation
325
+ if (params.confirmDelete !== true) {
326
+ return `# Confirmation Required
327
+
328
+ **You must get explicit permission from the user before deleting items.**
329
+
330
+ To delete the item "${params.itemSlug}" from collection "${params.collectionSlug}":
331
+
332
+ 1. Ask the user: "Are you sure you want to delete [item name]? This cannot be undone."
333
+ 2. Only after the user confirms, call this tool again with \`confirmDelete: true\`
334
+
335
+ **Never delete without user confirmation.**
336
+ `;
337
+ }
338
+ const prep = await prepareRequest(params.projectId);
339
+ if (!prep.success)
340
+ return prep.message;
341
+ // Get collection ID from slug
342
+ const collectionResult = await getCollectionId(prep.tenantId, params.collectionSlug);
343
+ if ('error' in collectionResult) {
344
+ return `# Error\n\n${collectionResult.error}`;
345
+ }
346
+ // Find item ID by slug
347
+ const itemsResponse = await (0, api_client_1.apiRequest)(`/api/collections/${collectionResult.collectionId}/items`, { tenantId: prep.tenantId, method: 'GET' });
348
+ if ((0, api_client_1.isApiError)(itemsResponse)) {
349
+ return `# Error Finding Item\n\n${itemsResponse.error}\n\n**Status:** ${itemsResponse.statusCode}`;
350
+ }
351
+ const existingItem = itemsResponse.data.find(i => i.slug === params.itemSlug);
352
+ if (!existingItem) {
353
+ return `# Item Not Found\n\nNo item with slug "${params.itemSlug}" in collection "${params.collectionSlug}".`;
354
+ }
355
+ const response = await (0, api_client_1.apiRequest)(`/api/collections/${collectionResult.collectionId}/items/${existingItem.id}`, { tenantId: prep.tenantId, method: 'DELETE' });
356
+ if ((0, api_client_1.isApiError)(response)) {
357
+ if ((0, api_client_1.needsAuthError)(response)) {
358
+ const authResult = await (0, device_flow_1.ensureAuthenticated)();
359
+ if (!authResult.authenticated)
360
+ return authResult.message;
361
+ const retry = await (0, api_client_1.apiRequest)(`/api/collections/${collectionResult.collectionId}/items/${existingItem.id}`, { tenantId: prep.tenantId, method: 'DELETE' });
362
+ if ((0, api_client_1.isApiError)(retry)) {
363
+ return `# Error Deleting Item\n\n${retry.error}\n\n**Status:** ${retry.statusCode}`;
364
+ }
365
+ return `# Item Deleted
366
+
367
+ Successfully deleted item "${params.itemSlug}" from collection "${params.collectionSlug}".
368
+ `;
369
+ }
370
+ return `# Error Deleting Item\n\n${response.error}\n\n**Status:** ${response.statusCode}`;
371
+ }
372
+ return `# Item Deleted
373
+
374
+ Successfully deleted item "${params.itemSlug}" from collection "${params.collectionSlug}".
375
+ `;
376
+ }
@@ -0,0 +1,9 @@
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
+ * @param confirmCreate - Optional: Skip similar-name check (set to true after user confirms)
7
+ */
8
+ export declare function createSite(name: string, subdomain?: string, confirmCreate?: boolean): Promise<string>;
9
+ //# 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":"AAiEA;;;;;;GAMG;AACH,wBAAsB,UAAU,CAAC,IAAI,EAAE,MAAM,EAAE,SAAS,CAAC,EAAE,MAAM,EAAE,aAAa,CAAC,EAAE,OAAO,GAAG,OAAO,CAAC,MAAM,CAAC,CAwI3G"}
@@ -0,0 +1,202 @@
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
+ * Check for similar project names
8
+ */
9
+ function findSimilarProjects(name, projects) {
10
+ const normalizedName = name.toLowerCase().trim();
11
+ return projects.filter(project => {
12
+ const projectName = project.name.toLowerCase();
13
+ // Exact match
14
+ if (projectName === normalizedName)
15
+ return true;
16
+ // One contains the other
17
+ if (projectName.includes(normalizedName) || normalizedName.includes(projectName))
18
+ return true;
19
+ // Similar words (check if main words overlap)
20
+ const nameWords = normalizedName.split(/\s+/).filter(w => w.length > 2);
21
+ const projectWords = projectName.split(/\s+/).filter(w => w.length > 2);
22
+ const overlap = nameWords.filter(w => projectWords.some(pw => pw.includes(w) || w.includes(pw)));
23
+ if (overlap.length > 0 && overlap.length >= nameWords.length * 0.5)
24
+ return true;
25
+ return false;
26
+ });
27
+ }
28
+ /**
29
+ * List existing projects for the authenticated user
30
+ */
31
+ async function listExistingProjects() {
32
+ const response = await (0, api_client_1.apiRequest)('/api/tenants');
33
+ if ((0, api_client_1.isApiError)(response)) {
34
+ return [];
35
+ }
36
+ return response.data;
37
+ }
38
+ /**
39
+ * Create a new Fast Mode site/project
40
+ *
41
+ * @param name - The name of the project
42
+ * @param subdomain - Optional: Custom subdomain (auto-generated from name if not provided)
43
+ * @param confirmCreate - Optional: Skip similar-name check (set to true after user confirms)
44
+ */
45
+ async function createSite(name, subdomain, confirmCreate) {
46
+ // Check authentication
47
+ if (await (0, api_client_1.needsAuthentication)()) {
48
+ const authResult = await (0, device_flow_1.ensureAuthenticated)();
49
+ if (!authResult.authenticated) {
50
+ return authResult.message;
51
+ }
52
+ }
53
+ // Validate name
54
+ if (!name || name.trim().length === 0) {
55
+ return `# Invalid Name
56
+
57
+ Please provide a name for your project.
58
+
59
+ Example:
60
+ \`\`\`
61
+ create_site(name: "My Awesome Website")
62
+ \`\`\`
63
+ `;
64
+ }
65
+ // Check for similar projects (unless confirmCreate is true)
66
+ if (!confirmCreate) {
67
+ const existingProjects = await listExistingProjects();
68
+ const similarProjects = findSimilarProjects(name, existingProjects);
69
+ if (similarProjects.length > 0) {
70
+ let output = `# Similar Project Found
71
+
72
+ Before creating "${name}", please note that you have similar existing project(s):
73
+
74
+ `;
75
+ similarProjects.forEach((project, index) => {
76
+ const url = project.customDomain || `${project.subdomain}.fastmode.ai`;
77
+ output += `${index + 1}. **${project.name}**
78
+ - ID: \`${project.id}\`
79
+ - URL: ${url}
80
+
81
+ `;
82
+ });
83
+ output += `---
84
+
85
+ ## ACTION REQUIRED
86
+
87
+ **Ask the user:** "I found a similar project called '${similarProjects[0].name}'. Would you like to:
88
+ 1. Deploy to the existing project '${similarProjects[0].name}'?
89
+ 2. Create a new project called '${name}'?"
90
+
91
+ ### If deploying to existing project:
92
+ \`\`\`
93
+ deploy_package(
94
+ packagePath: "./your-site.zip",
95
+ projectId: "${similarProjects[0].id}"
96
+ )
97
+ \`\`\`
98
+
99
+ ### If creating new project anyway:
100
+ \`\`\`
101
+ create_site(name: "${name}", confirmCreate: true)
102
+ \`\`\`
103
+ `;
104
+ return output;
105
+ }
106
+ }
107
+ // Generate subdomain from name if not provided
108
+ const finalSubdomain = subdomain || name
109
+ .toLowerCase()
110
+ .replace(/[^a-z0-9]+/g, '-')
111
+ .replace(/^-|-$/g, '')
112
+ .slice(0, 30);
113
+ // Create the site
114
+ const response = await (0, api_client_1.apiRequest)('/api/tenants', {
115
+ method: 'POST',
116
+ body: {
117
+ name: name.trim(),
118
+ subdomain: finalSubdomain,
119
+ },
120
+ });
121
+ if ((0, api_client_1.isApiError)(response)) {
122
+ // Check for common errors
123
+ if (response.error.includes('subdomain') && response.error.includes('taken')) {
124
+ return `# Subdomain Already Taken
125
+
126
+ The subdomain "${finalSubdomain}" is already in use.
127
+
128
+ Try creating with a different subdomain:
129
+ \`\`\`
130
+ create_site(name: "${name}", subdomain: "${finalSubdomain}-2")
131
+ \`\`\`
132
+ `;
133
+ }
134
+ if (response.statusCode === 401) {
135
+ // Try to re-authenticate
136
+ const authResult = await (0, device_flow_1.ensureAuthenticated)();
137
+ if (!authResult.authenticated) {
138
+ return authResult.message;
139
+ }
140
+ // Retry
141
+ const retryResponse = await (0, api_client_1.apiRequest)('/api/tenants', {
142
+ method: 'POST',
143
+ body: {
144
+ name: name.trim(),
145
+ subdomain: finalSubdomain,
146
+ },
147
+ });
148
+ if ((0, api_client_1.isApiError)(retryResponse)) {
149
+ return `# Failed to Create Site
150
+
151
+ ${retryResponse.error}
152
+
153
+ Please try again or create manually at app.fastmode.ai
154
+ `;
155
+ }
156
+ return formatSuccess(retryResponse.data);
157
+ }
158
+ return `# Failed to Create Site
159
+
160
+ ${response.error}
161
+
162
+ Please try again or create manually at app.fastmode.ai
163
+ `;
164
+ }
165
+ return formatSuccess(response.data);
166
+ }
167
+ /**
168
+ * Format success message
169
+ */
170
+ function formatSuccess(site) {
171
+ return `# Site Created Successfully! 🎉
172
+
173
+ ## Your New Site
174
+
175
+ - **Name:** ${site.name}
176
+ - **URL:** https://${site.subdomain}.fastmode.ai
177
+ - **Project ID:** \`${site.id}\`
178
+
179
+ ## Next Steps
180
+
181
+ **Option 1: Deploy via MCP**
182
+ \`\`\`
183
+ deploy_package(
184
+ packagePath: "./your-site.zip",
185
+ projectId: "${site.id}"
186
+ )
187
+ \`\`\`
188
+
189
+ **Option 2: Connect GitHub**
190
+ 1. Go to app.fastmode.ai
191
+ 2. Open Settings → Connected Git Repository
192
+ 3. Connect your GitHub account and select a repo
193
+
194
+ **Option 3: Manual Upload**
195
+ 1. Go to app.fastmode.ai
196
+ 2. Upload your website package in the Editor
197
+
198
+ ---
199
+
200
+ Your site is ready at: https://${site.subdomain}.fastmode.ai
201
+ `;
202
+ }
@@ -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 - Project ID to deploy to (use list_projects to find, or create_site to create new)
6
+ * @param force - Optional: Skip GitHub connection check and deploy anyway
7
+ */
8
+ export declare function deployPackage(packagePath: string, projectId?: string, force?: boolean): 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":"AA4QA;;;;;;GAMG;AACH,wBAAsB,aAAa,CACjC,WAAW,EAAE,MAAM,EACnB,SAAS,CAAC,EAAE,MAAM,EAClB,KAAK,CAAC,EAAE,OAAO,GACd,OAAO,CAAC,MAAM,CAAC,CA2LjB"}