@charisol/plexo-mcp 1.0.8 → 1.0.10

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.
@@ -147,15 +147,23 @@ class PlexoClient {
147
147
  /**
148
148
  * Creates a new page nested under an existing landing page (its home page
149
149
  * for a top-level page, or another sub-page's id to nest deeper) — how a
150
- * single landing page becomes a multi-page site. Starts blank; use
151
- * updateTemplate() with the returned templateId to add content.
150
+ * single landing page becomes a multi-page site. Starts blank unless
151
+ * htmlContent is given, in which case it's created as a RAW_UPLOAD page
152
+ * instead — see updateTemplate() (BUILDER) / updateRawLandingPage()
153
+ * (RAW_UPLOAD) to add/edit content afterward, depending on which mode.
152
154
  */
153
155
  async createLandingPageSubpage(params) {
154
156
  this.checkAuth();
155
157
  const res = await fetch(`${this.baseUrl}/api/templates`, {
156
158
  method: "POST",
157
159
  headers: this.getHeaders(),
158
- body: JSON.stringify({ name: params.name, parentId: params.parentTemplateId, slug: params.slug }),
160
+ body: JSON.stringify({
161
+ name: params.name,
162
+ parentId: params.parentTemplateId,
163
+ slug: params.slug,
164
+ htmlContent: params.htmlContent,
165
+ acceptAup: params.acceptAup,
166
+ }),
159
167
  });
160
168
  const data = (await res.json());
161
169
  if (!res.ok) {
@@ -163,6 +171,68 @@ class PlexoClient {
163
171
  }
164
172
  return data;
165
173
  }
174
+ /**
175
+ * Creates a landing page from raw HTML supplied directly (no designJson),
176
+ * served byte-for-byte as given — a different storage mode from
177
+ * createLandingPage/publishLandingPage, which are for the drag-and-drop
178
+ * BUILDER pages built from designJson. Same endpoint upload-raw's REST
179
+ * multipart path uses, but with a JSON body — this client has no real
180
+ * file to upload.
181
+ */
182
+ async createRawLandingPage(params) {
183
+ this.checkAuth();
184
+ const res = await fetch(`${this.baseUrl}/api/v1/templates/upload-raw`, {
185
+ method: "POST",
186
+ headers: this.getHeaders(),
187
+ body: JSON.stringify(params),
188
+ });
189
+ const data = (await res.json());
190
+ if (!res.ok) {
191
+ throw new Error(data?.error || `Raw landing page creation failed with status ${res.status}`);
192
+ }
193
+ return data;
194
+ }
195
+ /**
196
+ * Creates AND publishes a raw-HTML landing page in one call — the combined
197
+ * equivalent of createRawLandingPage() + publishExistingLandingPage().
198
+ */
199
+ async publishRawLandingPage(params) {
200
+ const created = await this.createRawLandingPage({
201
+ name: params.name,
202
+ htmlContent: params.htmlContent,
203
+ acceptAup: params.acceptAup,
204
+ });
205
+ const templateId = created?.templateId;
206
+ if (!templateId) {
207
+ throw new Error(created?.error || "Raw landing page creation did not return a templateId.");
208
+ }
209
+ const published = await this.publishExistingLandingPage({
210
+ templateId,
211
+ domain: params.domain,
212
+ type: params.type,
213
+ });
214
+ return { ...created, ...published };
215
+ }
216
+ /**
217
+ * Replaces an existing RAW_UPLOAD page's HTML in place, under the same
218
+ * template id — the editable URL, live URL, and domain (if published) are
219
+ * all unchanged. Same endpoint replace-upload's REST multipart path uses,
220
+ * but with a JSON body.
221
+ */
222
+ async updateRawLandingPage(params) {
223
+ this.checkAuth();
224
+ const { templateId, ...body } = params;
225
+ const res = await fetch(`${this.baseUrl}/api/v1/templates/${encodeURIComponent(templateId)}/replace-upload`, {
226
+ method: "POST",
227
+ headers: this.getHeaders(),
228
+ body: JSON.stringify(body),
229
+ });
230
+ const data = (await res.json());
231
+ if (!res.ok) {
232
+ throw new Error(data?.error || `Raw landing page update failed with status ${res.status}`);
233
+ }
234
+ return data;
235
+ }
166
236
  /**
167
237
  * Returns every page belonging to the same multi-page site as templateId
168
238
  * — its home page plus all nested sub-pages. templateId can be the home
@@ -380,5 +450,198 @@ class PlexoClient {
380
450
  async deleteBlogComment(templateId, commentId) {
381
451
  return this.blogFetch("DELETE", templateId, `/comments/${encodeURIComponent(commentId)}`);
382
452
  }
453
+ /** Shared request helper for every /api/v1/commerce/{templateId}/... route below — the
454
+ * same admin endpoints the Plexo dashboard's own Commerce UI calls, same Bearer/x-api-key
455
+ * auth as the rest of this client (see resolveCommerceAdmin server-side). */
456
+ async commerceFetch(method, templateId, path, body) {
457
+ this.checkAuth();
458
+ const res = await fetch(`${this.baseUrl}/api/v1/commerce/${encodeURIComponent(templateId)}${path}`, {
459
+ method,
460
+ headers: this.getHeaders(),
461
+ ...(body !== undefined ? { body: JSON.stringify(body) } : {}),
462
+ });
463
+ const data = (await res.json().catch(() => ({})));
464
+ if (!res.ok)
465
+ throw new Error(data?.error || `Commerce request failed (Status ${res.status})`);
466
+ return data;
467
+ }
468
+ async listCommerceProducts(templateId, params = {}) {
469
+ const query = new URLSearchParams();
470
+ if (params.kind)
471
+ query.set("kind", params.kind);
472
+ if (params.activeOnly)
473
+ query.set("activeOnly", "true");
474
+ const qs = query.toString();
475
+ return this.commerceFetch("GET", templateId, `/products${qs ? `?${qs}` : ""}`);
476
+ }
477
+ async getCommerceProduct(templateId, productId) {
478
+ return this.commerceFetch("GET", templateId, `/products/${encodeURIComponent(productId)}`);
479
+ }
480
+ async createCommerceProduct(templateId, payload) {
481
+ return this.commerceFetch("POST", templateId, "/products", payload);
482
+ }
483
+ async updateCommerceProduct(templateId, productId, payload) {
484
+ return this.commerceFetch("PATCH", templateId, `/products/${encodeURIComponent(productId)}`, payload);
485
+ }
486
+ async deleteCommerceProduct(templateId, productId) {
487
+ return this.commerceFetch("DELETE", templateId, `/products/${encodeURIComponent(productId)}`);
488
+ }
489
+ async getCommerceSettings(templateId) {
490
+ return this.commerceFetch("GET", templateId, "/settings");
491
+ }
492
+ async updateCommerceSettings(templateId, payload) {
493
+ return this.commerceFetch("PUT", templateId, "/settings", payload);
494
+ }
495
+ async listCommerceOrders(templateId, params = {}) {
496
+ const query = new URLSearchParams();
497
+ if (params.status)
498
+ query.set("status", params.status);
499
+ if (params.q)
500
+ query.set("q", params.q);
501
+ if (params.page)
502
+ query.set("page", String(params.page));
503
+ const qs = query.toString();
504
+ return this.commerceFetch("GET", templateId, `/orders${qs ? `?${qs}` : ""}`);
505
+ }
506
+ async getCommerceOrder(templateId, orderId) {
507
+ return this.commerceFetch("GET", templateId, `/orders/${encodeURIComponent(orderId)}`);
508
+ }
509
+ async updateCommerceOrder(templateId, orderId, fulfillmentStatus) {
510
+ return this.commerceFetch("PATCH", templateId, `/orders/${encodeURIComponent(orderId)}`, { fulfillmentStatus });
511
+ }
512
+ async refundCommerceOrder(templateId, orderId) {
513
+ return this.commerceFetch("POST", templateId, `/orders/${encodeURIComponent(orderId)}/refund`);
514
+ }
515
+ async resendDigitalDelivery(templateId, orderId, deliveryId) {
516
+ return this.commerceFetch("POST", templateId, `/orders/${encodeURIComponent(orderId)}/resend-digital-delivery`, { deliveryId });
517
+ }
518
+ async getCommerceWallet(templateId) {
519
+ return this.commerceFetch("GET", templateId, "/wallet");
520
+ }
521
+ async listCommerceWithdrawals(templateId) {
522
+ return this.commerceFetch("GET", templateId, "/wallet/withdrawals");
523
+ }
524
+ async requestCommerceWithdrawal(templateId, payload) {
525
+ return this.commerceFetch("POST", templateId, "/wallet/withdrawals", payload);
526
+ }
527
+ async getCommerceStripeAccessStatus() {
528
+ this.checkAuth();
529
+ const res = await fetch(`${this.baseUrl}/api/v1/commerce/stripe-access`, { method: "GET", headers: this.getHeaders() });
530
+ const data = (await res.json().catch(() => ({})));
531
+ if (!res.ok)
532
+ throw new Error(data?.error || `Failed to fetch Stripe access status (Status ${res.status})`);
533
+ return data;
534
+ }
535
+ async requestCommerceStripeAccess(payload = {}) {
536
+ this.checkAuth();
537
+ const res = await fetch(`${this.baseUrl}/api/v1/commerce/stripe-access`, {
538
+ method: "POST",
539
+ headers: this.getHeaders(),
540
+ body: JSON.stringify(payload),
541
+ });
542
+ const data = (await res.json().catch(() => ({})));
543
+ if (!res.ok)
544
+ throw new Error(data?.error || `Failed to request Stripe access (Status ${res.status})`);
545
+ return data;
546
+ }
547
+ async listCommerceDeliveryMethods(templateId) {
548
+ return this.commerceFetch("GET", templateId, "/delivery-methods");
549
+ }
550
+ async createCommerceDeliveryMethod(templateId, payload) {
551
+ return this.commerceFetch("POST", templateId, "/delivery-methods", payload);
552
+ }
553
+ async updateCommerceDeliveryMethod(templateId, id, payload) {
554
+ return this.commerceFetch("PATCH", templateId, `/delivery-methods/${encodeURIComponent(id)}`, payload);
555
+ }
556
+ async deleteCommerceDeliveryMethod(templateId, id) {
557
+ return this.commerceFetch("DELETE", templateId, `/delivery-methods/${encodeURIComponent(id)}`);
558
+ }
559
+ async listCommerceDiscounts(templateId) {
560
+ return this.commerceFetch("GET", templateId, "/discounts");
561
+ }
562
+ async createCommerceDiscount(templateId, payload) {
563
+ return this.commerceFetch("POST", templateId, "/discounts", payload);
564
+ }
565
+ async updateCommerceDiscount(templateId, id, payload) {
566
+ return this.commerceFetch("PATCH", templateId, `/discounts/${encodeURIComponent(id)}`, payload);
567
+ }
568
+ async deleteCommerceDiscount(templateId, id) {
569
+ return this.commerceFetch("DELETE", templateId, `/discounts/${encodeURIComponent(id)}`);
570
+ }
571
+ async listCommerceCustomers(templateId, page) {
572
+ return this.commerceFetch("GET", templateId, `/customers${page ? `?page=${page}` : ""}`);
573
+ }
574
+ /** Shared request helper for every /api/v1/lms/funnels/{templateId}/... route below. */
575
+ async lmsFetch(method, templateId, path, body) {
576
+ this.checkAuth();
577
+ const res = await fetch(`${this.baseUrl}/api/v1/lms/funnels/${encodeURIComponent(templateId)}${path}`, {
578
+ method,
579
+ headers: this.getHeaders(),
580
+ ...(body !== undefined ? { body: JSON.stringify(body) } : {}),
581
+ });
582
+ const data = (await res.json().catch(() => ({})));
583
+ if (!res.ok)
584
+ throw new Error(data?.error || `Funnel request failed (Status ${res.status})`);
585
+ return data;
586
+ }
587
+ async createFunnel(templateId) {
588
+ this.checkAuth();
589
+ const res = await fetch(`${this.baseUrl}/api/v1/lms/funnels`, {
590
+ method: "POST",
591
+ headers: this.getHeaders(),
592
+ body: JSON.stringify({ templateId }),
593
+ });
594
+ const data = (await res.json().catch(() => ({})));
595
+ if (!res.ok)
596
+ throw new Error(data?.error || `Funnel creation failed (Status ${res.status})`);
597
+ return data;
598
+ }
599
+ async getFunnel(templateId) {
600
+ return this.lmsFetch("GET", templateId, "");
601
+ }
602
+ async updateFunnel(templateId, payload) {
603
+ return this.lmsFetch("PATCH", templateId, "", payload);
604
+ }
605
+ async deleteFunnel(templateId) {
606
+ return this.lmsFetch("DELETE", templateId, "");
607
+ }
608
+ async listFunnelPages(templateId) {
609
+ return this.lmsFetch("GET", templateId, "/pages");
610
+ }
611
+ async createFunnelPage(templateId, payload = {}) {
612
+ return this.lmsFetch("POST", templateId, "/pages", payload);
613
+ }
614
+ async updateFunnelPage(templateId, pageId, payload) {
615
+ return this.lmsFetch("PATCH", templateId, `/pages/${encodeURIComponent(pageId)}`, payload);
616
+ }
617
+ async deleteFunnelPage(templateId, pageId) {
618
+ return this.lmsFetch("DELETE", templateId, `/pages/${encodeURIComponent(pageId)}`);
619
+ }
620
+ async reorderFunnelPages(templateId, orderedPageIds) {
621
+ return this.lmsFetch("POST", templateId, "/pages/reorder", { orderedPageIds });
622
+ }
623
+ async listFunnelEnrollments(templateId) {
624
+ return this.lmsFetch("GET", templateId, "/enrollments");
625
+ }
626
+ async getFunnelEnrollment(templateId, email) {
627
+ return this.lmsFetch("GET", templateId, `/enrollments/${encodeURIComponent(email)}`);
628
+ }
629
+ async setFunnelEnrollmentStatus(templateId, email, status) {
630
+ return this.lmsFetch("PATCH", templateId, `/enrollments/${encodeURIComponent(email)}`, { status });
631
+ }
632
+ async approveFunnelEnrollmentPage(templateId, email, pageId) {
633
+ return this.lmsFetch("POST", templateId, `/enrollments/${encodeURIComponent(email)}/approve`, { pageId });
634
+ }
635
+ async resendFunnelMagicLink(templateId, email) {
636
+ return this.lmsFetch("POST", templateId, `/enrollments/${encodeURIComponent(email)}/resend-magic-link`);
637
+ }
638
+ async getFunnelAddonSlots() {
639
+ this.checkAuth();
640
+ const res = await fetch(`${this.baseUrl}/api/v1/lms/addon-slots`, { method: "GET", headers: this.getHeaders() });
641
+ const data = (await res.json().catch(() => ({})));
642
+ if (!res.ok)
643
+ throw new Error(data?.error || `Failed to fetch funnel addon slots (Status ${res.status})`);
644
+ return data;
645
+ }
383
646
  }
384
647
  exports.PlexoClient = PlexoClient;