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.
- package/README.md +561 -0
- package/bin/run.js +50 -0
- package/dist/index.d.ts +3 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +802 -0
- package/dist/lib/api-client.d.ts +81 -0
- package/dist/lib/api-client.d.ts.map +1 -0
- package/dist/lib/api-client.js +237 -0
- package/dist/lib/auth-state.d.ts +13 -0
- package/dist/lib/auth-state.d.ts.map +1 -0
- package/dist/lib/auth-state.js +24 -0
- package/dist/lib/context-fetcher.d.ts +67 -0
- package/dist/lib/context-fetcher.d.ts.map +1 -0
- package/dist/lib/context-fetcher.js +190 -0
- package/dist/lib/credentials.d.ts +52 -0
- package/dist/lib/credentials.d.ts.map +1 -0
- package/dist/lib/credentials.js +196 -0
- package/dist/lib/device-flow.d.ts +14 -0
- package/dist/lib/device-flow.d.ts.map +1 -0
- package/dist/lib/device-flow.js +244 -0
- package/dist/tools/cms-items.d.ts +56 -0
- package/dist/tools/cms-items.d.ts.map +1 -0
- package/dist/tools/cms-items.js +376 -0
- package/dist/tools/create-site.d.ts +9 -0
- package/dist/tools/create-site.d.ts.map +1 -0
- package/dist/tools/create-site.js +202 -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 +434 -0
- package/dist/tools/generate-samples.d.ts +19 -0
- package/dist/tools/generate-samples.d.ts.map +1 -0
- package/dist/tools/generate-samples.js +272 -0
- package/dist/tools/get-conversion-guide.d.ts +7 -0
- package/dist/tools/get-conversion-guide.d.ts.map +1 -0
- package/dist/tools/get-conversion-guide.js +1323 -0
- package/dist/tools/get-example.d.ts +7 -0
- package/dist/tools/get-example.d.ts.map +1 -0
- package/dist/tools/get-example.js +1568 -0
- package/dist/tools/get-field-types.d.ts +30 -0
- package/dist/tools/get-field-types.d.ts.map +1 -0
- package/dist/tools/get-field-types.js +154 -0
- package/dist/tools/get-schema.d.ts +5 -0
- package/dist/tools/get-schema.d.ts.map +1 -0
- package/dist/tools/get-schema.js +320 -0
- package/dist/tools/get-started.d.ts +21 -0
- package/dist/tools/get-started.d.ts.map +1 -0
- package/dist/tools/get-started.js +624 -0
- package/dist/tools/get-tenant-schema.d.ts +18 -0
- package/dist/tools/get-tenant-schema.d.ts.map +1 -0
- package/dist/tools/get-tenant-schema.js +158 -0
- package/dist/tools/list-projects.d.ts +5 -0
- package/dist/tools/list-projects.d.ts.map +1 -0
- package/dist/tools/list-projects.js +101 -0
- package/dist/tools/sync-schema.d.ts +41 -0
- package/dist/tools/sync-schema.d.ts.map +1 -0
- package/dist/tools/sync-schema.js +483 -0
- package/dist/tools/validate-manifest.d.ts +5 -0
- package/dist/tools/validate-manifest.d.ts.map +1 -0
- package/dist/tools/validate-manifest.js +311 -0
- package/dist/tools/validate-package.d.ts +5 -0
- package/dist/tools/validate-package.d.ts.map +1 -0
- package/dist/tools/validate-package.js +337 -0
- package/dist/tools/validate-template.d.ts +12 -0
- package/dist/tools/validate-template.d.ts.map +1 -0
- package/dist/tools/validate-template.js +790 -0
- package/package.json +54 -0
- 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"}
|