@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.
- package/dist/client/plexoClient.js +266 -3
- package/dist/commerceTools.js +500 -0
- package/dist/index.js +188 -3
- package/dist/lmsTools.js +254 -0
- package/package.json +1 -1
- package/src/client/plexoClient.ts +313 -3
- package/src/commerceTools.ts +516 -0
- package/src/index.ts +191 -3
- package/src/lmsTools.ts +262 -0
|
@@ -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
|
|
200
|
-
*
|
|
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({
|
|
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
|
}
|