@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.
@@ -196,19 +196,29 @@ export class PlexoClient {
196
196
  /**
197
197
  * Creates a new page nested under an existing landing page (its home page
198
198
  * for a top-level page, or another sub-page's id to nest deeper) — how a
199
- * single landing page becomes a multi-page site. Starts blank; use
200
- * updateTemplate() with the returned templateId to add content.
199
+ * single landing page becomes a multi-page site. Starts blank unless
200
+ * htmlContent is given, in which case it's created as a RAW_UPLOAD page
201
+ * instead — see updateTemplate() (BUILDER) / updateRawLandingPage()
202
+ * (RAW_UPLOAD) to add/edit content afterward, depending on which mode.
201
203
  */
202
204
  async createLandingPageSubpage(params: {
203
205
  parentTemplateId: string;
204
206
  name: string;
205
207
  slug?: string;
208
+ htmlContent?: string;
209
+ acceptAup?: boolean;
206
210
  }): Promise<any> {
207
211
  this.checkAuth();
208
212
  const res = await fetch(`${this.baseUrl}/api/templates`, {
209
213
  method: "POST",
210
214
  headers: this.getHeaders(),
211
- body: JSON.stringify({ name: params.name, parentId: params.parentTemplateId, slug: params.slug }),
215
+ body: JSON.stringify({
216
+ name: params.name,
217
+ parentId: params.parentTemplateId,
218
+ slug: params.slug,
219
+ htmlContent: params.htmlContent,
220
+ acceptAup: params.acceptAup,
221
+ }),
212
222
  });
213
223
 
214
224
  const data = (await res.json()) as any;
@@ -219,6 +229,81 @@ export class PlexoClient {
219
229
  return data;
220
230
  }
221
231
 
232
+ /**
233
+ * Creates a landing page from raw HTML supplied directly (no designJson),
234
+ * served byte-for-byte as given — a different storage mode from
235
+ * createLandingPage/publishLandingPage, which are for the drag-and-drop
236
+ * BUILDER pages built from designJson. Same endpoint upload-raw's REST
237
+ * multipart path uses, but with a JSON body — this client has no real
238
+ * file to upload.
239
+ */
240
+ async createRawLandingPage(params: { name?: string; htmlContent: string; acceptAup?: boolean }): Promise<any> {
241
+ this.checkAuth();
242
+ const res = await fetch(`${this.baseUrl}/api/v1/templates/upload-raw`, {
243
+ method: "POST",
244
+ headers: this.getHeaders(),
245
+ body: JSON.stringify(params),
246
+ });
247
+
248
+ const data = (await res.json()) as any;
249
+ if (!res.ok) {
250
+ throw new Error(data?.error || `Raw landing page creation failed with status ${res.status}`);
251
+ }
252
+
253
+ return data;
254
+ }
255
+
256
+ /**
257
+ * Creates AND publishes a raw-HTML landing page in one call — the combined
258
+ * equivalent of createRawLandingPage() + publishExistingLandingPage().
259
+ */
260
+ async publishRawLandingPage(params: {
261
+ name?: string;
262
+ htmlContent: string;
263
+ acceptAup?: boolean;
264
+ domain: string;
265
+ type?: "SUBDOMAIN" | "CUSTOM";
266
+ }): Promise<any> {
267
+ const created = await this.createRawLandingPage({
268
+ name: params.name,
269
+ htmlContent: params.htmlContent,
270
+ acceptAup: params.acceptAup,
271
+ });
272
+ const templateId = created?.templateId;
273
+ if (!templateId) {
274
+ throw new Error(created?.error || "Raw landing page creation did not return a templateId.");
275
+ }
276
+ const published = await this.publishExistingLandingPage({
277
+ templateId,
278
+ domain: params.domain,
279
+ type: params.type,
280
+ });
281
+ return { ...created, ...published };
282
+ }
283
+
284
+ /**
285
+ * Replaces an existing RAW_UPLOAD page's HTML in place, under the same
286
+ * template id — the editable URL, live URL, and domain (if published) are
287
+ * all unchanged. Same endpoint replace-upload's REST multipart path uses,
288
+ * but with a JSON body.
289
+ */
290
+ async updateRawLandingPage(params: { templateId: string; htmlContent: string; name?: string }): Promise<any> {
291
+ this.checkAuth();
292
+ const { templateId, ...body } = params;
293
+ const res = await fetch(`${this.baseUrl}/api/v1/templates/${encodeURIComponent(templateId)}/replace-upload`, {
294
+ method: "POST",
295
+ headers: this.getHeaders(),
296
+ body: JSON.stringify(body),
297
+ });
298
+
299
+ const data = (await res.json()) as any;
300
+ if (!res.ok) {
301
+ throw new Error(data?.error || `Raw landing page update failed with status ${res.status}`);
302
+ }
303
+
304
+ return data;
305
+ }
306
+
222
307
  /**
223
308
  * Returns every page belonging to the same multi-page site as templateId
224
309
  * — its home page plus all nested sub-pages. templateId can be the home
@@ -485,4 +570,229 @@ export class PlexoClient {
485
570
  async deleteBlogComment(templateId: string, commentId: string): Promise<any> {
486
571
  return this.blogFetch("DELETE", templateId, `/comments/${encodeURIComponent(commentId)}`);
487
572
  }
573
+
574
+ /** Shared request helper for every /api/v1/commerce/{templateId}/... route below — the
575
+ * same admin endpoints the Plexo dashboard's own Commerce UI calls, same Bearer/x-api-key
576
+ * auth as the rest of this client (see resolveCommerceAdmin server-side). */
577
+ private async commerceFetch(method: string, templateId: string, path: string, body?: unknown): Promise<any> {
578
+ this.checkAuth();
579
+ const res = await fetch(`${this.baseUrl}/api/v1/commerce/${encodeURIComponent(templateId)}${path}`, {
580
+ method,
581
+ headers: this.getHeaders(),
582
+ ...(body !== undefined ? { body: JSON.stringify(body) } : {}),
583
+ });
584
+ const data = (await res.json().catch(() => ({}))) as any;
585
+ if (!res.ok) throw new Error(data?.error || `Commerce request failed (Status ${res.status})`);
586
+ return data;
587
+ }
588
+
589
+ async listCommerceProducts(templateId: string, params: { kind?: string; activeOnly?: boolean } = {}): Promise<any> {
590
+ const query = new URLSearchParams();
591
+ if (params.kind) query.set("kind", params.kind);
592
+ if (params.activeOnly) query.set("activeOnly", "true");
593
+ const qs = query.toString();
594
+ return this.commerceFetch("GET", templateId, `/products${qs ? `?${qs}` : ""}`);
595
+ }
596
+
597
+ async getCommerceProduct(templateId: string, productId: string): Promise<any> {
598
+ return this.commerceFetch("GET", templateId, `/products/${encodeURIComponent(productId)}`);
599
+ }
600
+
601
+ async createCommerceProduct(templateId: string, payload: Record<string, any>): Promise<any> {
602
+ return this.commerceFetch("POST", templateId, "/products", payload);
603
+ }
604
+
605
+ async updateCommerceProduct(templateId: string, productId: string, payload: Record<string, any>): Promise<any> {
606
+ return this.commerceFetch("PATCH", templateId, `/products/${encodeURIComponent(productId)}`, payload);
607
+ }
608
+
609
+ async deleteCommerceProduct(templateId: string, productId: string): Promise<any> {
610
+ return this.commerceFetch("DELETE", templateId, `/products/${encodeURIComponent(productId)}`);
611
+ }
612
+
613
+ async getCommerceSettings(templateId: string): Promise<any> {
614
+ return this.commerceFetch("GET", templateId, "/settings");
615
+ }
616
+
617
+ async updateCommerceSettings(templateId: string, payload: Record<string, any>): Promise<any> {
618
+ return this.commerceFetch("PUT", templateId, "/settings", payload);
619
+ }
620
+
621
+ async listCommerceOrders(templateId: string, params: { status?: string; q?: string; page?: number } = {}): Promise<any> {
622
+ const query = new URLSearchParams();
623
+ if (params.status) query.set("status", params.status);
624
+ if (params.q) query.set("q", params.q);
625
+ if (params.page) query.set("page", String(params.page));
626
+ const qs = query.toString();
627
+ return this.commerceFetch("GET", templateId, `/orders${qs ? `?${qs}` : ""}`);
628
+ }
629
+
630
+ async getCommerceOrder(templateId: string, orderId: string): Promise<any> {
631
+ return this.commerceFetch("GET", templateId, `/orders/${encodeURIComponent(orderId)}`);
632
+ }
633
+
634
+ async updateCommerceOrder(templateId: string, orderId: string, fulfillmentStatus: string): Promise<any> {
635
+ return this.commerceFetch("PATCH", templateId, `/orders/${encodeURIComponent(orderId)}`, { fulfillmentStatus });
636
+ }
637
+
638
+ async refundCommerceOrder(templateId: string, orderId: string): Promise<any> {
639
+ return this.commerceFetch("POST", templateId, `/orders/${encodeURIComponent(orderId)}/refund`);
640
+ }
641
+
642
+ async resendDigitalDelivery(templateId: string, orderId: string, deliveryId: string): Promise<any> {
643
+ return this.commerceFetch("POST", templateId, `/orders/${encodeURIComponent(orderId)}/resend-digital-delivery`, { deliveryId });
644
+ }
645
+
646
+ async getCommerceWallet(templateId: string): Promise<any> {
647
+ return this.commerceFetch("GET", templateId, "/wallet");
648
+ }
649
+
650
+ async listCommerceWithdrawals(templateId: string): Promise<any> {
651
+ return this.commerceFetch("GET", templateId, "/wallet/withdrawals");
652
+ }
653
+
654
+ async requestCommerceWithdrawal(templateId: string, payload: { amountCents: number; accountNumber: string; accountHolderName: string; bankName: string }): Promise<any> {
655
+ return this.commerceFetch("POST", templateId, "/wallet/withdrawals", payload);
656
+ }
657
+
658
+ async getCommerceStripeAccessStatus(): Promise<any> {
659
+ this.checkAuth();
660
+ const res = await fetch(`${this.baseUrl}/api/v1/commerce/stripe-access`, { method: "GET", headers: this.getHeaders() });
661
+ const data = (await res.json().catch(() => ({}))) as any;
662
+ if (!res.ok) throw new Error(data?.error || `Failed to fetch Stripe access status (Status ${res.status})`);
663
+ return data;
664
+ }
665
+
666
+ async requestCommerceStripeAccess(payload: { reason?: string; expectedVolume?: string } = {}): Promise<any> {
667
+ this.checkAuth();
668
+ const res = await fetch(`${this.baseUrl}/api/v1/commerce/stripe-access`, {
669
+ method: "POST",
670
+ headers: this.getHeaders(),
671
+ body: JSON.stringify(payload),
672
+ });
673
+ const data = (await res.json().catch(() => ({}))) as any;
674
+ if (!res.ok) throw new Error(data?.error || `Failed to request Stripe access (Status ${res.status})`);
675
+ return data;
676
+ }
677
+
678
+ async listCommerceDeliveryMethods(templateId: string): Promise<any> {
679
+ return this.commerceFetch("GET", templateId, "/delivery-methods");
680
+ }
681
+
682
+ async createCommerceDeliveryMethod(templateId: string, payload: Record<string, any>): Promise<any> {
683
+ return this.commerceFetch("POST", templateId, "/delivery-methods", payload);
684
+ }
685
+
686
+ async updateCommerceDeliveryMethod(templateId: string, id: string, payload: Record<string, any>): Promise<any> {
687
+ return this.commerceFetch("PATCH", templateId, `/delivery-methods/${encodeURIComponent(id)}`, payload);
688
+ }
689
+
690
+ async deleteCommerceDeliveryMethod(templateId: string, id: string): Promise<any> {
691
+ return this.commerceFetch("DELETE", templateId, `/delivery-methods/${encodeURIComponent(id)}`);
692
+ }
693
+
694
+ async listCommerceDiscounts(templateId: string): Promise<any> {
695
+ return this.commerceFetch("GET", templateId, "/discounts");
696
+ }
697
+
698
+ async createCommerceDiscount(templateId: string, payload: Record<string, any>): Promise<any> {
699
+ return this.commerceFetch("POST", templateId, "/discounts", payload);
700
+ }
701
+
702
+ async updateCommerceDiscount(templateId: string, id: string, payload: Record<string, any>): Promise<any> {
703
+ return this.commerceFetch("PATCH", templateId, `/discounts/${encodeURIComponent(id)}`, payload);
704
+ }
705
+
706
+ async deleteCommerceDiscount(templateId: string, id: string): Promise<any> {
707
+ return this.commerceFetch("DELETE", templateId, `/discounts/${encodeURIComponent(id)}`);
708
+ }
709
+
710
+ async listCommerceCustomers(templateId: string, page?: number): Promise<any> {
711
+ return this.commerceFetch("GET", templateId, `/customers${page ? `?page=${page}` : ""}`);
712
+ }
713
+
714
+ /** Shared request helper for every /api/v1/lms/funnels/{templateId}/... route below. */
715
+ private async lmsFetch(method: string, templateId: string, path: string, body?: unknown): Promise<any> {
716
+ this.checkAuth();
717
+ const res = await fetch(`${this.baseUrl}/api/v1/lms/funnels/${encodeURIComponent(templateId)}${path}`, {
718
+ method,
719
+ headers: this.getHeaders(),
720
+ ...(body !== undefined ? { body: JSON.stringify(body) } : {}),
721
+ });
722
+ const data = (await res.json().catch(() => ({}))) as any;
723
+ if (!res.ok) throw new Error(data?.error || `Funnel request failed (Status ${res.status})`);
724
+ return data;
725
+ }
726
+
727
+ async createFunnel(templateId: string): Promise<any> {
728
+ this.checkAuth();
729
+ const res = await fetch(`${this.baseUrl}/api/v1/lms/funnels`, {
730
+ method: "POST",
731
+ headers: this.getHeaders(),
732
+ body: JSON.stringify({ templateId }),
733
+ });
734
+ const data = (await res.json().catch(() => ({}))) as any;
735
+ if (!res.ok) throw new Error(data?.error || `Funnel creation failed (Status ${res.status})`);
736
+ return data;
737
+ }
738
+
739
+ async getFunnel(templateId: string): Promise<any> {
740
+ return this.lmsFetch("GET", templateId, "");
741
+ }
742
+
743
+ async updateFunnel(templateId: string, payload: Record<string, any>): Promise<any> {
744
+ return this.lmsFetch("PATCH", templateId, "", payload);
745
+ }
746
+
747
+ async deleteFunnel(templateId: string): Promise<any> {
748
+ return this.lmsFetch("DELETE", templateId, "");
749
+ }
750
+
751
+ async listFunnelPages(templateId: string): Promise<any> {
752
+ return this.lmsFetch("GET", templateId, "/pages");
753
+ }
754
+
755
+ async createFunnelPage(templateId: string, payload: { title?: string; kind?: string } = {}): Promise<any> {
756
+ return this.lmsFetch("POST", templateId, "/pages", payload);
757
+ }
758
+
759
+ async updateFunnelPage(templateId: string, pageId: string, payload: Record<string, any>): Promise<any> {
760
+ return this.lmsFetch("PATCH", templateId, `/pages/${encodeURIComponent(pageId)}`, payload);
761
+ }
762
+
763
+ async deleteFunnelPage(templateId: string, pageId: string): Promise<any> {
764
+ return this.lmsFetch("DELETE", templateId, `/pages/${encodeURIComponent(pageId)}`);
765
+ }
766
+
767
+ async reorderFunnelPages(templateId: string, orderedPageIds: string[]): Promise<any> {
768
+ return this.lmsFetch("POST", templateId, "/pages/reorder", { orderedPageIds });
769
+ }
770
+
771
+ async listFunnelEnrollments(templateId: string): Promise<any> {
772
+ return this.lmsFetch("GET", templateId, "/enrollments");
773
+ }
774
+
775
+ async getFunnelEnrollment(templateId: string, email: string): Promise<any> {
776
+ return this.lmsFetch("GET", templateId, `/enrollments/${encodeURIComponent(email)}`);
777
+ }
778
+
779
+ async setFunnelEnrollmentStatus(templateId: string, email: string, status: "ACTIVE" | "REVOKED"): Promise<any> {
780
+ return this.lmsFetch("PATCH", templateId, `/enrollments/${encodeURIComponent(email)}`, { status });
781
+ }
782
+
783
+ async approveFunnelEnrollmentPage(templateId: string, email: string, pageId: string): Promise<any> {
784
+ return this.lmsFetch("POST", templateId, `/enrollments/${encodeURIComponent(email)}/approve`, { pageId });
785
+ }
786
+
787
+ async resendFunnelMagicLink(templateId: string, email: string): Promise<any> {
788
+ return this.lmsFetch("POST", templateId, `/enrollments/${encodeURIComponent(email)}/resend-magic-link`);
789
+ }
790
+
791
+ async getFunnelAddonSlots(): Promise<any> {
792
+ this.checkAuth();
793
+ const res = await fetch(`${this.baseUrl}/api/v1/lms/addon-slots`, { method: "GET", headers: this.getHeaders() });
794
+ const data = (await res.json().catch(() => ({}))) as any;
795
+ if (!res.ok) throw new Error(data?.error || `Failed to fetch funnel addon slots (Status ${res.status})`);
796
+ return data;
797
+ }
488
798
  }