50c 1.5.0 → 2.0.1
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 +49 -235
- package/bin/50c.js +210 -258
- package/lib/config.js +185 -0
- package/lib/core/tools.js +107 -0
- package/lib/index.js +166 -0
- package/lib/packs/beacon.js +224 -0
- package/lib/packs/cf.js +156 -0
- package/lib/packs/labs.js +188 -0
- package/lib/packs/labs_plus.js +246 -0
- package/lib/packs/ux.js +76 -0
- package/lib/packs/whm.js +228 -0
- package/lib/packs/wp.js +82 -0
- package/lib/packs.js +406 -0
- package/lib/vault.js +354 -0
- package/package.json +25 -11
- package/LICENSE +0 -31
package/lib/vault.js
ADDED
|
@@ -0,0 +1,354 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* 50c Vault - Secure Credential Storage
|
|
3
|
+
* Local encryption, cloud storage of encrypted blobs
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
const crypto = require('crypto');
|
|
7
|
+
const fs = require('fs');
|
|
8
|
+
const path = require('path');
|
|
9
|
+
const { MODE, VAULT_DIR, ensureLocalDir, apiRequest } = require('./config');
|
|
10
|
+
|
|
11
|
+
// Crypto constants
|
|
12
|
+
const ALGORITHM = 'aes-256-gcm';
|
|
13
|
+
const PBKDF2_ITERATIONS = 100000;
|
|
14
|
+
const SALT_LENGTH = 32;
|
|
15
|
+
const IV_LENGTH = 16;
|
|
16
|
+
const AUTH_TAG_LENGTH = 16;
|
|
17
|
+
|
|
18
|
+
// Local file paths
|
|
19
|
+
const MASTER_KEY_FILE = path.join(VAULT_DIR, 'master.key.enc');
|
|
20
|
+
const VAULT_DB_FILE = path.join(VAULT_DIR, 'vault.db');
|
|
21
|
+
const SESSION_FILE = path.join(VAULT_DIR, 'session.json');
|
|
22
|
+
|
|
23
|
+
// In-memory session for cloud mode
|
|
24
|
+
let cloudSession = null;
|
|
25
|
+
|
|
26
|
+
// Derive key from passphrase
|
|
27
|
+
function deriveKey(passphrase, salt) {
|
|
28
|
+
return crypto.pbkdf2Sync(passphrase, salt, PBKDF2_ITERATIONS, 32, 'sha256');
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
// Encrypt data
|
|
32
|
+
function encrypt(plaintext, key) {
|
|
33
|
+
const iv = crypto.randomBytes(IV_LENGTH);
|
|
34
|
+
const cipher = crypto.createCipheriv(ALGORITHM, key, iv);
|
|
35
|
+
const encrypted = Buffer.concat([cipher.update(plaintext, 'utf8'), cipher.final()]);
|
|
36
|
+
const authTag = cipher.getAuthTag();
|
|
37
|
+
return Buffer.concat([iv, authTag, encrypted]).toString('base64');
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
// Decrypt data
|
|
41
|
+
function decrypt(encryptedBase64, key) {
|
|
42
|
+
const data = Buffer.from(encryptedBase64, 'base64');
|
|
43
|
+
const iv = data.subarray(0, IV_LENGTH);
|
|
44
|
+
const authTag = data.subarray(IV_LENGTH, IV_LENGTH + AUTH_TAG_LENGTH);
|
|
45
|
+
const encrypted = data.subarray(IV_LENGTH + AUTH_TAG_LENGTH);
|
|
46
|
+
const decipher = crypto.createDecipheriv(ALGORITHM, key, iv);
|
|
47
|
+
decipher.setAuthTag(authTag);
|
|
48
|
+
return decipher.update(encrypted) + decipher.final('utf8');
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
// ==================== LOCAL MODE ====================
|
|
52
|
+
|
|
53
|
+
function loadLocalSession() {
|
|
54
|
+
try {
|
|
55
|
+
if (fs.existsSync(SESSION_FILE)) {
|
|
56
|
+
const session = JSON.parse(fs.readFileSync(SESSION_FILE, 'utf8'));
|
|
57
|
+
if (session.expires_at > Date.now()) {
|
|
58
|
+
return session;
|
|
59
|
+
}
|
|
60
|
+
fs.unlinkSync(SESSION_FILE);
|
|
61
|
+
}
|
|
62
|
+
} catch {}
|
|
63
|
+
return null;
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
function saveLocalSession(masterKey, ttl) {
|
|
67
|
+
ensureLocalDir();
|
|
68
|
+
const session = {
|
|
69
|
+
master_key: masterKey.toString('base64'),
|
|
70
|
+
expires_at: Date.now() + (ttl * 1000),
|
|
71
|
+
last_access: Date.now()
|
|
72
|
+
};
|
|
73
|
+
fs.writeFileSync(SESSION_FILE, JSON.stringify(session), { mode: 0o600 });
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
function clearLocalSession() {
|
|
77
|
+
try {
|
|
78
|
+
if (fs.existsSync(SESSION_FILE)) fs.unlinkSync(SESSION_FILE);
|
|
79
|
+
} catch {}
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
function loadLocalDB() {
|
|
83
|
+
try {
|
|
84
|
+
if (fs.existsSync(VAULT_DB_FILE)) {
|
|
85
|
+
return JSON.parse(fs.readFileSync(VAULT_DB_FILE, 'utf8'));
|
|
86
|
+
}
|
|
87
|
+
} catch {}
|
|
88
|
+
return { credentials: {} };
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
function saveLocalDB(db) {
|
|
92
|
+
ensureLocalDir();
|
|
93
|
+
fs.writeFileSync(VAULT_DB_FILE, JSON.stringify(db, null, 2), { mode: 0o600 });
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
// ==================== CLOUD MODE ====================
|
|
97
|
+
|
|
98
|
+
async function loadCloudSession() {
|
|
99
|
+
if (cloudSession && cloudSession.expires_at > Date.now()) {
|
|
100
|
+
return cloudSession;
|
|
101
|
+
}
|
|
102
|
+
return null;
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
function saveCloudSession(masterKey, ttl) {
|
|
106
|
+
cloudSession = {
|
|
107
|
+
master_key: masterKey.toString('base64'),
|
|
108
|
+
expires_at: Date.now() + (ttl * 1000)
|
|
109
|
+
};
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
function clearCloudSession() {
|
|
113
|
+
cloudSession = null;
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
// ==================== UNIFIED API ====================
|
|
117
|
+
|
|
118
|
+
async function isInitialized() {
|
|
119
|
+
if (MODE === 'local') {
|
|
120
|
+
return fs.existsSync(MASTER_KEY_FILE);
|
|
121
|
+
} else {
|
|
122
|
+
const response = await apiRequest('GET', '/vault/status');
|
|
123
|
+
return response?.initialized || false;
|
|
124
|
+
}
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
async function isUnlocked() {
|
|
128
|
+
if (MODE === 'local') {
|
|
129
|
+
return !!loadLocalSession();
|
|
130
|
+
} else {
|
|
131
|
+
return !!(await loadCloudSession());
|
|
132
|
+
}
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
async function init(passphrase) {
|
|
136
|
+
if (passphrase.length < 8) {
|
|
137
|
+
throw new Error('Passphrase must be at least 8 characters');
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
const masterKey = crypto.randomBytes(32);
|
|
141
|
+
const salt = crypto.randomBytes(SALT_LENGTH);
|
|
142
|
+
const derivedKey = deriveKey(passphrase, salt);
|
|
143
|
+
const encryptedMasterKey = encrypt(masterKey.toString('hex'), derivedKey);
|
|
144
|
+
|
|
145
|
+
if (MODE === 'local') {
|
|
146
|
+
ensureLocalDir();
|
|
147
|
+
const stored = Buffer.concat([salt, Buffer.from(encryptedMasterKey, 'base64')]);
|
|
148
|
+
fs.writeFileSync(MASTER_KEY_FILE, stored.toString('base64'), { mode: 0o600 });
|
|
149
|
+
saveLocalDB({ credentials: {} });
|
|
150
|
+
return { success: true, mode: 'local', path: VAULT_DIR };
|
|
151
|
+
} else {
|
|
152
|
+
// Store encrypted master key in cloud
|
|
153
|
+
await apiRequest('POST', '/vault/init', {
|
|
154
|
+
encrypted_master_key: Buffer.concat([salt, Buffer.from(encryptedMasterKey, 'base64')]).toString('base64')
|
|
155
|
+
});
|
|
156
|
+
return { success: true, mode: 'cloud' };
|
|
157
|
+
}
|
|
158
|
+
}
|
|
159
|
+
|
|
160
|
+
async function unlock(passphrase, ttl = 3600) {
|
|
161
|
+
if (MODE === 'local') {
|
|
162
|
+
if (!fs.existsSync(MASTER_KEY_FILE)) {
|
|
163
|
+
throw new Error('Vault not initialized. Run vault_init first.');
|
|
164
|
+
}
|
|
165
|
+
|
|
166
|
+
const data = Buffer.from(fs.readFileSync(MASTER_KEY_FILE, 'utf8'), 'base64');
|
|
167
|
+
const salt = data.subarray(0, SALT_LENGTH);
|
|
168
|
+
const encryptedKey = data.subarray(SALT_LENGTH);
|
|
169
|
+
const derivedKey = deriveKey(passphrase, salt);
|
|
170
|
+
|
|
171
|
+
try {
|
|
172
|
+
const masterKeyHex = decrypt(encryptedKey.toString('base64'), derivedKey);
|
|
173
|
+
const masterKey = Buffer.from(masterKeyHex, 'hex');
|
|
174
|
+
saveLocalSession(masterKey, ttl);
|
|
175
|
+
return { success: true, ttl };
|
|
176
|
+
} catch {
|
|
177
|
+
throw new Error('Invalid passphrase');
|
|
178
|
+
}
|
|
179
|
+
} else {
|
|
180
|
+
// Cloud mode - get encrypted master key, decrypt locally
|
|
181
|
+
const response = await apiRequest('GET', '/vault/key');
|
|
182
|
+
if (!response?.encrypted_master_key) {
|
|
183
|
+
throw new Error('Vault not initialized');
|
|
184
|
+
}
|
|
185
|
+
|
|
186
|
+
const data = Buffer.from(response.encrypted_master_key, 'base64');
|
|
187
|
+
const salt = data.subarray(0, SALT_LENGTH);
|
|
188
|
+
const encryptedKey = data.subarray(SALT_LENGTH);
|
|
189
|
+
const derivedKey = deriveKey(passphrase, salt);
|
|
190
|
+
|
|
191
|
+
try {
|
|
192
|
+
const masterKeyHex = decrypt(encryptedKey.toString('base64'), derivedKey);
|
|
193
|
+
const masterKey = Buffer.from(masterKeyHex, 'hex');
|
|
194
|
+
saveCloudSession(masterKey, ttl);
|
|
195
|
+
return { success: true, ttl };
|
|
196
|
+
} catch {
|
|
197
|
+
throw new Error('Invalid passphrase');
|
|
198
|
+
}
|
|
199
|
+
}
|
|
200
|
+
}
|
|
201
|
+
|
|
202
|
+
function lock() {
|
|
203
|
+
if (MODE === 'local') {
|
|
204
|
+
clearLocalSession();
|
|
205
|
+
} else {
|
|
206
|
+
clearCloudSession();
|
|
207
|
+
}
|
|
208
|
+
return { success: true };
|
|
209
|
+
}
|
|
210
|
+
|
|
211
|
+
async function getMasterKey() {
|
|
212
|
+
if (MODE === 'local') {
|
|
213
|
+
const session = loadLocalSession();
|
|
214
|
+
if (!session) return null;
|
|
215
|
+
return Buffer.from(session.master_key, 'base64');
|
|
216
|
+
} else {
|
|
217
|
+
const session = await loadCloudSession();
|
|
218
|
+
if (!session) return null;
|
|
219
|
+
return Buffer.from(session.master_key, 'base64');
|
|
220
|
+
}
|
|
221
|
+
}
|
|
222
|
+
|
|
223
|
+
async function add(name, value) {
|
|
224
|
+
const masterKey = await getMasterKey();
|
|
225
|
+
if (!masterKey) throw new Error('Vault locked. Unlock first.');
|
|
226
|
+
|
|
227
|
+
const encrypted = encrypt(value, masterKey);
|
|
228
|
+
|
|
229
|
+
if (MODE === 'local') {
|
|
230
|
+
const db = loadLocalDB();
|
|
231
|
+
db.credentials[name] = { encrypted, updated_at: Date.now() };
|
|
232
|
+
saveLocalDB(db);
|
|
233
|
+
} else {
|
|
234
|
+
await apiRequest('POST', '/vault/credentials', { name, encrypted });
|
|
235
|
+
}
|
|
236
|
+
|
|
237
|
+
return { success: true, name };
|
|
238
|
+
}
|
|
239
|
+
|
|
240
|
+
async function get(name) {
|
|
241
|
+
const masterKey = await getMasterKey();
|
|
242
|
+
if (!masterKey) throw new Error('Vault locked. Unlock first.');
|
|
243
|
+
|
|
244
|
+
let encrypted;
|
|
245
|
+
|
|
246
|
+
if (MODE === 'local') {
|
|
247
|
+
const db = loadLocalDB();
|
|
248
|
+
if (!db.credentials[name]) throw new Error(`Not found: ${name}`);
|
|
249
|
+
encrypted = db.credentials[name].encrypted;
|
|
250
|
+
} else {
|
|
251
|
+
const response = await apiRequest('GET', `/vault/credentials/${encodeURIComponent(name)}`);
|
|
252
|
+
if (!response?.encrypted) throw new Error(`Not found: ${name}`);
|
|
253
|
+
encrypted = response.encrypted;
|
|
254
|
+
}
|
|
255
|
+
|
|
256
|
+
return decrypt(encrypted, masterKey);
|
|
257
|
+
}
|
|
258
|
+
|
|
259
|
+
async function list(namespace = null) {
|
|
260
|
+
if (MODE === 'local') {
|
|
261
|
+
const db = loadLocalDB();
|
|
262
|
+
let names = Object.keys(db.credentials);
|
|
263
|
+
if (namespace) {
|
|
264
|
+
names = names.filter(n => n.startsWith(namespace + '/'));
|
|
265
|
+
}
|
|
266
|
+
return names.sort();
|
|
267
|
+
} else {
|
|
268
|
+
const response = await apiRequest('GET', '/vault/credentials');
|
|
269
|
+
let names = response?.names || [];
|
|
270
|
+
if (namespace) {
|
|
271
|
+
names = names.filter(n => n.startsWith(namespace + '/'));
|
|
272
|
+
}
|
|
273
|
+
return names.sort();
|
|
274
|
+
}
|
|
275
|
+
}
|
|
276
|
+
|
|
277
|
+
async function remove(name) {
|
|
278
|
+
const masterKey = await getMasterKey();
|
|
279
|
+
if (!masterKey) throw new Error('Vault locked. Unlock first.');
|
|
280
|
+
|
|
281
|
+
if (MODE === 'local') {
|
|
282
|
+
const db = loadLocalDB();
|
|
283
|
+
if (!db.credentials[name]) throw new Error(`Not found: ${name}`);
|
|
284
|
+
delete db.credentials[name];
|
|
285
|
+
saveLocalDB(db);
|
|
286
|
+
} else {
|
|
287
|
+
await apiRequest('DELETE', `/vault/credentials/${encodeURIComponent(name)}`);
|
|
288
|
+
}
|
|
289
|
+
|
|
290
|
+
return { success: true, name };
|
|
291
|
+
}
|
|
292
|
+
|
|
293
|
+
async function status() {
|
|
294
|
+
return {
|
|
295
|
+
mode: MODE,
|
|
296
|
+
initialized: await isInitialized(),
|
|
297
|
+
locked: !(await isUnlocked()),
|
|
298
|
+
path: MODE === 'local' ? VAULT_DIR : 'cloud'
|
|
299
|
+
};
|
|
300
|
+
}
|
|
301
|
+
|
|
302
|
+
// YOLO mode - long session
|
|
303
|
+
async function yolo(passphrase) {
|
|
304
|
+
return unlock(passphrase, 365 * 24 * 3600);
|
|
305
|
+
}
|
|
306
|
+
|
|
307
|
+
// Tool definitions for MCP
|
|
308
|
+
const VAULT_TOOLS = [
|
|
309
|
+
{ name: 'vault_status', description: 'Check vault status. FREE.', inputSchema: { type: 'object', properties: {} } },
|
|
310
|
+
{ name: 'vault_init', description: 'Initialize vault with passphrase. FREE.', inputSchema: { type: 'object', properties: { passphrase: { type: 'string', minLength: 8 } }, required: ['passphrase'] } },
|
|
311
|
+
{ name: 'vault_unlock', description: 'Unlock vault for session. FREE.', inputSchema: { type: 'object', properties: { passphrase: { type: 'string' }, ttl: { type: 'number', default: 3600 } }, required: ['passphrase'] } },
|
|
312
|
+
{ name: 'vault_lock', description: 'Lock vault immediately. FREE.', inputSchema: { type: 'object', properties: {} } },
|
|
313
|
+
{ name: 'vault_yolo', description: 'YOLO mode - stay unlocked until lock/reboot. FREE.', inputSchema: { type: 'object', properties: { passphrase: { type: 'string' } }, required: ['passphrase'] } },
|
|
314
|
+
{ name: 'vault_add', description: 'Add credential to vault. FREE.', inputSchema: { type: 'object', properties: { name: { type: 'string' }, value: { type: 'string' } }, required: ['name', 'value'] } },
|
|
315
|
+
{ name: 'vault_get', description: 'Get credential from vault. FREE.', inputSchema: { type: 'object', properties: { name: { type: 'string' } }, required: ['name'] } },
|
|
316
|
+
{ name: 'vault_list', description: 'List credentials in vault. FREE.', inputSchema: { type: 'object', properties: { namespace: { type: 'string' } } } },
|
|
317
|
+
{ name: 'vault_delete', description: 'Delete credential from vault. FREE.', inputSchema: { type: 'object', properties: { name: { type: 'string' } }, required: ['name'] } }
|
|
318
|
+
];
|
|
319
|
+
|
|
320
|
+
// Handle vault tool calls
|
|
321
|
+
async function handleTool(name, args) {
|
|
322
|
+
try {
|
|
323
|
+
switch (name) {
|
|
324
|
+
case 'vault_status': return await status();
|
|
325
|
+
case 'vault_init': return await init(args.passphrase);
|
|
326
|
+
case 'vault_unlock': return await unlock(args.passphrase, args.ttl);
|
|
327
|
+
case 'vault_lock': return lock();
|
|
328
|
+
case 'vault_yolo': return await yolo(args.passphrase);
|
|
329
|
+
case 'vault_add': return await add(args.name, args.value);
|
|
330
|
+
case 'vault_get': return { value: await get(args.name) };
|
|
331
|
+
case 'vault_list': return { credentials: await list(args.namespace) };
|
|
332
|
+
case 'vault_delete': return await remove(args.name);
|
|
333
|
+
default: return { error: `Unknown vault tool: ${name}` };
|
|
334
|
+
}
|
|
335
|
+
} catch (e) {
|
|
336
|
+
return { error: e.message };
|
|
337
|
+
}
|
|
338
|
+
}
|
|
339
|
+
|
|
340
|
+
module.exports = {
|
|
341
|
+
VAULT_TOOLS,
|
|
342
|
+
handleTool,
|
|
343
|
+
status,
|
|
344
|
+
init,
|
|
345
|
+
unlock,
|
|
346
|
+
lock,
|
|
347
|
+
yolo,
|
|
348
|
+
add,
|
|
349
|
+
get,
|
|
350
|
+
list,
|
|
351
|
+
remove,
|
|
352
|
+
isUnlocked,
|
|
353
|
+
getMasterKey
|
|
354
|
+
};
|
package/package.json
CHANGED
|
@@ -1,23 +1,37 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "50c",
|
|
3
|
-
"version": "
|
|
4
|
-
"description": "AI
|
|
3
|
+
"version": "2.0.1",
|
|
4
|
+
"description": "AI toolkit. One install, 100+ tools. Works everywhere.",
|
|
5
|
+
"main": "lib/index.js",
|
|
5
6
|
"bin": {
|
|
6
7
|
"50c": "./bin/50c.js"
|
|
7
8
|
},
|
|
8
|
-
"
|
|
9
|
-
"
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
9
|
+
"keywords": [
|
|
10
|
+
"mcp",
|
|
11
|
+
"ai",
|
|
12
|
+
"llm",
|
|
13
|
+
"tools",
|
|
14
|
+
"genius",
|
|
15
|
+
"bcalc",
|
|
16
|
+
"vault",
|
|
17
|
+
"cloudflare",
|
|
18
|
+
"whm",
|
|
19
|
+
"cpanel",
|
|
20
|
+
"wordpress"
|
|
21
|
+
],
|
|
22
|
+
"author": "genxis.com",
|
|
23
|
+
"license": "MIT",
|
|
14
24
|
"homepage": "https://50c.ai",
|
|
25
|
+
"repository": {
|
|
26
|
+
"type": "git",
|
|
27
|
+
"url": "https://github.com/genxis/50c"
|
|
28
|
+
},
|
|
15
29
|
"engines": {
|
|
16
|
-
"node": ">=18
|
|
30
|
+
"node": ">=18"
|
|
17
31
|
},
|
|
18
32
|
"files": [
|
|
19
33
|
"bin/",
|
|
20
|
-
"
|
|
21
|
-
"
|
|
34
|
+
"lib/",
|
|
35
|
+
"README.md"
|
|
22
36
|
]
|
|
23
37
|
}
|
package/LICENSE
DELETED
|
@@ -1,31 +0,0 @@
|
|
|
1
|
-
PROPRIETARY SOFTWARE LICENSE
|
|
2
|
-
|
|
3
|
-
Copyright (c) 2026 genxis. All Rights Reserved.
|
|
4
|
-
|
|
5
|
-
This software and associated documentation files (the "Software") are the
|
|
6
|
-
proprietary property of genxis.
|
|
7
|
-
|
|
8
|
-
USAGE TERMS:
|
|
9
|
-
1. You may install and use this Software only with a valid API key.
|
|
10
|
-
2. Each use of the Software requires credits at published rates.
|
|
11
|
-
3. You may NOT copy, modify, merge, publish, distribute, sublicense, or sell
|
|
12
|
-
copies of the Software.
|
|
13
|
-
4. You may NOT reverse engineer, decompile, or disassemble the Software.
|
|
14
|
-
5. You may NOT remove or bypass the payment/credit system.
|
|
15
|
-
6. You may NOT create derivative works based on the Software.
|
|
16
|
-
|
|
17
|
-
RESTRICTIONS:
|
|
18
|
-
- This is a commercial product. No free usage rights are granted.
|
|
19
|
-
- Source code is provided for transparency only, not for modification.
|
|
20
|
-
- Unauthorized use, reproduction, or distribution is strictly prohibited.
|
|
21
|
-
|
|
22
|
-
WARRANTY DISCLAIMER:
|
|
23
|
-
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
24
|
-
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
25
|
-
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
|
|
26
|
-
|
|
27
|
-
LIMITATION OF LIABILITY:
|
|
28
|
-
IN NO EVENT SHALL GENXIS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
29
|
-
LIABILITY ARISING FROM THE USE OF THE SOFTWARE.
|
|
30
|
-
|
|
31
|
-
Contact: https://genxis.one
|