@joai/core 0.2.0

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/index.mjs ADDED
@@ -0,0 +1,303 @@
1
+ //#region src/config/urls.ts
2
+ const toSubdomainBaseUrl = (env, subdomain) => {
3
+ if (env === "local") return `http://${subdomain}.joai--api.test`;
4
+ if (env === "devnet") return `https://devnet-${subdomain}.joai.ai`;
5
+ if (env === "testnet") return `https://testnet-${subdomain}.joai.ai`;
6
+ return `https://${subdomain}.joai.ai`;
7
+ };
8
+ const getApiBaseUrl = (env) => toSubdomainBaseUrl(env, "api");
9
+ const getCortexBaseUrl = (env) => {
10
+ if (env === "local") return "http://localhost:3010";
11
+ if (env === "devnet") return "https://devnet-cortex.joai.ai";
12
+ if (env === "testnet") return "https://testnet-cortex.joai.ai";
13
+ return "https://cortex.joai.ai";
14
+ };
15
+ const getAppBaseUrl = (env) => {
16
+ if (env === "local") return "http://app.joai--api.test:3004";
17
+ if (env === "devnet") return "https://devnet.joai.ai";
18
+ if (env === "testnet") return "https://testnet.joai.ai";
19
+ return "https://joai.ai";
20
+ };
21
+ const getMcpServerAgentUrl = (env, agent) => `${toSubdomainBaseUrl(env, "cortex")}/mcp/agents/${agent}`;
22
+ //#endregion
23
+ //#region src/config/constants.ts
24
+ const JoaiUrls = {
25
+ TelegramGroup: "https://telegram.joai.ai",
26
+ Twitter: "https://x.com/JoAiAgents",
27
+ Reddit: "https://www.reddit.com/r/JoAi/",
28
+ GitHub: "https://github.com/JoAiHQ",
29
+ LinkedIn: "https://www.linkedin.com/company/joai",
30
+ Docs: "https://docs.joai.ai",
31
+ DocsApi: "https://docs.joai.ai/api",
32
+ DocsWebhooks: "https://docs.joai.ai/webhooks",
33
+ AppStore: "https://apps.apple.com/app/joai/id6741204807",
34
+ PlayStore: "https://play.google.com/store/apps/details?id=ai.joai.app"
35
+ };
36
+ //#endregion
37
+ //#region src/http/service.ts
38
+ var HttpService = class {
39
+ constructor(baseUrl, cookies, authToken, options) {
40
+ this.get = async (url, options) => await this.request("GET", url, void 0, options);
41
+ this.post = async (url, data, options) => await this.request("POST", url, data, options);
42
+ this.put = async (url, data, options) => await this.request("PUT", url, data, options);
43
+ this.delete = async (url, options) => await this.request("DELETE", url, void 0, options);
44
+ this.getCsrfToken = () => {
45
+ if (typeof document === "undefined") return null;
46
+ const match = document.cookie.match(/(?:^|;\s*)XSRF-TOKEN=([^;]*)/);
47
+ return match ? decodeURIComponent(match[1]) : null;
48
+ };
49
+ this.request = async (httpMethod, url, data, options, isRetry) => {
50
+ const requestUrl = url.startsWith("http") ? url : `${this.baseUrl}/${url}`;
51
+ const requestInfo = this.getRequestInfo(httpMethod, data, options);
52
+ const response = await this.convertResponse(this.fetchFn(requestUrl, requestInfo));
53
+ if (response.original.status === 419 && !isRetry) {
54
+ await this.refreshCsrfToken();
55
+ return this.request(httpMethod, url, data, options, true);
56
+ }
57
+ if (response.original.status === 401) this.onUnauthorized(response);
58
+ if (response.original.status === 429) {
59
+ console.warn(`[HTTP 429] ${httpMethod} ${requestUrl}`);
60
+ this.onTooManyRequests(response);
61
+ }
62
+ if (response.original.status === 503) this.onMaintenanceMode(response);
63
+ if (response.ok) this.onSuccessfulResponse(response);
64
+ return response;
65
+ };
66
+ this.refreshCsrfToken = async () => {
67
+ await this.fetchFn(`${this.baseUrl}/sanctum/csrf-cookie`, {
68
+ method: "GET",
69
+ credentials: "include",
70
+ headers: {
71
+ Accept: "application/json",
72
+ ...this.headers
73
+ }
74
+ });
75
+ };
76
+ this.convertResponse = async (apiRes) => {
77
+ try {
78
+ const response = await apiRes;
79
+ const body = response.status === 204 ? {} : await response.json();
80
+ return {
81
+ data: body.data || null,
82
+ meta: body.meta,
83
+ body,
84
+ errors: body.errors || [],
85
+ ok: response.ok && !(body.errors && body.errors.length > 0),
86
+ noContent: response.status === 204,
87
+ paymentRequired: response.status === 402,
88
+ original: response
89
+ };
90
+ } catch (error) {
91
+ this.onNetworkError(error);
92
+ const anyError = error;
93
+ return {
94
+ data: {},
95
+ errors: anyError.response && (anyError.response.data || {}).errors,
96
+ body: {},
97
+ ok: false,
98
+ noContent: false,
99
+ paymentRequired: false,
100
+ original: anyError
101
+ };
102
+ }
103
+ };
104
+ this.getRequestInfo = (httpMethod = "GET", data, options) => {
105
+ const isJson = options?.isJson !== false;
106
+ const isFormData = data instanceof FormData;
107
+ const situationalHeaders = options?.headers || {};
108
+ const csrfToken = httpMethod !== "GET" ? this.getCsrfToken() : null;
109
+ const headers = {
110
+ Accept: "application/json",
111
+ ...this.authToken ? { Authorization: `Bearer ${this.authToken}` } : {},
112
+ ...csrfToken ? { "X-XSRF-TOKEN": csrfToken } : {},
113
+ ...situationalHeaders,
114
+ ...this.headers
115
+ };
116
+ if (isJson && !isFormData) headers["Content-Type"] = "application/json";
117
+ return {
118
+ method: httpMethod,
119
+ credentials: "include",
120
+ headers,
121
+ body: httpMethod !== "GET" ? isJson && !isFormData ? JSON.stringify(data || {}) : data : void 0
122
+ };
123
+ };
124
+ this.baseUrl = baseUrl;
125
+ this.authToken = authToken || null;
126
+ this.fetchFn = options?.customFetch ?? globalThis.fetch.bind(globalThis);
127
+ this.onNetworkError = () => {};
128
+ this.onSuccessfulResponse = () => {};
129
+ this.onUnauthorized = () => {};
130
+ this.onMaintenanceMode = () => {};
131
+ this.onTooManyRequests = () => {};
132
+ this.headers = {
133
+ ...options?.refererUrl ? { Referer: options.refererUrl } : {},
134
+ ...cookies ? { Cookie: cookies } : {},
135
+ ...options?.headers ?? {}
136
+ };
137
+ }
138
+ };
139
+ //#endregion
140
+ //#region src/helpers/string.ts
141
+ const sanitizeAlphanumeric = (val) => val.replace(/[^a-z0-9]/gi, "");
142
+ const sanitizeNumeric = (val) => {
143
+ return (val.split(".").length > 2 && val.endsWith(".") ? val.slice(0, -1) : val).replace(/[^0-9\.]+/g, "");
144
+ };
145
+ const toNameInitials = (name) => name.split(" ").map((word) => word[0]).join("");
146
+ const toPreviewText = (text, maxChars = 100) => {
147
+ if (!text) return "";
148
+ let sanitized = text.replace(/<\/?(h[1-6])[^>]*>/gi, " - ").replace(/<\/?(p|div|ul|ol|li|br|hr)[^>]*>/gi, " ").replace(/<[^>]+>/g, "").replace(/\s+/g, " ").trim();
149
+ sanitized = sanitized.startsWith("- ") ? sanitized.slice(2) : sanitized;
150
+ sanitized = sanitized.length > maxChars ? sanitized.substring(0, sanitized.lastIndexOf(" ", maxChars)) + "..." : sanitized;
151
+ return sanitized;
152
+ };
153
+ //#endregion
154
+ //#region src/helpers/number.ts
155
+ const abbreviateNumber = (val) => {
156
+ const SI_SYMBOL = [
157
+ "",
158
+ "k",
159
+ "M",
160
+ "G",
161
+ "T",
162
+ "P",
163
+ "E"
164
+ ];
165
+ const tier = Math.log10(Math.abs(val)) / 3 | 0;
166
+ if (tier === 0) return val;
167
+ const suffix = SI_SYMBOL[tier];
168
+ return (val / Math.pow(10, tier * 3)).toFixed(1) + suffix;
169
+ };
170
+ const toHexEncodedNonce = (nonce) => {
171
+ const hex = nonce.toString(16);
172
+ return hex.length % 2 ? "0" + hex : hex;
173
+ };
174
+ const toNftId = (collection, nonce) => `${collection}-${toHexEncodedNonce(nonce)}`;
175
+ //#endregion
176
+ //#region src/helpers/file.ts
177
+ const isFileFormatImage = (filename) => {
178
+ if (!filename) return false;
179
+ const sanitized = filename.toLowerCase();
180
+ return sanitized.endsWith(".png") || sanitized.endsWith(".jpeg") || sanitized.endsWith(".jpg") || sanitized.endsWith(".gif");
181
+ };
182
+ const isFileFormatVideo = (filename) => {
183
+ if (!filename) return false;
184
+ const sanitized = filename.toLowerCase();
185
+ return sanitized.endsWith(".mov") || sanitized.endsWith(".quicktime") || sanitized.endsWith(".mp4") || sanitized.endsWith(".webm");
186
+ };
187
+ const isFileFormatAudio = (filename) => {
188
+ if (!filename) return false;
189
+ const sanitized = filename.toLowerCase();
190
+ return sanitized.endsWith(".acc") || sanitized.endsWith(".flac") || sanitized.endsWith(".m4a") || sanitized.endsWith(".mp3") || sanitized.endsWith(".wav");
191
+ };
192
+ //#endregion
193
+ //#region src/helpers/async.ts
194
+ const withRetry = async (fn, validate, { maxRetries = 5, retryDelay = 1e3 } = {}) => {
195
+ for (let attempt = 1; attempt <= maxRetries; attempt++) {
196
+ const result = await fn();
197
+ if (validate(result)) return result;
198
+ if (attempt === maxRetries) throw new Error("Max retries reached: validation failed");
199
+ await new Promise((resolve) => setTimeout(resolve, retryDelay));
200
+ }
201
+ throw new Error("Unexpected end of retry loop");
202
+ };
203
+ //#endregion
204
+ //#region src/helpers/asset.ts
205
+ const AssetBaseUrl = "https://raw.githubusercontent.com/JoAiHQ/assets/main";
206
+ const getAssetAppLogoUrl = (name) => `${AssetBaseUrl}/apps/logos/${name}`;
207
+ const getAssetChainLogoUrl = (name) => `${AssetBaseUrl}/chains/logos/${name}`;
208
+ const getAssetTokenLogoUrl = (name) => `${AssetBaseUrl}/tokens/logos/${name}`;
209
+ const getAssetWalletLogoUrl = (name) => `${AssetBaseUrl}/wallets/logos/${name}`;
210
+ const getAssetPartnerLogoUrl = (name) => `${AssetBaseUrl}/partners/logos/${name}`;
211
+ //#endregion
212
+ //#region src/budget/types.ts
213
+ const PeriodTypeLabels = {
214
+ 0: "One-time",
215
+ 1: "Daily",
216
+ 2: "Weekly",
217
+ 3: "Monthly"
218
+ };
219
+ const PeriodTypeOptions = [
220
+ {
221
+ value: 0,
222
+ label: "None (one-time)"
223
+ },
224
+ {
225
+ value: 1,
226
+ label: "Daily"
227
+ },
228
+ {
229
+ value: 2,
230
+ label: "Weekly"
231
+ },
232
+ {
233
+ value: 3,
234
+ label: "Monthly"
235
+ }
236
+ ];
237
+ //#endregion
238
+ //#region src/contract/types.ts
239
+ const DefaultCodeMetadata = {
240
+ isUpgradeable: true,
241
+ isReadable: false,
242
+ isPayable: false,
243
+ isPayableBySmartContract: false
244
+ };
245
+ const ContractCategoryDisplayNames = {
246
+ default: "Popular",
247
+ nft: "NFT",
248
+ marketplace: "Marketplace",
249
+ staking: "Staking",
250
+ governance: "Governance",
251
+ defi: "DeFi",
252
+ game: "Game",
253
+ social: "Social",
254
+ identity: "Identity",
255
+ infra: "Infrastructure"
256
+ };
257
+ //#endregion
258
+ //#region src/ogimage/types.ts
259
+ const OG_IMAGE_WIDTH = 1200;
260
+ const OG_IMAGE_HEIGHT = 630;
261
+ const OG_IMAGE_CONFIG = {
262
+ width: OG_IMAGE_WIDTH,
263
+ height: 630
264
+ };
265
+ const OgImageColors = {
266
+ primaryDark: "#66CC66",
267
+ primaryLight: "#98FF98",
268
+ secondaryDark: "#CCCC33",
269
+ secondaryLight: "#FFFF66",
270
+ brandBlack: "#181818",
271
+ brandWhite: "#f3f3f3",
272
+ gray: {
273
+ 400: "#A3A3A3",
274
+ 700: "#404040",
275
+ 800: "#262626",
276
+ 900: "#171717"
277
+ }
278
+ };
279
+ //#endregion
280
+ //#region src/skill/types.ts
281
+ const SkillType = {
282
+ Knowledge: "knowledge",
283
+ ContentTemplate: "content-template",
284
+ PersonalityClone: "personality-clone",
285
+ BrandKit: "brand-kit",
286
+ VideoTemplate: "video-template",
287
+ VisualKit: "visual-kit"
288
+ };
289
+ const SkillMediaBindingRole = {
290
+ Subject: "subject",
291
+ Style: "style",
292
+ Logo: "logo",
293
+ Product: "product",
294
+ Motion: "motion",
295
+ FirstFrame: "first_frame",
296
+ LastFrame: "last_frame"
297
+ };
298
+ const SkillScope = {
299
+ Agent: "agent",
300
+ Team: "team"
301
+ };
302
+ //#endregion
303
+ export { ContractCategoryDisplayNames, DefaultCodeMetadata, HttpService, JoaiUrls, OG_IMAGE_CONFIG, OG_IMAGE_HEIGHT, OG_IMAGE_WIDTH, OgImageColors, PeriodTypeLabels, PeriodTypeOptions, SkillMediaBindingRole, SkillScope, SkillType, abbreviateNumber, getApiBaseUrl, getAppBaseUrl, getAssetAppLogoUrl, getAssetChainLogoUrl, getAssetPartnerLogoUrl, getAssetTokenLogoUrl, getAssetWalletLogoUrl, getCortexBaseUrl, getMcpServerAgentUrl, isFileFormatAudio, isFileFormatImage, isFileFormatVideo, sanitizeAlphanumeric, sanitizeNumeric, toHexEncodedNonce, toNameInitials, toNftId, toPreviewText, withRetry };
package/package.json ADDED
@@ -0,0 +1,33 @@
1
+ {
2
+ "name": "@joai/core",
3
+ "version": "0.2.0",
4
+ "description": "JoAi shared types, HTTP service, and config",
5
+ "license": "MIT",
6
+ "type": "module",
7
+ "main": "./dist/index.mjs",
8
+ "exports": {
9
+ ".": {
10
+ "import": "./dist/index.mjs",
11
+ "types": "./dist/index.d.mts"
12
+ }
13
+ },
14
+ "files": [
15
+ "dist"
16
+ ],
17
+ "scripts": {
18
+ "build": "tsdown",
19
+ "dev": "tsdown --watch",
20
+ "test": "jest",
21
+ "test:watch": "jest --watch"
22
+ },
23
+ "peerDependencies": {
24
+ "@joai/warps": ">=4.0.0"
25
+ },
26
+ "devDependencies": {
27
+ "@types/jest": "^30.0.0",
28
+ "jest": "^30.3.0",
29
+ "ts-jest": "^29.4.6",
30
+ "tsdown": "^0.21.0",
31
+ "typescript": "^5.9.3"
32
+ }
33
+ }