myaidev-method 0.1.0 → 0.2.1
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/.env.example +20 -0
- package/PUBLISHING_GUIDE.md +521 -0
- package/README.md +123 -25
- package/bin/cli.js +89 -49
- package/package.json +25 -3
- package/src/lib/payloadcms-utils.js +394 -0
- package/src/lib/static-site-utils.js +377 -0
- package/src/scripts/astro-publish.js +209 -0
- package/src/scripts/docusaurus-publish.js +209 -0
- package/src/scripts/init-project.js +91 -0
- package/src/scripts/mintlify-publish.js +205 -0
- package/src/scripts/payloadcms-publish.js +202 -0
- package/src/templates/claude/agents/astro-publish.md +43 -0
- package/src/templates/claude/agents/docusaurus-publish.md +42 -0
- package/src/templates/claude/agents/mintlify-publish.md +42 -0
- package/src/templates/claude/agents/payloadcms-publish.md +134 -0
- package/src/templates/claude/commands/myai-astro-publish.md +54 -0
- package/src/templates/claude/commands/myai-docusaurus-publish.md +45 -0
- package/src/templates/claude/commands/myai-mintlify-publish.md +45 -0
- package/src/templates/claude/commands/myai-payloadcms-publish.md +45 -0
|
@@ -0,0 +1,394 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* PayloadCMS API Utilities
|
|
3
|
+
* Reusable functions for PayloadCMS content publishing and management
|
|
4
|
+
* Optimized for Claude Code 2.0 agent integration
|
|
5
|
+
*/
|
|
6
|
+
|
|
7
|
+
import fetch from 'node-fetch';
|
|
8
|
+
import { readFileSync } from 'fs';
|
|
9
|
+
import { parse } from 'dotenv';
|
|
10
|
+
import { marked } from 'marked';
|
|
11
|
+
|
|
12
|
+
export class PayloadCMSUtils {
|
|
13
|
+
constructor(config = {}) {
|
|
14
|
+
// Load config from .env if not provided
|
|
15
|
+
if (!config.url || (!config.email && !config.apiKey)) {
|
|
16
|
+
const envConfig = this.loadEnvConfig();
|
|
17
|
+
config = { ...envConfig, ...config };
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
this.url = config.url?.replace(/\/$/, '');
|
|
21
|
+
this.email = config.email;
|
|
22
|
+
this.password = config.password;
|
|
23
|
+
this.apiKey = config.apiKey;
|
|
24
|
+
this.token = null;
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
loadEnvConfig() {
|
|
28
|
+
try {
|
|
29
|
+
const envPath = process.env.ENV_PATH || '.env';
|
|
30
|
+
const envContent = readFileSync(envPath, 'utf8');
|
|
31
|
+
const parsed = parse(envContent);
|
|
32
|
+
|
|
33
|
+
return {
|
|
34
|
+
url: parsed.PAYLOADCMS_URL,
|
|
35
|
+
email: parsed.PAYLOADCMS_EMAIL,
|
|
36
|
+
password: parsed.PAYLOADCMS_PASSWORD,
|
|
37
|
+
apiKey: parsed.PAYLOADCMS_API_KEY
|
|
38
|
+
};
|
|
39
|
+
} catch (error) {
|
|
40
|
+
throw new Error(`Failed to load PayloadCMS configuration: ${error.message}`);
|
|
41
|
+
}
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
/**
|
|
45
|
+
* Authenticate with PayloadCMS and get JWT token
|
|
46
|
+
*/
|
|
47
|
+
async authenticate() {
|
|
48
|
+
// If API key is provided, use that instead
|
|
49
|
+
if (this.apiKey) {
|
|
50
|
+
this.token = this.apiKey;
|
|
51
|
+
return { success: true, method: 'api-key' };
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
if (!this.email || !this.password) {
|
|
55
|
+
throw new Error('Email and password required for authentication');
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
try {
|
|
59
|
+
const response = await fetch(`${this.url}/api/users/login`, {
|
|
60
|
+
method: 'POST',
|
|
61
|
+
headers: {
|
|
62
|
+
'Content-Type': 'application/json'
|
|
63
|
+
},
|
|
64
|
+
body: JSON.stringify({
|
|
65
|
+
email: this.email,
|
|
66
|
+
password: this.password
|
|
67
|
+
})
|
|
68
|
+
});
|
|
69
|
+
|
|
70
|
+
if (!response.ok) {
|
|
71
|
+
const error = await response.text();
|
|
72
|
+
throw new Error(`Authentication failed: ${error}`);
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
const data = await response.json();
|
|
76
|
+
this.token = data.token;
|
|
77
|
+
|
|
78
|
+
return {
|
|
79
|
+
success: true,
|
|
80
|
+
method: 'jwt',
|
|
81
|
+
user: data.user,
|
|
82
|
+
token: this.token
|
|
83
|
+
};
|
|
84
|
+
} catch (error) {
|
|
85
|
+
throw new Error(`PayloadCMS authentication failed: ${error.message}`);
|
|
86
|
+
}
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
/**
|
|
90
|
+
* Make authenticated API request
|
|
91
|
+
*/
|
|
92
|
+
async request(endpoint, options = {}) {
|
|
93
|
+
if (!this.token) {
|
|
94
|
+
await this.authenticate();
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
const url = `${this.url}${endpoint}`;
|
|
98
|
+
const headers = {
|
|
99
|
+
'Content-Type': 'application/json',
|
|
100
|
+
'Authorization': `JWT ${this.token}`,
|
|
101
|
+
...options.headers
|
|
102
|
+
};
|
|
103
|
+
|
|
104
|
+
try {
|
|
105
|
+
const response = await fetch(url, {
|
|
106
|
+
...options,
|
|
107
|
+
headers
|
|
108
|
+
});
|
|
109
|
+
|
|
110
|
+
const data = await response.json().catch(() => ({}));
|
|
111
|
+
|
|
112
|
+
if (!response.ok) {
|
|
113
|
+
throw new Error(`HTTP ${response.status}: ${data.message || response.statusText}`);
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
return data;
|
|
117
|
+
} catch (error) {
|
|
118
|
+
throw new Error(`PayloadCMS API request failed: ${error.message}`);
|
|
119
|
+
}
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
/**
|
|
123
|
+
* Convert markdown to Lexical rich text format
|
|
124
|
+
* PayloadCMS uses Lexical editor by default
|
|
125
|
+
*/
|
|
126
|
+
convertMarkdownToLexical(markdown) {
|
|
127
|
+
const tokens = marked.lexer(markdown);
|
|
128
|
+
|
|
129
|
+
const convertToken = (token) => {
|
|
130
|
+
switch (token.type) {
|
|
131
|
+
case 'heading':
|
|
132
|
+
return {
|
|
133
|
+
type: 'heading',
|
|
134
|
+
tag: `h${token.depth}`,
|
|
135
|
+
children: [{ type: 'text', text: token.text, format: 0 }],
|
|
136
|
+
direction: 'ltr',
|
|
137
|
+
format: '',
|
|
138
|
+
indent: 0,
|
|
139
|
+
version: 1
|
|
140
|
+
};
|
|
141
|
+
|
|
142
|
+
case 'paragraph':
|
|
143
|
+
return {
|
|
144
|
+
type: 'paragraph',
|
|
145
|
+
children: this.parseInlineText(token.text),
|
|
146
|
+
direction: 'ltr',
|
|
147
|
+
format: '',
|
|
148
|
+
indent: 0,
|
|
149
|
+
version: 1
|
|
150
|
+
};
|
|
151
|
+
|
|
152
|
+
case 'list':
|
|
153
|
+
return {
|
|
154
|
+
type: token.ordered ? 'number' : 'bullet',
|
|
155
|
+
listType: token.ordered ? 'number' : 'bullet',
|
|
156
|
+
start: token.start || 1,
|
|
157
|
+
tag: token.ordered ? 'ol' : 'ul',
|
|
158
|
+
children: token.items.map(item => ({
|
|
159
|
+
type: 'listitem',
|
|
160
|
+
value: 1,
|
|
161
|
+
children: [{
|
|
162
|
+
type: 'paragraph',
|
|
163
|
+
children: this.parseInlineText(item.text),
|
|
164
|
+
direction: 'ltr',
|
|
165
|
+
format: '',
|
|
166
|
+
indent: 0
|
|
167
|
+
}],
|
|
168
|
+
direction: 'ltr',
|
|
169
|
+
format: '',
|
|
170
|
+
indent: 0
|
|
171
|
+
})),
|
|
172
|
+
direction: 'ltr',
|
|
173
|
+
format: '',
|
|
174
|
+
indent: 0,
|
|
175
|
+
version: 1
|
|
176
|
+
};
|
|
177
|
+
|
|
178
|
+
case 'code':
|
|
179
|
+
return {
|
|
180
|
+
type: 'code',
|
|
181
|
+
language: token.lang || 'plaintext',
|
|
182
|
+
children: [{ type: 'text', text: token.text, format: 0 }],
|
|
183
|
+
direction: 'ltr',
|
|
184
|
+
format: '',
|
|
185
|
+
indent: 0,
|
|
186
|
+
version: 1
|
|
187
|
+
};
|
|
188
|
+
|
|
189
|
+
case 'blockquote':
|
|
190
|
+
return {
|
|
191
|
+
type: 'quote',
|
|
192
|
+
children: [{
|
|
193
|
+
type: 'paragraph',
|
|
194
|
+
children: this.parseInlineText(token.text),
|
|
195
|
+
direction: 'ltr',
|
|
196
|
+
format: '',
|
|
197
|
+
indent: 0
|
|
198
|
+
}],
|
|
199
|
+
direction: 'ltr',
|
|
200
|
+
format: '',
|
|
201
|
+
indent: 0,
|
|
202
|
+
version: 1
|
|
203
|
+
};
|
|
204
|
+
|
|
205
|
+
case 'hr':
|
|
206
|
+
return {
|
|
207
|
+
type: 'horizontalrule',
|
|
208
|
+
version: 1
|
|
209
|
+
};
|
|
210
|
+
|
|
211
|
+
default:
|
|
212
|
+
return {
|
|
213
|
+
type: 'paragraph',
|
|
214
|
+
children: [{ type: 'text', text: token.raw || '', format: 0 }],
|
|
215
|
+
direction: 'ltr',
|
|
216
|
+
format: '',
|
|
217
|
+
indent: 0,
|
|
218
|
+
version: 1
|
|
219
|
+
};
|
|
220
|
+
}
|
|
221
|
+
};
|
|
222
|
+
|
|
223
|
+
const children = tokens.map(convertToken);
|
|
224
|
+
|
|
225
|
+
return {
|
|
226
|
+
root: {
|
|
227
|
+
type: 'root',
|
|
228
|
+
format: '',
|
|
229
|
+
indent: 0,
|
|
230
|
+
version: 1,
|
|
231
|
+
children,
|
|
232
|
+
direction: 'ltr'
|
|
233
|
+
}
|
|
234
|
+
};
|
|
235
|
+
}
|
|
236
|
+
|
|
237
|
+
/**
|
|
238
|
+
* Parse inline text with formatting (bold, italic, code, links)
|
|
239
|
+
*/
|
|
240
|
+
parseInlineText(text) {
|
|
241
|
+
// Simple inline parser for bold, italic, code, links
|
|
242
|
+
const children = [];
|
|
243
|
+
|
|
244
|
+
// For now, return simple text node
|
|
245
|
+
// TODO: Enhance with full inline formatting support
|
|
246
|
+
children.push({
|
|
247
|
+
type: 'text',
|
|
248
|
+
text: text.replace(/<[^>]+>/g, ''), // Strip HTML tags
|
|
249
|
+
format: 0,
|
|
250
|
+
mode: 'normal',
|
|
251
|
+
style: '',
|
|
252
|
+
detail: 0,
|
|
253
|
+
version: 1
|
|
254
|
+
});
|
|
255
|
+
|
|
256
|
+
return children;
|
|
257
|
+
}
|
|
258
|
+
|
|
259
|
+
/**
|
|
260
|
+
* List all collections
|
|
261
|
+
*/
|
|
262
|
+
async listCollections() {
|
|
263
|
+
return await this.request('/api');
|
|
264
|
+
}
|
|
265
|
+
|
|
266
|
+
/**
|
|
267
|
+
* Get documents from a collection
|
|
268
|
+
*/
|
|
269
|
+
async getDocuments(collection, options = {}) {
|
|
270
|
+
const params = new URLSearchParams();
|
|
271
|
+
if (options.limit) params.append('limit', options.limit);
|
|
272
|
+
if (options.page) params.append('page', options.page);
|
|
273
|
+
if (options.where) params.append('where', JSON.stringify(options.where));
|
|
274
|
+
|
|
275
|
+
const query = params.toString() ? `?${params.toString()}` : '';
|
|
276
|
+
return await this.request(`/api/${collection}${query}`);
|
|
277
|
+
}
|
|
278
|
+
|
|
279
|
+
/**
|
|
280
|
+
* Get a single document by ID
|
|
281
|
+
*/
|
|
282
|
+
async getDocument(collection, id) {
|
|
283
|
+
return await this.request(`/api/${collection}/${id}`);
|
|
284
|
+
}
|
|
285
|
+
|
|
286
|
+
/**
|
|
287
|
+
* Create a new document in a collection
|
|
288
|
+
*/
|
|
289
|
+
async createDocument(collection, data) {
|
|
290
|
+
return await this.request(`/api/${collection}`, {
|
|
291
|
+
method: 'POST',
|
|
292
|
+
body: JSON.stringify(data)
|
|
293
|
+
});
|
|
294
|
+
}
|
|
295
|
+
|
|
296
|
+
/**
|
|
297
|
+
* Update an existing document
|
|
298
|
+
*/
|
|
299
|
+
async updateDocument(collection, id, data) {
|
|
300
|
+
return await this.request(`/api/${collection}/${id}`, {
|
|
301
|
+
method: 'PATCH',
|
|
302
|
+
body: JSON.stringify(data)
|
|
303
|
+
});
|
|
304
|
+
}
|
|
305
|
+
|
|
306
|
+
/**
|
|
307
|
+
* Delete a document
|
|
308
|
+
*/
|
|
309
|
+
async deleteDocument(collection, id) {
|
|
310
|
+
return await this.request(`/api/${collection}/${id}`, {
|
|
311
|
+
method: 'DELETE'
|
|
312
|
+
});
|
|
313
|
+
}
|
|
314
|
+
|
|
315
|
+
/**
|
|
316
|
+
* Upload media file
|
|
317
|
+
*/
|
|
318
|
+
async uploadMedia(filePath, altText = '') {
|
|
319
|
+
if (!this.token) {
|
|
320
|
+
await this.authenticate();
|
|
321
|
+
}
|
|
322
|
+
|
|
323
|
+
const FormData = (await import('form-data')).default;
|
|
324
|
+
const fs = await import('fs');
|
|
325
|
+
|
|
326
|
+
const formData = new FormData();
|
|
327
|
+
formData.append('file', fs.createReadStream(filePath));
|
|
328
|
+
if (altText) formData.append('alt', altText);
|
|
329
|
+
|
|
330
|
+
try {
|
|
331
|
+
const response = await fetch(`${this.url}/api/media`, {
|
|
332
|
+
method: 'POST',
|
|
333
|
+
headers: {
|
|
334
|
+
'Authorization': `JWT ${this.token}`,
|
|
335
|
+
...formData.getHeaders()
|
|
336
|
+
},
|
|
337
|
+
body: formData
|
|
338
|
+
});
|
|
339
|
+
|
|
340
|
+
if (!response.ok) {
|
|
341
|
+
const error = await response.text();
|
|
342
|
+
throw new Error(`Upload failed: ${error}`);
|
|
343
|
+
}
|
|
344
|
+
|
|
345
|
+
return await response.json();
|
|
346
|
+
} catch (error) {
|
|
347
|
+
throw new Error(`Media upload failed: ${error.message}`);
|
|
348
|
+
}
|
|
349
|
+
}
|
|
350
|
+
|
|
351
|
+
/**
|
|
352
|
+
* Publish markdown content to PayloadCMS
|
|
353
|
+
*/
|
|
354
|
+
async publishContent(markdownFile, collection, options = {}) {
|
|
355
|
+
const fs = await import('fs');
|
|
356
|
+
const grayMatter = (await import('gray-matter')).default;
|
|
357
|
+
|
|
358
|
+
// Read and parse markdown file
|
|
359
|
+
const fileContent = fs.readFileSync(markdownFile, 'utf8');
|
|
360
|
+
const { data: frontmatter, content } = grayMatter(fileContent);
|
|
361
|
+
|
|
362
|
+
// Convert markdown to Lexical format
|
|
363
|
+
const richText = this.convertMarkdownToLexical(content);
|
|
364
|
+
|
|
365
|
+
// Prepare document data
|
|
366
|
+
const documentData = {
|
|
367
|
+
...frontmatter,
|
|
368
|
+
content: richText,
|
|
369
|
+
status: options.status || frontmatter.status || 'draft',
|
|
370
|
+
...options.additionalFields
|
|
371
|
+
};
|
|
372
|
+
|
|
373
|
+
// Create or update document
|
|
374
|
+
if (options.id) {
|
|
375
|
+
return await this.updateDocument(collection, options.id, documentData);
|
|
376
|
+
} else {
|
|
377
|
+
return await this.createDocument(collection, documentData);
|
|
378
|
+
}
|
|
379
|
+
}
|
|
380
|
+
|
|
381
|
+
/**
|
|
382
|
+
* Health check
|
|
383
|
+
*/
|
|
384
|
+
async healthCheck() {
|
|
385
|
+
try {
|
|
386
|
+
const response = await fetch(`${this.url}/api`);
|
|
387
|
+
return response.ok;
|
|
388
|
+
} catch (error) {
|
|
389
|
+
return false;
|
|
390
|
+
}
|
|
391
|
+
}
|
|
392
|
+
}
|
|
393
|
+
|
|
394
|
+
export default PayloadCMSUtils;
|