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.
@@ -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;