@metagptx/web-sdk 0.0.32 → 0.0.34

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/README.md CHANGED
@@ -26,13 +26,14 @@ const user = await client.auth.me();
26
26
 
27
27
  ## Modules
28
28
 
29
- The SDK provides five main modules:
29
+ The SDK provides six main modules:
30
30
 
31
31
  - **auth**: User authentication operations
32
32
  - **entities**: Dynamic entity CRUD operations
33
33
  - **apiCall**: Custom API calls
34
34
  - **integrations**: Integration function invocations
35
35
  - **frame**: Frame communication operations for iframe/parent window messaging
36
+ - **utils**: Utility functions for URL opening and window management
36
37
 
37
38
  ## API Reference
38
39
 
@@ -448,71 +449,59 @@ const response = await client.integrations.storage.uploadFile({
448
449
 
449
450
  Provides utilities for communicating between iframe and parent window using postMessage API.
450
451
 
451
- #### `frame.postMessage(type, data?, options?)`
452
+ #### `frame.createPage(path?)`
452
453
 
453
- Send a message to the parent window using `window.top.postMessage`.
454
+ Create a page by sending a `'mgx-create-page'` message to the parent window.
454
455
 
455
456
  **Parameters:**
456
- - `type` (required): Message type identifier (must be a valid `FrameMessageType`)
457
- - `data` (optional): Message data payload (must match the data type for the given message type)
458
- - `options` (optional): Configuration options
459
- - `targetOrigin` (optional): Target origin for the message (defaults to `'*'`)
460
- - `targetName` (optional): Target window name (defaults to `window.name`)
461
-
462
- **Default Message Types:**
463
- - `'mgx-appview-error'`: Error message with `errMsg` and `stack` fields
464
- - `'mgx-create-page'`: Create page message with `path` field
465
-
466
- **TypeScript Support:**
467
- You can extend the `FrameMessageTypes` interface to add custom message types:
457
+ - `path` (optional): Page path to create. If not provided, uses `window.location.pathname`
468
458
 
459
+ **Example:**
469
460
  ```typescript
470
- import type { FrameMessageTypes } from '@metagptx/web-sdk';
461
+ // Create page with current path
462
+ client.frame.createPage();
471
463
 
472
- interface CustomMessageTypes extends FrameMessageTypes {
473
- 'custom-type': { customField: string };
474
- }
464
+ // Create page with specific path
465
+ client.frame.createPage('/custom/path');
475
466
  ```
476
467
 
477
- **Example:**
478
- ```typescript
479
- // Send an error message
480
- client.frame.postMessage('mgx-appview-error', {
481
- errMsg: 'Error message',
482
- stack: 'Error stack trace',
483
- });
468
+ ---
484
469
 
485
- // Send a message with custom origin
486
- client.frame.postMessage('mgx-appview-error', {
487
- errMsg: 'Error message',
488
- stack: 'Error stack trace',
489
- }, {
490
- targetOrigin: 'https://example.com',
491
- });
470
+ ### Utils Module
492
471
 
493
- // Send a message with custom target name
494
- client.frame.postMessage('mgx-appview-error', {
495
- errMsg: 'Error message',
496
- stack: 'Error stack trace',
497
- }, {
498
- targetName: 'custom-window',
499
- });
500
- ```
472
+ Provides utility functions for opening URLs and managing windows.
501
473
 
502
- #### `frame.createPage(path?)`
474
+ #### `utils.openUrl(url?, target?, features?)`
503
475
 
504
- Create a page by sending a `'mgx-create-page'` message to the parent window. This is a convenience method that uses `postMessage` internally.
476
+ Open a new window with the given URL, target, and features. For certain domains (like stripe.com), this will send a postMessage to the parent window instead of opening a new window directly.
505
477
 
506
478
  **Parameters:**
507
- - `path` (optional): Page path to create. If not provided, uses `window.location.pathname`
479
+ - `url` (optional): The URL to open (string or URL object)
480
+ - `target` (optional): The target window to open the URL in (e.g., `'_blank'`, `'_self'`)
481
+ - `features` (optional): The features to open the URL with (e.g., `'width=800,height=600'`)
482
+
483
+ **Behavior:**
484
+ - If `url` is not provided or is undefined, the function returns without doing anything
485
+ - For URLs containing `stripe.com`, the function sends a `'mgx-open-url'` postMessage to the parent window instead of opening a new window
486
+ - For other URLs, the function uses `window.open()` with the provided parameters
487
+ - The function automatically adds `noopener` and `noreferrer` to the features for security
488
+
489
+ **Returns:** The window object returned by `window.open()`, or `undefined` if no URL is provided or if postMessage is used
508
490
 
509
491
  **Example:**
510
492
  ```typescript
511
- // Create page with current path
512
- client.frame.createPage();
493
+ // Open a regular URL in a new window
494
+ client.utils.openUrl('https://example.com', '_blank', 'width=800,height=600');
513
495
 
514
- // Create page with specific path
515
- client.frame.createPage('/custom/path');
496
+ // Open a URL object
497
+ const url = new URL('https://example.com/page');
498
+ client.utils.openUrl(url, '_blank');
499
+
500
+ // Stripe URLs will trigger postMessage instead
501
+ client.utils.openUrl('https://stripe.com/checkout'); // Sends postMessage to parent
502
+
503
+ // Open with default features (noopener and noreferrer are automatically added)
504
+ client.utils.openUrl('https://example.com', '_blank');
516
505
  ```
517
506
 
518
507
  ---
package/dist/index.d.ts CHANGED
@@ -1,63 +1,6 @@
1
1
  import * as axios0 from "axios";
2
2
  import { AxiosRequestConfig } from "axios";
3
3
 
4
- //#region src/modules/frame/frame.d.ts
5
-
6
- /**
7
- * Default message type definitions
8
- * Extend this interface to add custom message types
9
- */
10
- interface DefaultFrameMessageTypes {
11
- 'mgx-appview-error': {
12
- errMsg: string;
13
- stack: string;
14
- };
15
- 'mgx-create-page': {
16
- path: string;
17
- };
18
- 'mgx-token-request': {
19
- domain: string;
20
- };
21
- }
22
- /**
23
- * Message type map interface
24
- * Extend this interface to add custom message types and their corresponding data types
25
- * @example
26
- * ```typescript
27
- * interface CustomMessageTypes extends FrameMessageTypes {
28
- * 'custom-type': { customField: string };
29
- * }
30
- * ```
31
- */
32
- interface FrameMessageTypes extends DefaultFrameMessageTypes {}
33
- /**
34
- * Extract message type keys from FrameMessageTypes
35
- */
36
- type FrameMessageType = keyof FrameMessageTypes;
37
- /**
38
- * Extract data type for a specific message type
39
- */
40
- type FrameMessageData<T extends FrameMessageType> = FrameMessageTypes[T];
41
- /**
42
- * PostMessage options interface
43
- */
44
- interface PostMessageOptions {
45
- /**
46
- * Target origin for the message
47
- * @default '*'
48
- */
49
- targetOrigin?: string;
50
- /**
51
- * Target window name
52
- * @default window.name
53
- */
54
- targetName?: string;
55
- }
56
- /**
57
- * Reset the initialization flag (for testing purposes)
58
- * @internal
59
- */
60
- //#endregion
61
4
  //#region src/utils/request.d.ts
62
5
  /**
63
6
  * Request instance configuration options
@@ -126,9 +69,11 @@ declare const createClient: (config?: ClientConfig) => {
126
69
  };
127
70
  integrations: Record<string, Record<string, IntegrationFunction>>;
128
71
  frame: {
129
- postMessage<T extends FrameMessageType>(type: T, data?: FrameMessageData<T>, options?: PostMessageOptions, origin?: Window | WindowProxy): void;
130
72
  createPage(path?: string): void;
131
73
  };
74
+ utils: {
75
+ openUrl: (url?: string | URL, target?: string, features?: string) => Window | null | undefined;
76
+ };
132
77
  };
133
78
  //#endregion
134
79
  //#region src/types/index.d.ts
package/dist/index.js CHANGED
@@ -1,4 +1,4 @@
1
- import e from"axios";import t from"qs";const n=e=>{let t=e.response?.status;if(t&&t>=400&&t!==401)try{typeof window<`u`&&window.top&&window.top?.postMessage({type:`mgx-appview-error`,targetName:window.name,data:{errMsg:e.response?.data?.message||e.response?.data?.detail||`Server Error`,stack:JSON.stringify({url:e.response?.config?.url,data:e.response?.data,status:e.response?.status})}},`*`)}catch(e){console.warn(`Failed to notify parent window about API error:`,e)}},r=(r={})=>{let{onUnauthorized:i,...a}=r,o=typeof globalThis<`u`&&`localStorage`in globalThis&&typeof globalThis.localStorage?.getItem==`function`?globalThis.localStorage.getItem(`token`)??void 0:void 0,s={timeout:6e4,paramsSerializer:e=>t.stringify(e,{arrayFormat:`brackets`}),...a,headers:{"Content-Type":`application/json`,...o?{Authorization:`Bearer ${o}`}:{},...a.headers}},c=e.create(s);return c.interceptors.request.use(e=>e,e=>Promise.reject(e)),c.interceptors.response.use(e=>e,e=>{let t=e.response?.status;return t===401&&i&&i(),n(e),Promise.reject(e)}),c},i=e=>typeof e==`string`?e.startsWith(`_`)||e.startsWith(`$`)||e===`constructor`||e===`toString`||e===`valueOf`||e===`inspect`||e===`toJSON`:!0,a=()=>{let e=window.location?.href??``,t=``;e.startsWith(`/`)?t=e:e&&(t=e.replace(/^[a-z][a-z0-9+.-]*:\/\/[^/]+/i,``));let n=new URLSearchParams({from_url:t});window.location.href=`/api/v1/auth/login?${n.toString()}`},o=e=>{let{requestInstance:t}=e;return{async login(){let e=new URLSearchParams(window.location.search),t=e.get(`token`);return t&&typeof globalThis<`u`&&`localStorage`in globalThis&&(globalThis.localStorage?.setItem(`token`,t),window.location.href=`/`),t},async me(){return t.get(`/api/v1/auth/me`)},async logout(){typeof globalThis<`u`&&`localStorage`in globalThis&&globalThis.localStorage?.removeItem(`token`);let e=await t.get(`/api/v1/auth/logout`);return window.location.href=`/`,e},toLogin:a}},s=e=>{let{requestInstance:t,entityName:n}=e,r=`/api/v1/entities/${n}`,i=e=>{if(!e)return;let t={...e};return e.fields&&Array.isArray(e.fields)&&(t.fields=e.fields.join(`,`)),e.query&&typeof e.query==`object`&&(t.query=JSON.stringify(e.query)),t};return{async query(e){let n=i(e);return t.get(r,{params:n})},async queryAll(e){let n=i(e);return t.get(`${r}/all`,{params:n})},async get(e){let{id:n,...a}=e,o=i(a);return t.get(`${r}/${n}`,{params:o})},async create(e){return t.post(r,e.data)},async update(e){return t.put(`${r}/${e.id}`,e.data)},async delete(e){return t.delete(`${r}/${e.id}`)},async deleteBatch(e){return t.delete(`${r}/batch`,{data:{ids:e.ids}})},async createBatch(e){return t.post(`${r}/batch`,e.data)},async updateBatch(e){return t.put(`${r}/batch`,e.data)}}},c=e=>{let{requestInstance:t}=e,n=new Map;return new Proxy({},{get(e,r){if(!i(r))return n.has(r)||n.set(r,s({requestInstance:t,entityName:r})),n.get(r)}})},l=e=>{let{requestInstance:t}=e;return{async invoke(e){let{url:n,method:r=`GET`,data:i,options:a={}}=e,o={method:r.toUpperCase(),url:n,...a};return i&&[`POST`,`PUT`,`PATCH`].includes(o.method)?o.data=i:i&&[`GET`,`DELETE`].includes(o.method)&&(o.params=i),t.request(o)}}},u=e=>{let{requestInstance:t}=e;return new Proxy({},{get(e,n){if(!i(n))return new Proxy({},{get(e,r){if(!i(r))return(e={})=>{let i=`/api/integrations/core/${r}`;n!==`core`&&(i=`/api/integrations/providers/${n}/${r}`);let{payload:a={},option:o={}}=e,s=a instanceof FormData?{...o,headers:{...o.headers,"Content-Type":void 0}}:o;return t.post(i,a,s)}}})}})};let d=null;const f=e=>{if(d)return;let{requestInstance:t}=e,n=async e=>{let n=globalThis?.localStorage?.getItem(`token`);if(n)return;let r=e.token,i=await t.post(`/api/v1/auth/token/exchange`,{platform_token:r});i.status===200&&i.data.token&&(globalThis?.localStorage?.setItem(`token`,i.data.token),window.location.href=`/`)},r=e=>{switch(e.data.type){case`mgx-token-saved`:n(e.data.data);break;default:break}};d={requestInstance:t,handleMessage:r},globalThis?.window?.addEventListener(`message`,r)};let p=!1;const m=e=>{f(e);let t={postMessage(e,t,n,r){if(globalThis?.window===void 0||!globalThis?.window?.top){console.warn(`postMessage: window.top is not available`);return}try{let{targetOrigin:i=`*`,targetName:a=globalThis?.window?.name??``}=n||{},o={type:e,targetName:a,data:t};(r??globalThis?.window?.top)?.postMessage(o,i)}catch(e){console.warn(`Failed to send postMessage:`,e)}},createPage(e){let t=e??globalThis?.window?.location?.pathname??``;this.postMessage(`mgx-create-page`,{path:t})}},n=()=>{if(p)return;p=!0;let e=globalThis?.localStorage?.getItem(`token`);e||(t.postMessage(`mgx-token-request`,{domain:globalThis?.window?.location?.href??``},{},globalThis?.window?.opener),t.postMessage(`mgx-token-request`,{domain:globalThis?.window?.location?.href??``}))};return n(),t},h=(e={})=>{let t=r({baseURL:`/`,...e}),n=o({requestInstance:t}),i=c({requestInstance:t}),a=l({requestInstance:t}),s=u({requestInstance:t}),d=m({requestInstance:t});return{auth:n,entities:i,apiCall:a,integrations:s,frame:d}},g=`import { Button } from '@/components/ui/button';
1
+ import e from"axios";import t from"qs";const n=e=>{let t=e.response?.status;if(t&&t>=400&&t!==401)try{typeof window<`u`&&window.top&&window.top?.postMessage({type:`mgx-appview-error`,targetName:window.name,data:{errMsg:e.response?.data?.message||e.response?.data?.detail||`Server Error`,stack:JSON.stringify({url:e.response?.config?.url,data:e.response?.data,status:e.response?.status})}},`*`)}catch(e){console.warn(`Failed to notify parent window about API error:`,e)}},r=(r={})=>{let{onUnauthorized:i,...a}=r,o=typeof globalThis<`u`&&`localStorage`in globalThis&&typeof globalThis.localStorage?.getItem==`function`?globalThis.localStorage.getItem(`token`)??void 0:void 0,s={timeout:6e4,paramsSerializer:e=>t.stringify(e,{arrayFormat:`brackets`}),...a,headers:{"Content-Type":`application/json`,"X-Forwarded-Host":globalThis?.window?.location?.origin??``,...o?{Authorization:`Bearer ${o}`}:{},...a.headers}},c=e.create(s);return c.interceptors.request.use(e=>e,e=>Promise.reject(e)),c.interceptors.response.use(e=>e,e=>{let t=e.response?.status;return t===401&&i&&i(),n(e),Promise.reject(e)}),c},i=[`stripe.com`],a=e=>typeof e==`string`?e.startsWith(`_`)||e.startsWith(`$`)||e===`constructor`||e===`toString`||e===`valueOf`||e===`inspect`||e===`toJSON`:!0,o=()=>{let e=window.location?.href??``,t=``;e.startsWith(`/`)?t=e:e&&(t=e.replace(/^[a-z][a-z0-9+.-]*:\/\/[^/]+/i,``));let n=new URLSearchParams({from_url:t});window.location.href=`/api/v1/auth/login?${n.toString()}`},s=e=>e?i.some(t=>e.toString().includes(t)):!1,c=(e=``)=>Array.from(new Set([...e.split(`,`)||[],`noopener`,`noreferrer`])).filter(Boolean).join(`,`),l=e=>{let{requestInstance:t}=e;return{async login(){let e=new URLSearchParams(window.location.search),t=e.get(`token`);return t&&typeof globalThis<`u`&&`localStorage`in globalThis&&(globalThis.localStorage?.setItem(`token`,t),window.location.href=`/`),t},async me(){return t.get(`/api/v1/auth/me`)},async logout(){typeof globalThis<`u`&&`localStorage`in globalThis&&globalThis.localStorage?.removeItem(`token`);let e=await t.get(`/api/v1/auth/logout`);return window.location.href=`/`,e},toLogin:o}},u=e=>{let{requestInstance:t,entityName:n}=e,r=`/api/v1/entities/${n}`,i=e=>{if(!e)return;let t={...e};return e.fields&&Array.isArray(e.fields)&&(t.fields=e.fields.join(`,`)),e.query&&typeof e.query==`object`&&(t.query=JSON.stringify(e.query)),t};return{async query(e){let n=i(e);return t.get(r,{params:n})},async queryAll(e){let n=i(e);return t.get(`${r}/all`,{params:n})},async get(e){let{id:n,...a}=e,o=i(a);return t.get(`${r}/${n}`,{params:o})},async create(e){return t.post(r,e.data)},async update(e){return t.put(`${r}/${e.id}`,e.data)},async delete(e){return t.delete(`${r}/${e.id}`)},async deleteBatch(e){return t.delete(`${r}/batch`,{data:{ids:e.ids}})},async createBatch(e){return t.post(`${r}/batch`,e.data)},async updateBatch(e){return t.put(`${r}/batch`,e.data)}}},d=e=>{let{requestInstance:t}=e,n=new Map;return new Proxy({},{get(e,r){if(!a(r))return n.has(r)||n.set(r,u({requestInstance:t,entityName:r})),n.get(r)}})},f=e=>{let{requestInstance:t}=e;return{async invoke(e){let{url:n,method:r=`GET`,data:i,options:a={}}=e,o={method:r.toUpperCase(),url:n,...a};return i&&[`POST`,`PUT`,`PATCH`].includes(o.method)?o.data=i:i&&[`GET`,`DELETE`].includes(o.method)&&(o.params=i),t.request(o)}}},p=e=>{let{requestInstance:t}=e;return new Proxy({},{get(e,n){if(!a(n))return new Proxy({},{get(e,r){if(!a(r))return(e={})=>{let i=`/api/integrations/core/${r}`;n!==`core`&&(i=`/api/integrations/providers/${n}/${r}`);let{payload:a={},option:o={}}=e,s=a instanceof FormData?{...o,headers:{...o.headers,"Content-Type":void 0}}:o;return t.post(i,a,s)}}})}})};let m=null;const h=e=>{if(m)return;let{requestInstance:t}=e,n=async e=>{let n=globalThis?.localStorage?.getItem(`token`);if(n)return;let r=e.token,i=await t.post(`/api/v1/auth/token/exchange`,{platform_token:r});i.status===200&&i.data.token&&(globalThis?.localStorage?.setItem(`token`,i.data.token),window.location.href=`/`)},r=e=>{switch(e.data.type){case`mgx-token-saved`:n(e.data.data);break;default:break}};m={requestInstance:t,handleMessage:r},globalThis?.window?.addEventListener(`message`,r)},g=()=>{let e=(e,t,n,r)=>{if(globalThis?.window===void 0||!globalThis?.window?.top){console.warn(`postMessage: window.top is not available`);return}try{let{targetOrigin:i=`*`,targetName:a=globalThis?.window?.name??``}=n||{},o={type:e,targetName:a,data:t};(r??globalThis?.window?.top)?.postMessage(o,i)}catch(e){console.warn(`Failed to send postMessage:`,e)}};return{postMessage:e}};let _=!1;const v=e=>{h(e);let{postMessage:t}=g(),n=()=>{if(_)return;_=!0;let e=globalThis?.localStorage?.getItem(`token`);e||(t(`mgx-token-request`,{domain:globalThis?.window?.location?.href??``},{},globalThis?.window?.opener),t(`mgx-token-request`,{domain:globalThis?.window?.location?.href??``}))};return n(),{createPage(e){let n=e??globalThis?.window?.location?.pathname??``;t(`mgx-create-page`,{path:n})}}},y=()=>{let{postMessage:e}=g(),t=(t,n,r)=>{if(!t)return;if(globalThis?.window===void 0){console.warn(`openUrl: window is not available`);return}if(s(t)){e(`mgx-open-url`,{url:t.toString()},{},globalThis?.window?.opener);return}let i=c(r);return globalThis?.window?.open(t,n,i)};return{openUrl:t}},b=(e={})=>{let t=r({baseURL:`/`,...e}),n=l({requestInstance:t}),i=d({requestInstance:t}),a=f({requestInstance:t}),o=p({requestInstance:t}),s=v({requestInstance:t}),c=y();return{auth:n,entities:i,apiCall:a,integrations:o,frame:s,utils:c}},x=`import { Button } from '@/components/ui/button';
2
2
  import { createClient } from '@metagptx/web-sdk';
3
3
 
4
4
  // Create client instance
@@ -41,6 +41,6 @@ export default function NotFoundPage() {
41
41
  </div>
42
42
  );
43
43
  }
44
- `,_=[{path:`*`,componentName:`NotFoundPage`,componentCode:g}];function v(e={}){let t=e.routes||_,n=new Map;return t.forEach(e=>{let t=`\0virtual:${e.componentName.toLowerCase()}.tsx`;n.set(t,e.componentCode)}),{name:`vite-default-routes`,enforce:`pre`,resolveId(e){for(let n of t){let t=`virtual:${n.componentName.toLowerCase()}`;if(e===t)return`\0${t}.tsx`}return null},load(e){return n.get(e)||null},transform(e,n){if(!n.includes(`App.tsx`)||n.includes(`node_modules`))return null;let r=t.every(t=>e.includes(`import ${t.componentName}`)&&e.includes(t.path));if(r)return null;let i=e,a=[];if(t.forEach(t=>{if(!e.includes(`import ${t.componentName}`)){let e=`virtual:${t.componentName.toLowerCase()}`;a.push(`import ${t.componentName} from '${e}';`)}}),a.length>0){let n=e.match(/(import AuthError from[^;]+;)/);n&&(i=i.replace(n[0],`${n[0]}\n${a.join(`
44
+ `,S=[{path:`*`,componentName:`NotFoundPage`,componentCode:x}];function C(e={}){let t=e.routes||S,n=new Map;return t.forEach(e=>{let t=`\0virtual:${e.componentName.toLowerCase()}.tsx`;n.set(t,e.componentCode)}),{name:`vite-default-routes`,enforce:`pre`,resolveId(e){for(let n of t){let t=`virtual:${n.componentName.toLowerCase()}`;if(e===t)return`\0${t}.tsx`}return null},load(e){return n.get(e)||null},transform(e,n){if(!n.includes(`App.tsx`)||n.includes(`node_modules`))return null;let r=t.every(t=>e.includes(`import ${t.componentName}`)&&e.includes(t.path));if(r)return null;let i=e,a=[];if(t.forEach(t=>{if(!e.includes(`import ${t.componentName}`)){let e=`virtual:${t.componentName.toLowerCase()}`;a.push(`import ${t.componentName} from '${e}';`)}}),a.length>0){let n=e.match(/(import AuthError from[^;]+;)/);n&&(i=i.replace(n[0],`${n[0]}\n${a.join(`
45
45
  `)}`));let r=t,o=r.map(e=>` <Route path="${e.path}" element={<${e.componentName} />} />`).join(`
46
- `),s=i.match(/(\s+)(<\/Routes>)/i);s&&(i=i.replace(s[0],`${s[1]}${o}\n${s[0]}`))}return i===e?null:{code:i,map:null}}}}export{h as createClient,v as viteDefaultRoutes};
46
+ `),s=i.match(/(\s+)(<\/Routes>)/i);s&&(i=i.replace(s[0],`${s[1]}${o}\n${s[0]}`))}return i===e?null:{code:i,map:null}}}}export{b as createClient,C as viteDefaultRoutes};
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@metagptx/web-sdk",
3
3
  "type": "module",
4
- "version": "0.0.32",
4
+ "version": "0.0.34",
5
5
  "packageManager": "pnpm@10.15.0+sha512.486ebc259d3e999a4e8691ce03b5cac4a71cbeca39372a9b762cb500cfdf0873e2cb16abe3d951b1ee2cf012503f027b98b6584e4df22524e0c7450d9ec7aa7b",
6
6
  "description": "TypeScript SDK for interacting with FuncSea API",
7
7
  "author": "MetaGPTX",