@customyai/customy-sdk 0.1.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/README.md +32 -0
- package/dist/index.d.mts +487 -0
- package/dist/index.d.ts +487 -0
- package/dist/index.js +441 -0
- package/dist/index.mjs +408 -0
- package/package.json +42 -0
package/dist/index.mjs
ADDED
|
@@ -0,0 +1,408 @@
|
|
|
1
|
+
// src/client.ts
|
|
2
|
+
var CustomyClient = class {
|
|
3
|
+
baseUrl;
|
|
4
|
+
apiKey;
|
|
5
|
+
timeout;
|
|
6
|
+
fetchFn;
|
|
7
|
+
constructor(config) {
|
|
8
|
+
this.baseUrl = config.baseUrl.replace(/\/$/, "");
|
|
9
|
+
this.apiKey = config.apiKey;
|
|
10
|
+
this.timeout = config.timeout ?? 3e4;
|
|
11
|
+
this.fetchFn = config.fetch ?? globalThis.fetch;
|
|
12
|
+
}
|
|
13
|
+
// ─── HTTP Layer ─────────────────────────────────
|
|
14
|
+
async request(method, path, body, query) {
|
|
15
|
+
let url = `${this.baseUrl}${path}`;
|
|
16
|
+
if (query) {
|
|
17
|
+
const params = new URLSearchParams(
|
|
18
|
+
Object.entries(query).filter(([, v]) => v !== void 0)
|
|
19
|
+
);
|
|
20
|
+
if (params.toString()) url += `?${params.toString()}`;
|
|
21
|
+
}
|
|
22
|
+
const controller = new AbortController();
|
|
23
|
+
const timer = setTimeout(() => controller.abort(), this.timeout);
|
|
24
|
+
try {
|
|
25
|
+
const res = await this.fetchFn(url, {
|
|
26
|
+
method,
|
|
27
|
+
headers: {
|
|
28
|
+
"Content-Type": "application/json",
|
|
29
|
+
Authorization: `Bearer ${this.apiKey}`
|
|
30
|
+
},
|
|
31
|
+
body: body ? JSON.stringify(body) : void 0,
|
|
32
|
+
signal: controller.signal
|
|
33
|
+
});
|
|
34
|
+
if (!res.ok) {
|
|
35
|
+
const errorBody = await res.json().catch(() => ({}));
|
|
36
|
+
throw new CustomyAPIError(
|
|
37
|
+
res.status,
|
|
38
|
+
errorBody.error ?? res.statusText,
|
|
39
|
+
errorBody
|
|
40
|
+
);
|
|
41
|
+
}
|
|
42
|
+
return await res.json();
|
|
43
|
+
} finally {
|
|
44
|
+
clearTimeout(timer);
|
|
45
|
+
}
|
|
46
|
+
}
|
|
47
|
+
// ─── Tasks ──────────────────────────────────────
|
|
48
|
+
tasks = {
|
|
49
|
+
/** Parse a natural language prompt into a structured task */
|
|
50
|
+
createFromNaturalLanguage: (prompt) => this.request("POST", "/v2/tasks/natural", { prompt }),
|
|
51
|
+
/** Create a task with explicit parameters */
|
|
52
|
+
create: (options) => this.request("POST", "/v2/tasks", options),
|
|
53
|
+
/** Get a task by ID */
|
|
54
|
+
get: (id) => this.request("GET", `/v2/tasks/${id}`),
|
|
55
|
+
/** List tasks with pagination */
|
|
56
|
+
list: (options) => this.request(
|
|
57
|
+
"GET",
|
|
58
|
+
"/v2/tasks",
|
|
59
|
+
void 0,
|
|
60
|
+
{
|
|
61
|
+
page: String(options?.page ?? 1),
|
|
62
|
+
limit: String(options?.limit ?? 20),
|
|
63
|
+
...options?.status ? { status: options.status } : {}
|
|
64
|
+
}
|
|
65
|
+
)
|
|
66
|
+
};
|
|
67
|
+
// ─── Templates ──────────────────────────────────
|
|
68
|
+
templates = {
|
|
69
|
+
/** List all available agent templates */
|
|
70
|
+
list: (options) => this.request(
|
|
71
|
+
"GET",
|
|
72
|
+
"/v2/templates",
|
|
73
|
+
void 0,
|
|
74
|
+
{
|
|
75
|
+
...options?.vertical ? { vertical: options.vertical } : {},
|
|
76
|
+
...options?.tag ? { tag: options.tag } : {}
|
|
77
|
+
}
|
|
78
|
+
),
|
|
79
|
+
/** Get full template details */
|
|
80
|
+
get: (id) => this.request("GET", `/v2/templates/${id}`),
|
|
81
|
+
/** Instantiate a template into an agent config */
|
|
82
|
+
instantiate: (templateId, options) => this.request(
|
|
83
|
+
"POST",
|
|
84
|
+
`/v2/templates/${templateId}/instantiate`,
|
|
85
|
+
options
|
|
86
|
+
)
|
|
87
|
+
};
|
|
88
|
+
// ─── Marketplace ────────────────────────────────
|
|
89
|
+
marketplace = {
|
|
90
|
+
/** Browse marketplace listings */
|
|
91
|
+
browse: (options) => this.request("GET", "/v2/marketplace", void 0, {
|
|
92
|
+
...options?.category ? { category: options.category } : {},
|
|
93
|
+
...options?.tag ? { tag: options.tag } : {},
|
|
94
|
+
...options?.sort ? { sort: options.sort } : {},
|
|
95
|
+
page: String(options?.page ?? 1),
|
|
96
|
+
limit: String(options?.limit ?? 20)
|
|
97
|
+
}),
|
|
98
|
+
/** Get listing details */
|
|
99
|
+
get: (id) => this.request("GET", `/v2/marketplace/${id}`),
|
|
100
|
+
/** Install a skill from the marketplace */
|
|
101
|
+
install: (listingId) => this.request(
|
|
102
|
+
"POST",
|
|
103
|
+
`/v2/marketplace/${listingId}/install`
|
|
104
|
+
),
|
|
105
|
+
/** Submit a review */
|
|
106
|
+
review: (listingId, review) => this.request(
|
|
107
|
+
"POST",
|
|
108
|
+
`/v2/marketplace/${listingId}/review`,
|
|
109
|
+
review
|
|
110
|
+
),
|
|
111
|
+
/** List installed skills */
|
|
112
|
+
installed: () => this.request(
|
|
113
|
+
"GET",
|
|
114
|
+
"/v2/marketplace/installed"
|
|
115
|
+
)
|
|
116
|
+
};
|
|
117
|
+
// ─── AI Triggers ────────────────────────────────
|
|
118
|
+
triggers = {
|
|
119
|
+
/** Create an AI-powered trigger */
|
|
120
|
+
createAI: (options) => this.request(
|
|
121
|
+
"POST",
|
|
122
|
+
"/v2/triggers/ai",
|
|
123
|
+
options
|
|
124
|
+
),
|
|
125
|
+
/** Test an AI trigger evaluation */
|
|
126
|
+
evaluateAI: (options) => this.request(
|
|
127
|
+
"POST",
|
|
128
|
+
"/v2/triggers/ai/evaluate",
|
|
129
|
+
options
|
|
130
|
+
),
|
|
131
|
+
/** List AI triggers */
|
|
132
|
+
listAI: () => this.request(
|
|
133
|
+
"GET",
|
|
134
|
+
"/v2/triggers/ai"
|
|
135
|
+
)
|
|
136
|
+
};
|
|
137
|
+
// ─── Browser Agent ──────────────────────────────
|
|
138
|
+
browser = {
|
|
139
|
+
sessions: {
|
|
140
|
+
/** List browser sessions */
|
|
141
|
+
list: (options) => this.request(
|
|
142
|
+
"GET",
|
|
143
|
+
"/v1/browser/sessions",
|
|
144
|
+
void 0,
|
|
145
|
+
options
|
|
146
|
+
),
|
|
147
|
+
/** Get a specific session */
|
|
148
|
+
get: (sessionId) => this.request("GET", `/v1/browser/sessions/${sessionId}`),
|
|
149
|
+
/** Revoke a session manually */
|
|
150
|
+
revoke: (sessionId) => this.request(
|
|
151
|
+
"DELETE",
|
|
152
|
+
`/v1/browser/sessions/${sessionId}`
|
|
153
|
+
)
|
|
154
|
+
},
|
|
155
|
+
tasks: {
|
|
156
|
+
/** List browser tasks */
|
|
157
|
+
list: (options) => this.request(
|
|
158
|
+
"GET",
|
|
159
|
+
"/v1/browser/tasks",
|
|
160
|
+
void 0,
|
|
161
|
+
options
|
|
162
|
+
),
|
|
163
|
+
/** Resume a task waiting for human input */
|
|
164
|
+
resume: (taskId, action) => this.request(
|
|
165
|
+
"POST",
|
|
166
|
+
`/v1/browser/tasks/${taskId}/resume`,
|
|
167
|
+
{ action }
|
|
168
|
+
),
|
|
169
|
+
/** Get tasks currently waiting for a human */
|
|
170
|
+
getWaiting: () => this.request("GET", "/v1/browser/tasks/waiting")
|
|
171
|
+
},
|
|
172
|
+
/**
|
|
173
|
+
* Helper: Executes a browser task and waits for its completion.
|
|
174
|
+
* Resolves when the task is 'completed', 'failed', 'cancelled', 'timeout',
|
|
175
|
+
* or 'waiting_human' (meaning it needs intervention).
|
|
176
|
+
*/
|
|
177
|
+
executeAndWait: async (taskId, pollIntervalMs = 2e3) => {
|
|
178
|
+
while (true) {
|
|
179
|
+
const res = await this.browser.tasks.list({ taskId });
|
|
180
|
+
const task = res.tasks[0];
|
|
181
|
+
if (!task) throw new Error(`Task ${taskId} not found`);
|
|
182
|
+
if (task.status === "completed" || task.status === "failed" || task.status === "cancelled" || task.status === "timeout" || task.status === "waiting_human") {
|
|
183
|
+
return task;
|
|
184
|
+
}
|
|
185
|
+
await new Promise((r) => setTimeout(r, pollIntervalMs));
|
|
186
|
+
}
|
|
187
|
+
}
|
|
188
|
+
};
|
|
189
|
+
// ─── Payments Namespace ─────────────────────────────────────
|
|
190
|
+
payments = {
|
|
191
|
+
/** Create a payment intent */
|
|
192
|
+
createIntent: (options) => this.request("POST", "/v1/payments/intents", options),
|
|
193
|
+
/** Get payment intent status */
|
|
194
|
+
getIntent: (intentId) => this.request("GET", `/v1/payments/intents/${intentId}`),
|
|
195
|
+
/** List payment intents */
|
|
196
|
+
listIntents: (query) => this.request("GET", "/v1/payments/intents", void 0, query)
|
|
197
|
+
};
|
|
198
|
+
// ─── Voice Namespace ────────────────────────────────────────
|
|
199
|
+
voice = {
|
|
200
|
+
/** Initiate an autonomous voice call */
|
|
201
|
+
initiateCall: (options) => this.request("POST", "/v1/voice/calls", options),
|
|
202
|
+
/** Get voice call details and transcript */
|
|
203
|
+
getCall: (callId) => this.request("GET", `/v1/voice/calls/${callId}`),
|
|
204
|
+
/** List voice calls */
|
|
205
|
+
listCalls: (query) => this.request("GET", "/v1/voice/calls", void 0, query)
|
|
206
|
+
};
|
|
207
|
+
// ─── CRM Namespace ──────────────────────────────────────────
|
|
208
|
+
crm = {
|
|
209
|
+
/** Create or upsert a contact */
|
|
210
|
+
createContact: (options) => this.request("POST", "/v1/crm/contacts", options),
|
|
211
|
+
/** Get contact details */
|
|
212
|
+
getContact: (contactId) => this.request("GET", `/v1/crm/contacts/${contactId}`),
|
|
213
|
+
/** List contacts */
|
|
214
|
+
listContacts: (query) => this.request("GET", "/v1/crm/contacts", void 0, query)
|
|
215
|
+
};
|
|
216
|
+
// ─── Events Namespace ───────────────────────────────────────
|
|
217
|
+
events = {
|
|
218
|
+
/** Publish an event to the Customy Event Backbone */
|
|
219
|
+
publish: (options) => this.request("POST", "/v1/events/publish", options)
|
|
220
|
+
};
|
|
221
|
+
};
|
|
222
|
+
var CustomyAPIError = class extends Error {
|
|
223
|
+
constructor(status, message, body) {
|
|
224
|
+
super(`Customy API Error (${status}): ${message}`);
|
|
225
|
+
this.status = status;
|
|
226
|
+
this.body = body;
|
|
227
|
+
this.name = "CustomyAPIError";
|
|
228
|
+
}
|
|
229
|
+
status;
|
|
230
|
+
body;
|
|
231
|
+
};
|
|
232
|
+
|
|
233
|
+
// src/portfolio.ts
|
|
234
|
+
var CUSTOMY_PRODUCT_KEYS = [
|
|
235
|
+
"agent",
|
|
236
|
+
"ads",
|
|
237
|
+
"analytics",
|
|
238
|
+
"atlas",
|
|
239
|
+
"bookings",
|
|
240
|
+
"campaigns",
|
|
241
|
+
"commerce",
|
|
242
|
+
"content",
|
|
243
|
+
"crm",
|
|
244
|
+
"data",
|
|
245
|
+
"design",
|
|
246
|
+
"domains",
|
|
247
|
+
"engagement",
|
|
248
|
+
"flows",
|
|
249
|
+
"forms",
|
|
250
|
+
"links",
|
|
251
|
+
"mission-control",
|
|
252
|
+
"pages",
|
|
253
|
+
"payments",
|
|
254
|
+
"send",
|
|
255
|
+
"sitesight",
|
|
256
|
+
"support",
|
|
257
|
+
"tables",
|
|
258
|
+
"tasks",
|
|
259
|
+
"voice",
|
|
260
|
+
"whatsapp",
|
|
261
|
+
"work"
|
|
262
|
+
];
|
|
263
|
+
var CUSTOMY_PLATFORM_SERVICE_KEYS = [
|
|
264
|
+
"access",
|
|
265
|
+
"audit",
|
|
266
|
+
"billing",
|
|
267
|
+
"events",
|
|
268
|
+
"integrations",
|
|
269
|
+
"realtime",
|
|
270
|
+
"scheduler",
|
|
271
|
+
"storage"
|
|
272
|
+
];
|
|
273
|
+
var CustomySdkError = class extends Error {
|
|
274
|
+
constructor(product, status, code, body) {
|
|
275
|
+
super(`Customy ${product} SDK request failed (${status}): ${code}`);
|
|
276
|
+
this.product = product;
|
|
277
|
+
this.status = status;
|
|
278
|
+
this.code = code;
|
|
279
|
+
this.body = body;
|
|
280
|
+
this.name = "CustomySdkError";
|
|
281
|
+
}
|
|
282
|
+
product;
|
|
283
|
+
status;
|
|
284
|
+
code;
|
|
285
|
+
body;
|
|
286
|
+
};
|
|
287
|
+
function normalizeBaseUrl(value, product) {
|
|
288
|
+
if (!value) throw new CustomySdkError(product, 0, "SDK_BASE_URL_REQUIRED");
|
|
289
|
+
let url;
|
|
290
|
+
try {
|
|
291
|
+
url = new URL(value);
|
|
292
|
+
} catch {
|
|
293
|
+
throw new CustomySdkError(product, 0, "SDK_BASE_URL_INVALID");
|
|
294
|
+
}
|
|
295
|
+
if (url.protocol !== "https:" || url.username || url.password || url.search || url.hash)
|
|
296
|
+
throw new CustomySdkError(product, 0, "SDK_BASE_URL_INVALID");
|
|
297
|
+
return url.toString().replace(/\/$/, "");
|
|
298
|
+
}
|
|
299
|
+
function buildUrl(baseUrl, path, query, product) {
|
|
300
|
+
if (!path.startsWith("/") || path.startsWith("//") || path.includes("..")) throw new CustomySdkError(product, 0, "SDK_PATH_INVALID");
|
|
301
|
+
const url = new URL(path, `${baseUrl}/`);
|
|
302
|
+
for (const [key, value] of Object.entries(query ?? {})) {
|
|
303
|
+
if (value === void 0 || value === null) continue;
|
|
304
|
+
if (Array.isArray(value)) for (const item of value) url.searchParams.append(key, String(item));
|
|
305
|
+
else url.searchParams.set(key, String(value));
|
|
306
|
+
}
|
|
307
|
+
return url;
|
|
308
|
+
}
|
|
309
|
+
async function readBody(response, maxBytes) {
|
|
310
|
+
if (response.status === 204) return null;
|
|
311
|
+
const text = await response.text();
|
|
312
|
+
if (new TextEncoder().encode(text).byteLength > maxBytes) throw new Error("SDK_RESPONSE_TOO_LARGE");
|
|
313
|
+
if (!text) return null;
|
|
314
|
+
try {
|
|
315
|
+
return JSON.parse(text);
|
|
316
|
+
} catch {
|
|
317
|
+
return text;
|
|
318
|
+
}
|
|
319
|
+
}
|
|
320
|
+
var CustomyProductClient = class {
|
|
321
|
+
product;
|
|
322
|
+
configuredBaseUrl;
|
|
323
|
+
config;
|
|
324
|
+
fetchImpl;
|
|
325
|
+
constructor(product, config) {
|
|
326
|
+
this.product = product;
|
|
327
|
+
this.config = config;
|
|
328
|
+
this.configuredBaseUrl = config.baseUrls?.[product] ?? config.baseUrl;
|
|
329
|
+
this.fetchImpl = config.fetch ?? globalThis.fetch;
|
|
330
|
+
if (!this.fetchImpl) throw new CustomySdkError(product, 0, "SDK_FETCH_REQUIRED");
|
|
331
|
+
}
|
|
332
|
+
get baseUrl() {
|
|
333
|
+
return normalizeBaseUrl(this.configuredBaseUrl, this.product);
|
|
334
|
+
}
|
|
335
|
+
async request(method, path, options = {}) {
|
|
336
|
+
const url = buildUrl(this.baseUrl, path, options.query, this.product);
|
|
337
|
+
const controller = new AbortController();
|
|
338
|
+
const signal = options.signal ? AbortSignal.any([options.signal, controller.signal]) : controller.signal;
|
|
339
|
+
const timeout = setTimeout(() => controller.abort(), options.timeoutMs ?? this.config.timeoutMs ?? 3e4);
|
|
340
|
+
const headers = { accept: "application/json", ...this.config.headers, ...options.headers };
|
|
341
|
+
if (options.body !== void 0) headers["content-type"] ??= "application/json";
|
|
342
|
+
if (this.config.apiKey || this.config.bearerToken) headers.authorization ??= `Bearer ${this.config.bearerToken ?? this.config.apiKey}`;
|
|
343
|
+
if (this.config.internalKey) headers["x-internal-key"] ??= this.config.internalKey;
|
|
344
|
+
try {
|
|
345
|
+
const response = await this.fetchImpl(url, { method, headers, body: options.body === void 0 ? void 0 : JSON.stringify(options.body), cache: "no-store", credentials: "omit", redirect: "error", signal });
|
|
346
|
+
let body;
|
|
347
|
+
try {
|
|
348
|
+
body = await readBody(response, options.maxBytes ?? 4 * 1024 * 1024);
|
|
349
|
+
} catch (error) {
|
|
350
|
+
if (error instanceof Error && error.message === "SDK_RESPONSE_TOO_LARGE") throw new CustomySdkError(this.product, 413, "SDK_RESPONSE_TOO_LARGE");
|
|
351
|
+
throw new CustomySdkError(this.product, 502, "SDK_RESPONSE_INVALID");
|
|
352
|
+
}
|
|
353
|
+
if (!response.ok) {
|
|
354
|
+
const code = body && typeof body === "object" && "code" in body && typeof body.code === "string" ? body.code : `HTTP_${response.status}`;
|
|
355
|
+
throw new CustomySdkError(this.product, response.status, code, body);
|
|
356
|
+
}
|
|
357
|
+
return body;
|
|
358
|
+
} catch (error) {
|
|
359
|
+
if (error instanceof CustomySdkError) throw error;
|
|
360
|
+
if (error instanceof DOMException && error.name === "AbortError") throw new CustomySdkError(this.product, 408, "SDK_TIMEOUT");
|
|
361
|
+
throw new CustomySdkError(this.product, 503, "SDK_UNAVAILABLE");
|
|
362
|
+
} finally {
|
|
363
|
+
clearTimeout(timeout);
|
|
364
|
+
}
|
|
365
|
+
}
|
|
366
|
+
get(path, options) {
|
|
367
|
+
return this.request("GET", path, options);
|
|
368
|
+
}
|
|
369
|
+
post(path, body, options) {
|
|
370
|
+
return this.request("POST", path, { ...options, body });
|
|
371
|
+
}
|
|
372
|
+
put(path, body, options) {
|
|
373
|
+
return this.request("PUT", path, { ...options, body });
|
|
374
|
+
}
|
|
375
|
+
patch(path, body, options) {
|
|
376
|
+
return this.request("PATCH", path, { ...options, body });
|
|
377
|
+
}
|
|
378
|
+
delete(path, options) {
|
|
379
|
+
return this.request("DELETE", path, options);
|
|
380
|
+
}
|
|
381
|
+
};
|
|
382
|
+
var CustomyPortfolioSdk = class {
|
|
383
|
+
clients;
|
|
384
|
+
products;
|
|
385
|
+
platform;
|
|
386
|
+
constructor(config) {
|
|
387
|
+
const keys = [...CUSTOMY_PRODUCT_KEYS, ...CUSTOMY_PLATFORM_SERVICE_KEYS];
|
|
388
|
+
this.clients = Object.fromEntries(keys.map((key) => [key, new CustomyProductClient(key, config)]));
|
|
389
|
+
this.products = Object.fromEntries(CUSTOMY_PRODUCT_KEYS.map((key) => [key, this.clients[key]]));
|
|
390
|
+
this.platform = Object.fromEntries(CUSTOMY_PLATFORM_SERVICE_KEYS.map((key) => [key, this.clients[key]]));
|
|
391
|
+
}
|
|
392
|
+
product(key) {
|
|
393
|
+
return this.products[key];
|
|
394
|
+
}
|
|
395
|
+
service(key) {
|
|
396
|
+
return this.platform[key];
|
|
397
|
+
}
|
|
398
|
+
};
|
|
399
|
+
var createCustomySdk = (config) => new CustomyPortfolioSdk(config);
|
|
400
|
+
export {
|
|
401
|
+
CUSTOMY_PLATFORM_SERVICE_KEYS,
|
|
402
|
+
CUSTOMY_PRODUCT_KEYS,
|
|
403
|
+
CustomyClient,
|
|
404
|
+
CustomyPortfolioSdk,
|
|
405
|
+
CustomyProductClient,
|
|
406
|
+
CustomySdkError,
|
|
407
|
+
createCustomySdk
|
|
408
|
+
};
|
package/package.json
ADDED
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@customyai/customy-sdk",
|
|
3
|
+
"version": "0.1.0",
|
|
4
|
+
"description": "TypeScript umbrella SDK for all Customy products and platform services",
|
|
5
|
+
"main": "./dist/index.js",
|
|
6
|
+
"module": "./dist/index.mjs",
|
|
7
|
+
"types": "./dist/index.d.ts",
|
|
8
|
+
"exports": {
|
|
9
|
+
".": {
|
|
10
|
+
"types": "./dist/index.d.ts",
|
|
11
|
+
"import": "./dist/index.mjs",
|
|
12
|
+
"require": "./dist/index.js"
|
|
13
|
+
}
|
|
14
|
+
},
|
|
15
|
+
"files": [
|
|
16
|
+
"dist"
|
|
17
|
+
],
|
|
18
|
+
"scripts": {
|
|
19
|
+
"build": "tsup src/index.ts --format cjs,esm --dts --clean",
|
|
20
|
+
"dev": "tsup src/index.ts --format cjs,esm --dts --watch",
|
|
21
|
+
"typecheck": "tsc --noEmit"
|
|
22
|
+
},
|
|
23
|
+
"devDependencies": {
|
|
24
|
+
"tsup": "^8.0.0",
|
|
25
|
+
"typescript": "^5.5.0"
|
|
26
|
+
},
|
|
27
|
+
"keywords": [
|
|
28
|
+
"customy",
|
|
29
|
+
"ai",
|
|
30
|
+
"agent",
|
|
31
|
+
"sdk",
|
|
32
|
+
"api",
|
|
33
|
+
"client"
|
|
34
|
+
],
|
|
35
|
+
"license": "MIT",
|
|
36
|
+
"repository": {
|
|
37
|
+
"type": "git",
|
|
38
|
+
"url": "https://github.com/customyco/customynew",
|
|
39
|
+
"directory": "packages/sdk"
|
|
40
|
+
},
|
|
41
|
+
"author": "Customy <hello@customy.ai>"
|
|
42
|
+
}
|