mcp-wordpress 1.1.2

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 (134) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +568 -0
  3. package/bin/mcp-wordpress.js +12 -0
  4. package/bin/setup.js +302 -0
  5. package/bin/status.js +359 -0
  6. package/dist/client/WordPressClient.d.ts +81 -0
  7. package/dist/client/WordPressClient.d.ts.map +1 -0
  8. package/dist/client/WordPressClient.js +354 -0
  9. package/dist/client/WordPressClient.js.map +1 -0
  10. package/dist/client/api.d.ts +140 -0
  11. package/dist/client/api.d.ts.map +1 -0
  12. package/dist/client/api.js +727 -0
  13. package/dist/client/api.js.map +1 -0
  14. package/dist/client/auth.d.ts +121 -0
  15. package/dist/client/auth.d.ts.map +1 -0
  16. package/dist/client/auth.js +430 -0
  17. package/dist/client/auth.js.map +1 -0
  18. package/dist/client/managers/AuthenticationManager.d.ts +39 -0
  19. package/dist/client/managers/AuthenticationManager.d.ts.map +1 -0
  20. package/dist/client/managers/AuthenticationManager.js +159 -0
  21. package/dist/client/managers/AuthenticationManager.js.map +1 -0
  22. package/dist/client/managers/BaseManager.d.ts +22 -0
  23. package/dist/client/managers/BaseManager.d.ts.map +1 -0
  24. package/dist/client/managers/BaseManager.js +47 -0
  25. package/dist/client/managers/BaseManager.js.map +1 -0
  26. package/dist/client/managers/RequestManager.d.ts +45 -0
  27. package/dist/client/managers/RequestManager.d.ts.map +1 -0
  28. package/dist/client/managers/RequestManager.js +161 -0
  29. package/dist/client/managers/RequestManager.js.map +1 -0
  30. package/dist/client/managers/index.d.ts +8 -0
  31. package/dist/client/managers/index.d.ts.map +1 -0
  32. package/dist/client/managers/index.js +8 -0
  33. package/dist/client/managers/index.js.map +1 -0
  34. package/dist/index.d.ts +19 -0
  35. package/dist/index.d.ts.map +1 -0
  36. package/dist/index.js +264 -0
  37. package/dist/index.js.map +1 -0
  38. package/dist/server.d.ts +7 -0
  39. package/dist/server.d.ts.map +1 -0
  40. package/dist/server.js +7 -0
  41. package/dist/server.js.map +1 -0
  42. package/dist/tools/auth.d.ts +44 -0
  43. package/dist/tools/auth.d.ts.map +1 -0
  44. package/dist/tools/auth.js +126 -0
  45. package/dist/tools/auth.js.map +1 -0
  46. package/dist/tools/base.d.ts +37 -0
  47. package/dist/tools/base.d.ts.map +1 -0
  48. package/dist/tools/base.js +60 -0
  49. package/dist/tools/base.js.map +1 -0
  50. package/dist/tools/comments.d.ts +33 -0
  51. package/dist/tools/comments.d.ts.map +1 -0
  52. package/dist/tools/comments.js +228 -0
  53. package/dist/tools/comments.js.map +1 -0
  54. package/dist/tools/index.d.ts +9 -0
  55. package/dist/tools/index.d.ts.map +1 -0
  56. package/dist/tools/index.js +9 -0
  57. package/dist/tools/index.js.map +1 -0
  58. package/dist/tools/media.d.ts +29 -0
  59. package/dist/tools/media.d.ts.map +1 -0
  60. package/dist/tools/media.js +208 -0
  61. package/dist/tools/media.js.map +1 -0
  62. package/dist/tools/pages.d.ts +30 -0
  63. package/dist/tools/pages.d.ts.map +1 -0
  64. package/dist/tools/pages.js +211 -0
  65. package/dist/tools/pages.js.map +1 -0
  66. package/dist/tools/posts.d.ts +30 -0
  67. package/dist/tools/posts.d.ts.map +1 -0
  68. package/dist/tools/posts.js +240 -0
  69. package/dist/tools/posts.js.map +1 -0
  70. package/dist/tools/site.d.ts +31 -0
  71. package/dist/tools/site.d.ts.map +1 -0
  72. package/dist/tools/site.js +192 -0
  73. package/dist/tools/site.js.map +1 -0
  74. package/dist/tools/taxonomies.d.ts +37 -0
  75. package/dist/tools/taxonomies.d.ts.map +1 -0
  76. package/dist/tools/taxonomies.js +280 -0
  77. package/dist/tools/taxonomies.js.map +1 -0
  78. package/dist/tools/users.d.ts +28 -0
  79. package/dist/tools/users.d.ts.map +1 -0
  80. package/dist/tools/users.js +201 -0
  81. package/dist/tools/users.js.map +1 -0
  82. package/dist/types/client.d.ts +215 -0
  83. package/dist/types/client.d.ts.map +1 -0
  84. package/dist/types/client.js +72 -0
  85. package/dist/types/client.js.map +1 -0
  86. package/dist/types/index.d.ts +157 -0
  87. package/dist/types/index.d.ts.map +1 -0
  88. package/dist/types/index.js +12 -0
  89. package/dist/types/index.js.map +1 -0
  90. package/dist/types/mcp.d.ts +178 -0
  91. package/dist/types/mcp.d.ts.map +1 -0
  92. package/dist/types/mcp.js +7 -0
  93. package/dist/types/mcp.js.map +1 -0
  94. package/dist/types/wordpress.d.ts +443 -0
  95. package/dist/types/wordpress.d.ts.map +1 -0
  96. package/dist/types/wordpress.js +7 -0
  97. package/dist/types/wordpress.js.map +1 -0
  98. package/dist/utils/debug.d.ts +63 -0
  99. package/dist/utils/debug.d.ts.map +1 -0
  100. package/dist/utils/debug.js +195 -0
  101. package/dist/utils/debug.js.map +1 -0
  102. package/dist/utils/error.d.ts +19 -0
  103. package/dist/utils/error.d.ts.map +1 -0
  104. package/dist/utils/error.js +71 -0
  105. package/dist/utils/error.js.map +1 -0
  106. package/dist/utils/toolWrapper.d.ts +36 -0
  107. package/dist/utils/toolWrapper.d.ts.map +1 -0
  108. package/dist/utils/toolWrapper.js +90 -0
  109. package/dist/utils/toolWrapper.js.map +1 -0
  110. package/package.json +115 -0
  111. package/src/client/api.ts +1043 -0
  112. package/src/client/auth.ts +527 -0
  113. package/src/client/managers/AuthenticationManager.ts +190 -0
  114. package/src/client/managers/BaseManager.ts +73 -0
  115. package/src/client/managers/RequestManager.ts +214 -0
  116. package/src/client/managers/index.ts +8 -0
  117. package/src/index.ts +337 -0
  118. package/src/server.ts +7 -0
  119. package/src/tools/auth.ts +153 -0
  120. package/src/tools/comments.ts +263 -0
  121. package/src/tools/index.ts +8 -0
  122. package/src/tools/media.ts +240 -0
  123. package/src/tools/pages.ts +246 -0
  124. package/src/tools/posts.ts +277 -0
  125. package/src/tools/site.ts +227 -0
  126. package/src/tools/taxonomies.ts +322 -0
  127. package/src/tools/users.ts +233 -0
  128. package/src/types/client.ts +304 -0
  129. package/src/types/index.ts +207 -0
  130. package/src/types/mcp.ts +247 -0
  131. package/src/types/wordpress.ts +491 -0
  132. package/src/utils/debug.ts +258 -0
  133. package/src/utils/error.ts +88 -0
  134. package/src/utils/toolWrapper.ts +105 -0
@@ -0,0 +1,727 @@
1
+ /**
2
+ * WordPress API Client
3
+ * Handles all REST API communication with WordPress
4
+ */
5
+ import fetch from "node-fetch";
6
+ import FormData from "form-data";
7
+ import * as fs from "fs";
8
+ import * as path from "path";
9
+ import { WordPressAPIError, AuthenticationError, RateLimitError, } from "../types/client.js";
10
+ import { debug, logError, startTimer } from "../utils/debug.js";
11
+ export class WordPressClient {
12
+ baseUrl;
13
+ apiUrl;
14
+ timeout;
15
+ maxRetries;
16
+ auth;
17
+ requestQueue = [];
18
+ lastRequestTime = 0;
19
+ requestInterval;
20
+ authenticated = false;
21
+ jwtToken = null;
22
+ _stats;
23
+ constructor(options = {}) {
24
+ this.baseUrl = options.baseUrl || process.env.WORDPRESS_SITE_URL || "";
25
+ this.apiUrl = "";
26
+ this.timeout =
27
+ options.timeout || parseInt(process.env.WORDPRESS_TIMEOUT || "30000");
28
+ this.maxRetries =
29
+ options.maxRetries || parseInt(process.env.WORDPRESS_MAX_RETRIES || "3");
30
+ // Authentication configuration
31
+ this.auth = options.auth || this.getAuthFromEnv();
32
+ // Rate limiting
33
+ this.requestInterval = 60000 / parseInt(process.env.RATE_LIMIT || "60");
34
+ // Initialize stats
35
+ this._stats = {
36
+ totalRequests: 0,
37
+ successfulRequests: 0,
38
+ failedRequests: 0,
39
+ averageResponseTime: 0,
40
+ rateLimitHits: 0,
41
+ authFailures: 0,
42
+ };
43
+ // Validate configuration
44
+ this.validateConfig();
45
+ }
46
+ get config() {
47
+ return {
48
+ baseUrl: this.baseUrl,
49
+ auth: this.auth,
50
+ timeout: this.timeout,
51
+ maxRetries: this.maxRetries,
52
+ };
53
+ }
54
+ get isAuthenticated() {
55
+ return this.authenticated;
56
+ }
57
+ get stats() {
58
+ return { ...this._stats };
59
+ }
60
+ getAuthFromEnv() {
61
+ const authMethod = process.env.WORDPRESS_AUTH_METHOD;
62
+ // Use explicit auth method if set
63
+ if (authMethod === "app-password" &&
64
+ process.env.WORDPRESS_USERNAME &&
65
+ process.env.WORDPRESS_APP_PASSWORD) {
66
+ return {
67
+ method: "app-password",
68
+ username: process.env.WORDPRESS_USERNAME,
69
+ appPassword: process.env.WORDPRESS_APP_PASSWORD,
70
+ };
71
+ }
72
+ // Try Application Password first (fallback)
73
+ if (process.env.WORDPRESS_USERNAME && process.env.WORDPRESS_APP_PASSWORD) {
74
+ return {
75
+ method: "app-password",
76
+ username: process.env.WORDPRESS_USERNAME,
77
+ appPassword: process.env.WORDPRESS_APP_PASSWORD,
78
+ };
79
+ }
80
+ // Try JWT
81
+ if (process.env.WORDPRESS_JWT_SECRET &&
82
+ process.env.WORDPRESS_USERNAME &&
83
+ process.env.WORDPRESS_PASSWORD) {
84
+ return {
85
+ method: "jwt",
86
+ secret: process.env.WORDPRESS_JWT_SECRET,
87
+ username: process.env.WORDPRESS_USERNAME,
88
+ password: process.env.WORDPRESS_PASSWORD,
89
+ };
90
+ }
91
+ // Try API Key
92
+ if (process.env.WORDPRESS_API_KEY) {
93
+ return {
94
+ method: "api-key",
95
+ apiKey: process.env.WORDPRESS_API_KEY,
96
+ };
97
+ }
98
+ // Try Cookie
99
+ if (process.env.WORDPRESS_COOKIE_NONCE) {
100
+ return {
101
+ method: "cookie",
102
+ nonce: process.env.WORDPRESS_COOKIE_NONCE,
103
+ };
104
+ }
105
+ // Default to basic authentication
106
+ return {
107
+ method: "basic",
108
+ username: process.env.WORDPRESS_USERNAME || "",
109
+ password: process.env.WORDPRESS_PASSWORD ||
110
+ process.env.WORDPRESS_APP_PASSWORD ||
111
+ "",
112
+ };
113
+ }
114
+ validateConfig() {
115
+ if (!this.baseUrl) {
116
+ throw new Error("WordPress configuration is incomplete: baseUrl is required");
117
+ }
118
+ // Ensure URL doesn't end with slash and add API path
119
+ this.baseUrl = this.baseUrl.replace(/\/$/, "");
120
+ this.apiUrl = `${this.baseUrl}/wp-json/wp/v2`;
121
+ debug.log(`WordPress API Client initialized for: ${this.apiUrl}`);
122
+ }
123
+ async initialize() {
124
+ await this.authenticate();
125
+ }
126
+ async disconnect() {
127
+ this.authenticated = false;
128
+ this.jwtToken = null;
129
+ debug.log("WordPress client disconnected");
130
+ }
131
+ /**
132
+ * Add authentication headers to request
133
+ */
134
+ addAuthHeaders(headers) {
135
+ const method = this.auth.method?.toLowerCase();
136
+ switch (method) {
137
+ case "app-password":
138
+ if (this.auth.username && this.auth.appPassword) {
139
+ const credentials = Buffer.from(`${this.auth.username}:${this.auth.appPassword}`).toString("base64");
140
+ headers["Authorization"] = `Basic ${credentials}`;
141
+ }
142
+ break;
143
+ case "basic":
144
+ if (this.auth.username && this.auth.password) {
145
+ const credentials = Buffer.from(`${this.auth.username}:${this.auth.password}`).toString("base64");
146
+ headers["Authorization"] = `Basic ${credentials}`;
147
+ }
148
+ break;
149
+ case "jwt":
150
+ if (this.jwtToken) {
151
+ headers["Authorization"] = `Bearer ${this.jwtToken}`;
152
+ }
153
+ break;
154
+ case "api-key":
155
+ if (this.auth.apiKey) {
156
+ headers["X-API-Key"] = this.auth.apiKey;
157
+ }
158
+ break;
159
+ case "cookie":
160
+ if (this.auth.nonce) {
161
+ headers["X-WP-Nonce"] = this.auth.nonce;
162
+ }
163
+ break;
164
+ }
165
+ }
166
+ /**
167
+ * Rate limiting implementation
168
+ */
169
+ async rateLimit() {
170
+ const now = Date.now();
171
+ const timeSinceLastRequest = now - this.lastRequestTime;
172
+ if (timeSinceLastRequest < this.requestInterval) {
173
+ const delay = this.requestInterval - timeSinceLastRequest;
174
+ await this.delay(delay);
175
+ }
176
+ this.lastRequestTime = Date.now();
177
+ }
178
+ /**
179
+ * Delay utility
180
+ */
181
+ delay(ms) {
182
+ return new Promise((resolve) => setTimeout(resolve, ms));
183
+ }
184
+ async authenticate() {
185
+ const method = this.auth.method?.toLowerCase();
186
+ try {
187
+ switch (method) {
188
+ case "app-password":
189
+ case "basic":
190
+ return await this.authenticateWithBasic();
191
+ case "jwt":
192
+ return await this.authenticateWithJWT();
193
+ case "cookie":
194
+ return await this.authenticateWithCookie();
195
+ case "api-key":
196
+ // API key auth doesn't require separate authentication step
197
+ this.authenticated = true;
198
+ return true;
199
+ default:
200
+ throw new Error(`Unsupported authentication method: ${method}`);
201
+ }
202
+ }
203
+ catch (error) {
204
+ this._stats.authFailures++;
205
+ logError(error, { method });
206
+ throw error;
207
+ }
208
+ }
209
+ /**
210
+ * Authenticate using Basic/Application Password
211
+ */
212
+ async authenticateWithBasic() {
213
+ const hasCredentials = this.auth.username &&
214
+ (this.auth.method === "app-password"
215
+ ? this.auth.appPassword
216
+ : this.auth.password);
217
+ if (!hasCredentials) {
218
+ const methodName = this.auth.method === "app-password" ? "Application Password" : "Basic";
219
+ const passwordField = this.auth.method === "app-password" ? "app password" : "password";
220
+ throw new AuthenticationError(`Username and ${passwordField} are required for ${methodName} authentication`, this.auth.method);
221
+ }
222
+ try {
223
+ // Test authentication by getting current user
224
+ await this.request("GET", "users/me");
225
+ this.authenticated = true;
226
+ debug.log("Basic/Application Password authentication successful");
227
+ return true;
228
+ }
229
+ catch (error) {
230
+ throw new AuthenticationError(`Basic authentication failed: ${error.message}`, this.auth.method);
231
+ }
232
+ }
233
+ /**
234
+ * Authenticate using JWT
235
+ */
236
+ async authenticateWithJWT() {
237
+ if (!this.auth.secret || !this.auth.username || !this.auth.password) {
238
+ throw new AuthenticationError("JWT secret, username, and password are required for JWT authentication", this.auth.method);
239
+ }
240
+ try {
241
+ const response = await fetch(`${this.baseUrl}/wp-json/jwt-auth/v1/token`, {
242
+ method: "POST",
243
+ headers: {
244
+ "Content-Type": "application/json",
245
+ },
246
+ body: JSON.stringify({
247
+ username: this.auth.username,
248
+ password: this.auth.password,
249
+ }),
250
+ });
251
+ if (!response.ok) {
252
+ throw new Error(`HTTP ${response.status}: ${response.statusText}`);
253
+ }
254
+ const data = (await response.json());
255
+ this.jwtToken = data.token;
256
+ this.authenticated = true;
257
+ debug.log("JWT authentication successful");
258
+ return true;
259
+ }
260
+ catch (error) {
261
+ throw new AuthenticationError(`JWT authentication failed: ${error.message}`, this.auth.method);
262
+ }
263
+ }
264
+ /**
265
+ * Authenticate using Cookie
266
+ */
267
+ async authenticateWithCookie() {
268
+ if (!this.auth.nonce) {
269
+ throw new AuthenticationError("Nonce is required for cookie authentication", this.auth.method);
270
+ }
271
+ this.authenticated = true;
272
+ debug.log("Cookie authentication configured");
273
+ return true;
274
+ }
275
+ /**
276
+ * Make authenticated request to WordPress REST API
277
+ */
278
+ async request(method, endpoint, data = null, options = {}) {
279
+ const timer = startTimer();
280
+ this._stats.totalRequests++;
281
+ // Handle endpoint properly - remove leading slash if present to avoid double slashes
282
+ const cleanEndpoint = endpoint.replace(/^\/+/, "");
283
+ const url = endpoint.startsWith("http")
284
+ ? endpoint
285
+ : `${this.apiUrl}/${cleanEndpoint}`;
286
+ const headers = {
287
+ "Content-Type": "application/json",
288
+ "User-Agent": "MCP-WordPress/1.0.0",
289
+ ...options.headers,
290
+ };
291
+ // Add authentication headers
292
+ this.addAuthHeaders(headers);
293
+ // Set up timeout using AbortController - use options timeout if provided
294
+ const controller = new AbortController();
295
+ const requestTimeout = options.timeout || this.timeout;
296
+ const timeoutId = setTimeout(() => controller.abort(), requestTimeout);
297
+ const fetchOptions = {
298
+ method,
299
+ headers,
300
+ signal: controller.signal,
301
+ ...options,
302
+ };
303
+ // Add body for POST/PUT/PATCH requests
304
+ if (data && ["POST", "PUT", "PATCH"].includes(method)) {
305
+ if (data instanceof FormData ||
306
+ (data && typeof data.append === "function")) {
307
+ // For FormData, don't set Content-Type (let fetch set it with boundary)
308
+ delete headers["Content-Type"];
309
+ fetchOptions.body = data;
310
+ }
311
+ else if (Buffer.isBuffer(data)) {
312
+ // For Buffer data (manual multipart), keep Content-Type from headers
313
+ fetchOptions.body = data;
314
+ }
315
+ else if (typeof data === "string") {
316
+ fetchOptions.body = data;
317
+ }
318
+ else {
319
+ fetchOptions.body = JSON.stringify(data);
320
+ }
321
+ }
322
+ // Rate limiting
323
+ await this.rateLimit();
324
+ let lastError = new Error("Unknown error");
325
+ for (let attempt = 0; attempt < this.maxRetries; attempt++) {
326
+ try {
327
+ debug.log(`API Request: ${method} ${url}${attempt > 0 ? ` (attempt ${attempt + 1})` : ""}`);
328
+ const response = await fetch(url, fetchOptions);
329
+ clearTimeout(timeoutId);
330
+ // Handle different response types
331
+ if (!response.ok) {
332
+ const errorText = await response.text();
333
+ let errorMessage;
334
+ try {
335
+ const errorData = JSON.parse(errorText);
336
+ errorMessage =
337
+ errorData.message || errorData.error || `HTTP ${response.status}`;
338
+ }
339
+ catch {
340
+ errorMessage =
341
+ errorText || `HTTP ${response.status}: ${response.statusText}`;
342
+ }
343
+ // Handle rate limiting
344
+ if (response.status === 429) {
345
+ this._stats.rateLimitHits++;
346
+ throw new RateLimitError(errorMessage, Date.now() + 60000);
347
+ }
348
+ // Handle permission errors specifically for uploads
349
+ if (response.status === 403 &&
350
+ endpoint.includes("media") &&
351
+ method === "POST") {
352
+ throw new AuthenticationError(`Media upload blocked: WordPress REST API media uploads appear to be disabled or restricted by a plugin/security policy. ` +
353
+ `Error: ${errorMessage}. ` +
354
+ `Common causes: W3 Total Cache, security plugins, or custom REST API restrictions. ` +
355
+ `Please check WordPress admin settings or contact your system administrator.`, this.auth.method);
356
+ }
357
+ // Handle general upload permission errors
358
+ if (errorMessage.includes("Beiträge zu erstellen") &&
359
+ endpoint.includes("media")) {
360
+ throw new AuthenticationError(`WordPress REST API media upload restriction detected: ${errorMessage}. ` +
361
+ `This typically indicates that media uploads via REST API are disabled by WordPress configuration, ` +
362
+ `a security plugin (like W3 Total Cache, Borlabs Cookie), or server policy. ` +
363
+ `User has sufficient permissions but WordPress/plugins are blocking the upload.`, this.auth.method);
364
+ }
365
+ throw new WordPressAPIError(errorMessage, response.status);
366
+ }
367
+ // Parse response
368
+ const responseText = await response.text();
369
+ if (!responseText) {
370
+ this._stats.successfulRequests++;
371
+ const duration = timer.end();
372
+ this.updateAverageResponseTime(duration);
373
+ return null;
374
+ }
375
+ try {
376
+ const result = JSON.parse(responseText);
377
+ this._stats.successfulRequests++;
378
+ const duration = timer.end();
379
+ this.updateAverageResponseTime(duration);
380
+ return result;
381
+ }
382
+ catch (parseError) {
383
+ // For authentication requests, malformed JSON should be an error
384
+ if (endpoint.includes("users/me") || endpoint.includes("jwt-auth")) {
385
+ throw new WordPressAPIError(`Invalid JSON response: ${parseError.message}`);
386
+ }
387
+ this._stats.successfulRequests++;
388
+ const duration = timer.end();
389
+ this.updateAverageResponseTime(duration);
390
+ return responseText;
391
+ }
392
+ }
393
+ catch (error) {
394
+ clearTimeout(timeoutId);
395
+ lastError = error;
396
+ // Handle timeout errors
397
+ if (error.name === "AbortError") {
398
+ lastError = new Error(`Request timeout after ${requestTimeout}ms`);
399
+ }
400
+ // Handle network errors
401
+ if (lastError.message.includes("socket hang up") ||
402
+ lastError.message.includes("ECONNRESET")) {
403
+ lastError = new Error(`Network connection lost during upload: ${lastError.message}`);
404
+ }
405
+ debug.log(`Request failed (attempt ${attempt + 1}): ${lastError.message}`);
406
+ // Don't retry on authentication errors, timeouts, or critical network errors
407
+ if (lastError.message.includes("401") ||
408
+ lastError.message.includes("403") ||
409
+ lastError.message.includes("timeout") ||
410
+ lastError.message.includes("Network connection lost")) {
411
+ break;
412
+ }
413
+ if (attempt < this.maxRetries - 1) {
414
+ await this.delay(1000 * (attempt + 1)); // Exponential backoff
415
+ }
416
+ }
417
+ }
418
+ this._stats.failedRequests++;
419
+ timer.end();
420
+ throw new WordPressAPIError(`Request failed after ${this.maxRetries} attempts: ${lastError.message}`);
421
+ }
422
+ updateAverageResponseTime(duration) {
423
+ const totalSuccessful = this._stats.successfulRequests;
424
+ this._stats.averageResponseTime =
425
+ (this._stats.averageResponseTime * (totalSuccessful - 1) + duration) /
426
+ totalSuccessful;
427
+ this._stats.lastRequestTime = Date.now();
428
+ }
429
+ // HTTP method helpers
430
+ async get(endpoint, options) {
431
+ return this.request("GET", endpoint, null, options);
432
+ }
433
+ async post(endpoint, data, options) {
434
+ return this.request("POST", endpoint, data, options);
435
+ }
436
+ async put(endpoint, data, options) {
437
+ return this.request("PUT", endpoint, data, options);
438
+ }
439
+ async patch(endpoint, data, options) {
440
+ return this.request("PATCH", endpoint, data, options);
441
+ }
442
+ async delete(endpoint, options) {
443
+ return this.request("DELETE", endpoint, null, options);
444
+ }
445
+ // WordPress API Methods
446
+ // Posts
447
+ async getPosts(params) {
448
+ const queryString = params
449
+ ? "?" + new URLSearchParams(params).toString()
450
+ : "";
451
+ return this.get(`posts${queryString}`);
452
+ }
453
+ async getPost(id, context = "view") {
454
+ return this.get(`posts/${id}?context=${context}`);
455
+ }
456
+ async createPost(data) {
457
+ return this.post("posts", data);
458
+ }
459
+ async updatePost(data) {
460
+ const { id, ...updateData } = data;
461
+ return this.put(`posts/${id}`, updateData);
462
+ }
463
+ async deletePost(id, force = false) {
464
+ return this.delete(`posts/${id}?force=${force}`);
465
+ }
466
+ async getPostRevisions(id) {
467
+ return this.get(`posts/${id}/revisions`);
468
+ }
469
+ // Pages
470
+ async getPages(params) {
471
+ const queryString = params
472
+ ? "?" + new URLSearchParams(params).toString()
473
+ : "";
474
+ return this.get(`pages${queryString}`);
475
+ }
476
+ async getPage(id, context = "view") {
477
+ return this.get(`pages/${id}?context=${context}`);
478
+ }
479
+ async createPage(data) {
480
+ return this.post("pages", data);
481
+ }
482
+ async updatePage(data) {
483
+ const { id, ...updateData } = data;
484
+ return this.put(`pages/${id}`, updateData);
485
+ }
486
+ async deletePage(id, force = false) {
487
+ return this.delete(`pages/${id}?force=${force}`);
488
+ }
489
+ async getPageRevisions(id) {
490
+ return this.get(`pages/${id}/revisions`);
491
+ }
492
+ // Media
493
+ async getMedia(params) {
494
+ const queryString = params
495
+ ? "?" + new URLSearchParams(params).toString()
496
+ : "";
497
+ return this.get(`media${queryString}`);
498
+ }
499
+ async getMediaItem(id, context = "view") {
500
+ return this.get(`media/${id}?context=${context}`);
501
+ }
502
+ async uploadMedia(data) {
503
+ if (!fs.existsSync(data.file_path)) {
504
+ throw new Error(`File not found: ${data.file_path}`);
505
+ }
506
+ const stats = fs.statSync(data.file_path);
507
+ const filename = data.title || path.basename(data.file_path);
508
+ const fileBuffer = fs.readFileSync(data.file_path);
509
+ // Check if file is too large (WordPress default is 2MB for most installs)
510
+ const maxSize = 10 * 1024 * 1024; // 10MB reasonable limit
511
+ if (stats.size > maxSize) {
512
+ throw new Error(`File too large: ${(stats.size / 1024 / 1024).toFixed(2)}MB. Maximum allowed: ${maxSize / 1024 / 1024}MB`);
513
+ }
514
+ debug.log(`Uploading file: ${filename} (${(stats.size / 1024).toFixed(2)}KB)`);
515
+ return this.uploadFile(fileBuffer, filename, this.getMimeType(data.file_path), data);
516
+ }
517
+ async uploadFile(fileData, filename, mimeType, meta = {}, options) {
518
+ debug.log(`Uploading file: ${filename} (${fileData.length} bytes)`);
519
+ // Use FormData but with correct configuration for node-fetch
520
+ const formData = new FormData();
521
+ formData.setMaxListeners(20);
522
+ // Add file with correct options
523
+ formData.append("file", fileData, {
524
+ filename,
525
+ contentType: mimeType,
526
+ });
527
+ // Add metadata
528
+ if (meta.title)
529
+ formData.append("title", meta.title);
530
+ if (meta.alt_text)
531
+ formData.append("alt_text", meta.alt_text);
532
+ if (meta.caption)
533
+ formData.append("caption", meta.caption);
534
+ if (meta.description)
535
+ formData.append("description", meta.description);
536
+ if (meta.post)
537
+ formData.append("post", meta.post.toString());
538
+ // Use longer timeout for file uploads
539
+ const uploadTimeout = options?.timeout !== undefined ? options.timeout : 600000; // 10 minutes default
540
+ const uploadOptions = {
541
+ ...options,
542
+ timeout: uploadTimeout,
543
+ };
544
+ debug.log(`Upload prepared with FormData, timeout: ${uploadTimeout}ms`);
545
+ // Use the regular post method which handles FormData correctly
546
+ return this.post("media", formData, uploadOptions);
547
+ }
548
+ async updateMedia(data) {
549
+ const { id, ...updateData } = data;
550
+ return this.put(`media/${id}`, updateData);
551
+ }
552
+ async deleteMedia(id, force = false) {
553
+ return this.delete(`media/${id}?force=${force}`);
554
+ }
555
+ // Users
556
+ async getUsers(params) {
557
+ const queryString = params
558
+ ? "?" + new URLSearchParams(params).toString()
559
+ : "";
560
+ return this.get(`users${queryString}`);
561
+ }
562
+ async getUser(id, context = "view") {
563
+ return this.get(`users/${id}?context=${context}`);
564
+ }
565
+ async createUser(data) {
566
+ return this.post("users", data);
567
+ }
568
+ async updateUser(data) {
569
+ const { id, ...updateData } = data;
570
+ return this.put(`users/${id}`, updateData);
571
+ }
572
+ async deleteUser(id, reassign) {
573
+ const params = reassign
574
+ ? `?reassign=${reassign}&force=true`
575
+ : "?force=true";
576
+ return this.delete(`users/${id}${params}`);
577
+ }
578
+ async getCurrentUser() {
579
+ return this.getUser("me");
580
+ }
581
+ // Comments
582
+ async getComments(params) {
583
+ const queryString = params
584
+ ? "?" + new URLSearchParams(params).toString()
585
+ : "";
586
+ return this.get(`comments${queryString}`);
587
+ }
588
+ async getComment(id, context = "view") {
589
+ return this.get(`comments/${id}?context=${context}`);
590
+ }
591
+ async createComment(data) {
592
+ return this.post("comments", data);
593
+ }
594
+ async updateComment(data) {
595
+ const { id, ...updateData } = data;
596
+ return this.put(`comments/${id}`, updateData);
597
+ }
598
+ async deleteComment(id, force = false) {
599
+ return this.delete(`comments/${id}?force=${force}`);
600
+ }
601
+ async approveComment(id) {
602
+ return this.put(`comments/${id}`, { status: "approved" });
603
+ }
604
+ async rejectComment(id) {
605
+ return this.put(`comments/${id}`, {
606
+ status: "unapproved",
607
+ });
608
+ }
609
+ async spamComment(id) {
610
+ return this.put(`comments/${id}`, { status: "spam" });
611
+ }
612
+ // Taxonomies
613
+ async getCategories(params) {
614
+ const queryString = params
615
+ ? "?" + new URLSearchParams(params).toString()
616
+ : "";
617
+ return this.get(`categories${queryString}`);
618
+ }
619
+ async getCategory(id) {
620
+ return this.get(`categories/${id}`);
621
+ }
622
+ async createCategory(data) {
623
+ return this.post("categories", data);
624
+ }
625
+ async updateCategory(data) {
626
+ const { id, ...updateData } = data;
627
+ return this.put(`categories/${id}`, updateData);
628
+ }
629
+ async deleteCategory(id, force = false) {
630
+ return this.delete(`categories/${id}?force=${force}`);
631
+ }
632
+ async getTags(params) {
633
+ const queryString = params
634
+ ? "?" + new URLSearchParams(params).toString()
635
+ : "";
636
+ return this.get(`tags${queryString}`);
637
+ }
638
+ async getTag(id) {
639
+ return this.get(`tags/${id}`);
640
+ }
641
+ async createTag(data) {
642
+ return this.post("tags", data);
643
+ }
644
+ async updateTag(data) {
645
+ const { id, ...updateData } = data;
646
+ return this.put(`tags/${id}`, updateData);
647
+ }
648
+ async deleteTag(id, force = false) {
649
+ return this.delete(`tags/${id}?force=${force}`);
650
+ }
651
+ // Site Management
652
+ async getSiteSettings() {
653
+ return this.get("settings");
654
+ }
655
+ async updateSiteSettings(settings) {
656
+ return this.post("settings", settings);
657
+ }
658
+ async getSiteInfo() {
659
+ return this.get("");
660
+ }
661
+ // Application Passwords
662
+ async getApplicationPasswords(userId = "me") {
663
+ return this.get(`users/${userId}/application-passwords`);
664
+ }
665
+ async createApplicationPassword(userId, name, appId) {
666
+ const data = { name };
667
+ if (appId)
668
+ data.app_id = appId;
669
+ return this.post(`users/${userId}/application-passwords`, data);
670
+ }
671
+ async deleteApplicationPassword(userId, uuid) {
672
+ return this.delete(`users/${userId}/application-passwords/${uuid}`);
673
+ }
674
+ // Search
675
+ async search(query, types, subtype) {
676
+ const params = new URLSearchParams({ search: query });
677
+ if (types)
678
+ params.append("type", types.join(","));
679
+ if (subtype)
680
+ params.append("subtype", subtype);
681
+ return this.get(`search?${params.toString()}`);
682
+ }
683
+ // Utility Methods
684
+ async ping() {
685
+ try {
686
+ await this.get("");
687
+ return true;
688
+ }
689
+ catch {
690
+ return false;
691
+ }
692
+ }
693
+ async getServerInfo() {
694
+ return this.get("");
695
+ }
696
+ validateEndpoint(endpoint) {
697
+ return /^[a-zA-Z0-9\/\-_]+$/.test(endpoint);
698
+ }
699
+ buildUrl(endpoint, params) {
700
+ const url = `${this.apiUrl}/${endpoint.replace(/^\/+/, "")}`;
701
+ if (params) {
702
+ const searchParams = new URLSearchParams(params);
703
+ return `${url}?${searchParams.toString()}`;
704
+ }
705
+ return url;
706
+ }
707
+ getMimeType(filePath) {
708
+ const ext = path.extname(filePath).toLowerCase();
709
+ const mimeTypes = {
710
+ ".jpg": "image/jpeg",
711
+ ".jpeg": "image/jpeg",
712
+ ".png": "image/png",
713
+ ".gif": "image/gif",
714
+ ".webp": "image/webp",
715
+ ".svg": "image/svg+xml",
716
+ ".pdf": "application/pdf",
717
+ ".doc": "application/msword",
718
+ ".docx": "application/vnd.openxmlformats-officedocument.wordprocessingml.document",
719
+ ".txt": "text/plain",
720
+ ".mp4": "video/mp4",
721
+ ".mp3": "audio/mpeg",
722
+ ".wav": "audio/wav",
723
+ };
724
+ return mimeTypes[ext] || "application/octet-stream";
725
+ }
726
+ }
727
+ //# sourceMappingURL=api.js.map