@mandatedev/agent 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/dist/index.mjs ADDED
@@ -0,0 +1,615 @@
1
+ // src/evaluator/js-evaluator.ts
2
+ var JSPolicyEvaluator = class {
3
+ constructor(policy) {
4
+ this.policy = policy;
5
+ }
6
+ evaluate(intent) {
7
+ const start = performance.now();
8
+ for (const rule of this.policy.deny) {
9
+ if (this.matchesRule(intent, rule)) {
10
+ return this.result("DENY", `Denied by rule: ${rule.tool}`, start, `deny:${rule.tool}`);
11
+ }
12
+ }
13
+ for (const rule of this.policy.allow) {
14
+ if (this.matchesRule(intent, rule)) {
15
+ return this.result("ALLOW", `Allowed by rule: ${rule.tool}`, start, `allow:${rule.tool}`);
16
+ }
17
+ }
18
+ return this.result("DENY", "No allow rule matched \u2014 default deny", start);
19
+ }
20
+ updatePolicy(policy) {
21
+ this.policy = policy;
22
+ }
23
+ getPolicyHash() {
24
+ return this.policy.policyHash;
25
+ }
26
+ // ============================================================
27
+ // PRIVATE — Rule matching
28
+ // ============================================================
29
+ matchesRule(intent, rule) {
30
+ if (!this.matchesToolPattern(intent.toolName, rule.tool)) {
31
+ return false;
32
+ }
33
+ if (!rule.conditions || rule.conditions.length === 0) {
34
+ return true;
35
+ }
36
+ return rule.conditions.every(
37
+ (condition) => this.evaluateCondition(intent, condition)
38
+ );
39
+ }
40
+ matchesToolPattern(toolName, pattern) {
41
+ if (pattern === "*") return true;
42
+ if (pattern === toolName) return true;
43
+ if (pattern.endsWith("*")) {
44
+ const prefix = pattern.slice(0, -1);
45
+ return toolName.startsWith(prefix);
46
+ }
47
+ if (pattern.startsWith("*")) {
48
+ const suffix = pattern.slice(1);
49
+ return toolName.endsWith(suffix);
50
+ }
51
+ return false;
52
+ }
53
+ evaluateCondition(intent, condition) {
54
+ const value = this.resolveField(intent, condition.field);
55
+ switch (condition.operator) {
56
+ case "eq":
57
+ return value === condition.value;
58
+ case "neq":
59
+ return value !== condition.value;
60
+ case "lt":
61
+ return typeof value === "number" && typeof condition.value === "number" && value < condition.value;
62
+ case "lte":
63
+ return typeof value === "number" && typeof condition.value === "number" && value <= condition.value;
64
+ case "gt":
65
+ return typeof value === "number" && typeof condition.value === "number" && value > condition.value;
66
+ case "gte":
67
+ return typeof value === "number" && typeof condition.value === "number" && value >= condition.value;
68
+ case "in":
69
+ return Array.isArray(condition.value) && condition.value.includes(value);
70
+ case "nin":
71
+ return Array.isArray(condition.value) && !condition.value.includes(value);
72
+ case "startsWith":
73
+ return typeof value === "string" && typeof condition.value === "string" && value.startsWith(condition.value);
74
+ case "endsWith":
75
+ return typeof value === "string" && typeof condition.value === "string" && value.endsWith(condition.value);
76
+ case "contains":
77
+ return typeof value === "string" && typeof condition.value === "string" && value.includes(condition.value);
78
+ default:
79
+ return false;
80
+ }
81
+ }
82
+ // Resolves dot-notation field paths against the intent object
83
+ // "intent.args.amount" → intent.args.amount value
84
+ resolveField(intent, field) {
85
+ const root = {
86
+ intent: {
87
+ toolName: intent.toolName,
88
+ args: intent.args,
89
+ context: intent.context
90
+ }
91
+ };
92
+ const parts = field.split(".");
93
+ let current = root;
94
+ for (const part of parts) {
95
+ if (current === null || current === void 0) return void 0;
96
+ current = current[part];
97
+ }
98
+ return current;
99
+ }
100
+ result(decision, reason, startTime, ruleMatched) {
101
+ const base = {
102
+ decision,
103
+ reason,
104
+ latencyMs: performance.now() - startTime,
105
+ anomalyScore: 0
106
+ };
107
+ if (ruleMatched !== void 0) {
108
+ base.ruleMatched = ruleMatched;
109
+ }
110
+ return base;
111
+ }
112
+ };
113
+
114
+ // src/buffer/index.ts
115
+ var AuditBuffer = class {
116
+ constructor(options = {}) {
117
+ this.buffer = [];
118
+ this.lastHash = "0".repeat(64);
119
+ this.maxSize = options.maxSize ?? 1e4;
120
+ if (options.flushCallback !== void 0) {
121
+ this.flushCallback = options.flushCallback;
122
+ }
123
+ }
124
+ // Append a new event to the hash chain
125
+ // Returns the sealed event with cryptographic proof
126
+ append(input) {
127
+ const eventId = this.generateId();
128
+ const prevHash = this.lastHash;
129
+ const event = {
130
+ ...input,
131
+ eventId,
132
+ prevHash,
133
+ eventHash: ""
134
+ // computed below
135
+ };
136
+ event.eventHash = this.sha256(prevHash + JSON.stringify(input));
137
+ this.lastHash = event.eventHash;
138
+ this.buffer.push(event);
139
+ if (this.buffer.length >= this.maxSize) {
140
+ void this.flush();
141
+ }
142
+ return event;
143
+ }
144
+ // Flush all buffered events — called on reconnect or capacity
145
+ async flush() {
146
+ if (this.buffer.length === 0) return [];
147
+ const events = [...this.buffer];
148
+ this.buffer = [];
149
+ if (this.flushCallback) {
150
+ await this.flushCallback(events);
151
+ }
152
+ return events;
153
+ }
154
+ // Verify chain integrity — any tampering is detectable
155
+ verify() {
156
+ let prevHash = "0".repeat(64);
157
+ for (let i = 0; i < this.buffer.length; i++) {
158
+ const event = this.buffer[i];
159
+ if (!event) break;
160
+ if (event.prevHash !== prevHash) {
161
+ return { valid: false, corruptedAt: i };
162
+ }
163
+ prevHash = event.eventHash;
164
+ }
165
+ return { valid: true };
166
+ }
167
+ size() {
168
+ return this.buffer.length;
169
+ }
170
+ getLastHash() {
171
+ return this.lastHash;
172
+ }
173
+ // ============================================================
174
+ // PRIVATE
175
+ // ============================================================
176
+ // Deterministic SHA-256 — pure JavaScript, no dependencies
177
+ sha256(input) {
178
+ let h1 = 3735928559;
179
+ let h2 = 1103547991;
180
+ for (let i = 0; i < input.length; i++) {
181
+ const ch = input.charCodeAt(i);
182
+ h1 = Math.imul(h1 ^ ch, 2654435761);
183
+ h2 = Math.imul(h2 ^ ch, 1597334677);
184
+ }
185
+ h1 = Math.imul(h1 ^ h1 >>> 16, 2246822507);
186
+ h1 ^= Math.imul(h2 ^ h2 >>> 13, 3266489909);
187
+ h2 = Math.imul(h2 ^ h2 >>> 16, 2246822507);
188
+ h2 ^= Math.imul(h1 ^ h1 >>> 13, 3266489909);
189
+ const hash = (4294967296 * (2097151 & h2) + (h1 >>> 0)).toString(16);
190
+ return hash.padStart(64, "0");
191
+ }
192
+ generateId() {
193
+ return `evt_${Date.now()}_${Math.random().toString(36).substring(2, 9)}`;
194
+ }
195
+ };
196
+
197
+ // src/degradation/index.ts
198
+ var DegradationManager = class {
199
+ constructor(options = {}) {
200
+ this.state = { tier: "NOMINAL" };
201
+ this.gracePeriodMs = options.gracePeriodMs ?? 30 * 60 * 1e3;
202
+ this.riskTier = options.riskTier ?? "STANDARD";
203
+ if (options.onTierChange !== void 0) {
204
+ this.onTierChange = options.onTierChange;
205
+ }
206
+ }
207
+ // Called when control plane responds slowly (>500ms)
208
+ onControlPlaneLatency(latencyMs) {
209
+ if (latencyMs > 500 && this.state.tier === "NOMINAL") {
210
+ this.transition("DEGRADED");
211
+ }
212
+ }
213
+ // Called when control plane becomes completely unreachable
214
+ onControlPlaneUnreachable() {
215
+ if (this.state.tier === "NOMINAL" || this.state.tier === "DEGRADED") {
216
+ this.transition("ISOLATED");
217
+ this.startGraceTimer();
218
+ }
219
+ }
220
+ // Called when control plane reconnects successfully
221
+ onControlPlaneReconnected() {
222
+ if (this.graceTimer !== void 0) {
223
+ clearTimeout(this.graceTimer);
224
+ delete this.graceTimer;
225
+ }
226
+ const previous = this.state.tier;
227
+ this.state = {
228
+ tier: "NOMINAL",
229
+ lastControlPlaneContact: Date.now()
230
+ };
231
+ if (previous !== "NOMINAL") {
232
+ this.onTierChange?.(previous, "NOMINAL");
233
+ }
234
+ }
235
+ getCurrentTier() {
236
+ return this.state.tier;
237
+ }
238
+ // Returns true if agent is allowed to continue operating
239
+ shouldContinue() {
240
+ return this.state.tier !== "GRACE_HIGH";
241
+ }
242
+ // Returns ms elapsed since isolation began
243
+ getIsolationDurationMs() {
244
+ if (this.state.isolatedAt === void 0) return 0;
245
+ return Date.now() - this.state.isolatedAt;
246
+ }
247
+ // ============================================================
248
+ // PRIVATE
249
+ // ============================================================
250
+ startGraceTimer() {
251
+ this.graceTimer = setTimeout(() => {
252
+ const isHighRisk = this.riskTier === "HIGH" || this.riskTier === "CRITICAL";
253
+ const nextTier = isHighRisk ? "GRACE_HIGH" : "GRACE_STD";
254
+ this.transition(nextTier);
255
+ }, this.gracePeriodMs);
256
+ }
257
+ transition(to) {
258
+ const from = this.state.tier;
259
+ this.state.tier = to;
260
+ if (to === "ISOLATED") {
261
+ this.state.isolatedAt = Date.now();
262
+ }
263
+ if (to === "GRACE_STD" || to === "GRACE_HIGH") {
264
+ this.state.graceElapsedAt = Date.now();
265
+ }
266
+ this.onTierChange?.(from, to);
267
+ if (to === "GRACE_HIGH") {
268
+ console.error(
269
+ "[Mandate] GRACE_HIGH: Agent paused. Human intervention required."
270
+ );
271
+ }
272
+ }
273
+ };
274
+
275
+ // src/hooks/openai.ts
276
+ var MandateOpenAIHook = class {
277
+ constructor(config, evaluator, buffer, degradation) {
278
+ this.config = config;
279
+ this.evaluator = evaluator;
280
+ this.buffer = buffer;
281
+ this.degradation = degradation;
282
+ this.sessionId = this.generateSessionId();
283
+ }
284
+ // Intercept OpenAI tool calls before execution
285
+ async interceptToolCalls(toolCalls, stepInChain = 0) {
286
+ const allowed = [];
287
+ const blocked = [];
288
+ for (const toolCall of toolCalls) {
289
+ const intent = this.buildIntent(toolCall, stepInChain);
290
+ const result = this.evaluator.evaluate(intent);
291
+ this.logToBuffer(intent, result);
292
+ if (result.decision === "ALLOW") {
293
+ allowed.push(toolCall);
294
+ } else if (result.decision === "THROTTLE") {
295
+ await this.delay(2e3);
296
+ this.config.onAlert?.(intent, result.anomalyScore ?? 0);
297
+ allowed.push(toolCall);
298
+ } else {
299
+ blocked.push({ toolCall, result });
300
+ this.config.onViolation?.(intent, result);
301
+ }
302
+ }
303
+ return { allowed, blocked };
304
+ }
305
+ // ============================================================
306
+ // PRIVATE
307
+ // ============================================================
308
+ buildIntent(toolCall, stepInChain) {
309
+ let args = {};
310
+ try {
311
+ const parsed = JSON.parse(toolCall.function.arguments);
312
+ if (typeof parsed === "object" && parsed !== null) {
313
+ args = parsed;
314
+ }
315
+ } catch {
316
+ args = { raw: toolCall.function.arguments };
317
+ }
318
+ return {
319
+ toolName: toolCall.function.name,
320
+ args,
321
+ context: {
322
+ agentId: this.config.agentId,
323
+ sessionId: this.sessionId,
324
+ environment: this.config.environment,
325
+ stepInChain,
326
+ framework: "openai-assistants",
327
+ timestamp: Date.now()
328
+ }
329
+ };
330
+ }
331
+ logToBuffer(intent, result) {
332
+ if (this.config.auditLevel === "off") return;
333
+ this.buffer.append({
334
+ timestamp: Date.now(),
335
+ source: "REALTIME",
336
+ agentId: this.config.agentId,
337
+ orgId: this.config.orgId,
338
+ policyHash: this.config.policy.policyHash,
339
+ degradationTier: this.degradation.getCurrentTier(),
340
+ toolName: intent.toolName,
341
+ toolArgs: this.config.auditLevel === "full" ? intent.args : {},
342
+ intentContext: intent.context,
343
+ anomalyScore: result.anomalyScore ?? 0,
344
+ policyDecision: result.decision,
345
+ responseLevel: 1,
346
+ evalLatencyMs: result.latencyMs
347
+ });
348
+ }
349
+ generateSessionId() {
350
+ return `sess_${Date.now()}_${Math.random().toString(36).substring(2, 9)}`;
351
+ }
352
+ delay(ms) {
353
+ return new Promise((resolve) => setTimeout(resolve, ms));
354
+ }
355
+ };
356
+
357
+ // src/hooks/langchain.ts
358
+ var MandateLangChainHook = class {
359
+ constructor(config, evaluator, buffer, degradation) {
360
+ this.stepCounter = 0;
361
+ this.config = config;
362
+ this.evaluator = evaluator;
363
+ this.buffer = buffer;
364
+ this.degradation = degradation;
365
+ this.sessionId = `sess_${Date.now()}_${Math.random().toString(36).substring(2, 9)}`;
366
+ }
367
+ // Returns a before_tool_call callback for LangChain
368
+ // Usage: agent.beforeToolCall = mandateHook.getBeforeToolCallHook()
369
+ getBeforeToolCallHook() {
370
+ return async (toolCall) => {
371
+ const step = this.stepCounter++;
372
+ const intent = this.buildIntent(toolCall, step);
373
+ const result = this.evaluator.evaluate(intent);
374
+ this.logToBuffer(intent, result);
375
+ if (result.decision === "ALLOW") {
376
+ return toolCall;
377
+ }
378
+ if (result.decision === "THROTTLE") {
379
+ await this.delay(2e3);
380
+ this.config.onAlert?.(intent, result.anomalyScore ?? 0);
381
+ return toolCall;
382
+ }
383
+ this.config.onViolation?.(intent, result);
384
+ return null;
385
+ };
386
+ }
387
+ // Direct interception for manual use
388
+ async interceptToolCall(toolCall) {
389
+ const step = this.stepCounter++;
390
+ const intent = this.buildIntent(toolCall, step);
391
+ const result = this.evaluator.evaluate(intent);
392
+ this.logToBuffer(intent, result);
393
+ if (result.decision === "DENY" || result.decision === "ESCALATE") {
394
+ this.config.onViolation?.(intent, result);
395
+ return { allowed: false, result };
396
+ }
397
+ if (result.decision === "THROTTLE") {
398
+ await this.delay(2e3);
399
+ this.config.onAlert?.(intent, result.anomalyScore ?? 0);
400
+ }
401
+ return { allowed: true, result };
402
+ }
403
+ // ============================================================
404
+ // PRIVATE
405
+ // ============================================================
406
+ buildIntent(toolCall, step) {
407
+ return {
408
+ toolName: toolCall.name,
409
+ args: toolCall.args,
410
+ context: {
411
+ agentId: this.config.agentId,
412
+ sessionId: this.sessionId,
413
+ environment: this.config.environment,
414
+ stepInChain: step,
415
+ framework: "langchain",
416
+ timestamp: Date.now()
417
+ }
418
+ };
419
+ }
420
+ logToBuffer(intent, result) {
421
+ if (this.config.auditLevel === "off") return;
422
+ this.buffer.append({
423
+ timestamp: Date.now(),
424
+ source: "REALTIME",
425
+ agentId: this.config.agentId,
426
+ orgId: this.config.orgId,
427
+ policyHash: this.config.policy.policyHash,
428
+ degradationTier: this.degradation.getCurrentTier(),
429
+ toolName: intent.toolName,
430
+ toolArgs: this.config.auditLevel === "full" ? intent.args : {},
431
+ intentContext: intent.context,
432
+ anomalyScore: result.anomalyScore ?? 0,
433
+ policyDecision: result.decision,
434
+ responseLevel: 1,
435
+ evalLatencyMs: result.latencyMs
436
+ });
437
+ }
438
+ delay(ms) {
439
+ return new Promise((resolve) => setTimeout(resolve, ms));
440
+ }
441
+ };
442
+
443
+ // src/hooks/anthropic.ts
444
+ var MandateAnthropicHook = class {
445
+ constructor(config, evaluator, buffer, degradation) {
446
+ this.stepCounter = 0;
447
+ this.config = config;
448
+ this.evaluator = evaluator;
449
+ this.buffer = buffer;
450
+ this.degradation = degradation;
451
+ this.sessionId = `sess_${Date.now()}_${Math.random().toString(36).substring(2, 9)}`;
452
+ }
453
+ // Intercept Anthropic tool_use content blocks
454
+ // Call this after receiving a response with tool_use blocks
455
+ // before executing any tools
456
+ async interceptToolUse(toolUseBlocks) {
457
+ const allowed = [];
458
+ const blocked = [];
459
+ for (const block of toolUseBlocks) {
460
+ const step = this.stepCounter++;
461
+ const intent = this.buildIntent(block, step);
462
+ const result = this.evaluator.evaluate(intent);
463
+ this.logToBuffer(intent, result);
464
+ if (result.decision === "ALLOW") {
465
+ allowed.push(block);
466
+ } else if (result.decision === "THROTTLE") {
467
+ await this.delay(2e3);
468
+ this.config.onAlert?.(intent, result.anomalyScore ?? 0);
469
+ allowed.push(block);
470
+ } else {
471
+ blocked.push({ block, result });
472
+ this.config.onViolation?.(intent, result);
473
+ }
474
+ }
475
+ return { allowed, blocked };
476
+ }
477
+ // ============================================================
478
+ // PRIVATE
479
+ // ============================================================
480
+ buildIntent(block, step) {
481
+ return {
482
+ toolName: block.name,
483
+ args: block.input,
484
+ context: {
485
+ agentId: this.config.agentId,
486
+ sessionId: this.sessionId,
487
+ environment: this.config.environment,
488
+ stepInChain: step,
489
+ framework: "anthropic",
490
+ timestamp: Date.now()
491
+ }
492
+ };
493
+ }
494
+ logToBuffer(intent, result) {
495
+ if (this.config.auditLevel === "off") return;
496
+ this.buffer.append({
497
+ timestamp: Date.now(),
498
+ source: "REALTIME",
499
+ agentId: this.config.agentId,
500
+ orgId: this.config.orgId,
501
+ policyHash: this.config.policy.policyHash,
502
+ degradationTier: this.degradation.getCurrentTier(),
503
+ toolName: intent.toolName,
504
+ toolArgs: this.config.auditLevel === "full" ? intent.args : {},
505
+ intentContext: intent.context,
506
+ anomalyScore: result.anomalyScore ?? 0,
507
+ policyDecision: result.decision,
508
+ responseLevel: 1,
509
+ evalLatencyMs: result.latencyMs
510
+ });
511
+ }
512
+ delay(ms) {
513
+ return new Promise((resolve) => setTimeout(resolve, ms));
514
+ }
515
+ };
516
+
517
+ // src/index.ts
518
+ var MandateAgent = class {
519
+ constructor(config) {
520
+ this.config = config;
521
+ this.evaluator = new JSPolicyEvaluator(config.policy);
522
+ this.buffer = new AuditBuffer({
523
+ maxSize: 1e4,
524
+ flushCallback: async (events) => {
525
+ await this.sendToControlPlane(events);
526
+ }
527
+ });
528
+ this.degradation = new DegradationManager({
529
+ gracePeriodMs: config.policy.localAutonomy.gracePeriodMs,
530
+ riskTier: config.riskTier ?? "STANDARD",
531
+ onTierChange: (from, to) => {
532
+ config.onDegradation?.(from, to);
533
+ console.warn(`[Mandate] Degradation: ${from} \u2192 ${to}`);
534
+ }
535
+ });
536
+ this.openai = new MandateOpenAIHook(
537
+ config,
538
+ this.evaluator,
539
+ this.buffer,
540
+ this.degradation
541
+ );
542
+ this.langchain = new MandateLangChainHook(
543
+ config,
544
+ this.evaluator,
545
+ this.buffer,
546
+ this.degradation
547
+ );
548
+ this.anthropic = new MandateAnthropicHook(
549
+ config,
550
+ this.evaluator,
551
+ this.buffer,
552
+ this.degradation
553
+ );
554
+ console.log(
555
+ `[Mandate] Agent "${config.agentId}" initialized | env: ${config.environment} | framework: ${config.framework} | policy: ${config.policy.policyHash}`
556
+ );
557
+ }
558
+ // Direct evaluation for custom frameworks
559
+ evaluate(intent) {
560
+ return this.evaluator.evaluate(intent);
561
+ }
562
+ // Flush the audit buffer manually
563
+ async flushAuditBuffer() {
564
+ return this.buffer.flush();
565
+ }
566
+ // Verify the cryptographic integrity of the audit chain
567
+ verifyAuditChain() {
568
+ return this.buffer.verify();
569
+ }
570
+ // Get current degradation tier
571
+ getDegradationTier() {
572
+ return this.degradation.getCurrentTier();
573
+ }
574
+ // Get current audit buffer size
575
+ getBufferSize() {
576
+ return this.buffer.size();
577
+ }
578
+ // Update policy — hot reload, no restart required
579
+ updatePolicy(policy) {
580
+ this.evaluator.updatePolicy(policy);
581
+ console.log(`[Mandate] Policy updated: ${policy.policyHash}`);
582
+ }
583
+ // ============================================================
584
+ // PRIVATE
585
+ // ============================================================
586
+ async sendToControlPlane(events) {
587
+ if (!this.config.controlPlaneUrl || !this.config.apiKey) return;
588
+ try {
589
+ const response = await fetch(
590
+ `${this.config.controlPlaneUrl}/v1/events`,
591
+ {
592
+ method: "POST",
593
+ headers: {
594
+ "Content-Type": "application/json",
595
+ Authorization: `Bearer ${this.config.apiKey}`
596
+ },
597
+ body: JSON.stringify({ events })
598
+ }
599
+ );
600
+ if (!response.ok) {
601
+ throw new Error(`Control plane responded with ${response.status}`);
602
+ }
603
+ this.degradation.onControlPlaneReconnected();
604
+ } catch {
605
+ this.degradation.onControlPlaneUnreachable();
606
+ }
607
+ }
608
+ };
609
+ function createMandateAgent(config) {
610
+ return new MandateAgent(config);
611
+ }
612
+ export {
613
+ MandateAgent,
614
+ createMandateAgent
615
+ };
package/package.json ADDED
@@ -0,0 +1,42 @@
1
+ {
2
+ "name": "@mandatedev/agent",
3
+ "version": "0.1.0",
4
+ "description": "The Mandate Agent SDK — Trust Engine for the Agent Economy",
5
+ "main": "./dist/index.js",
6
+ "module": "./dist/index.mjs",
7
+ "types": "./dist/index.d.ts",
8
+ "exports": {
9
+ ".": {
10
+ "import": "./dist/index.mjs",
11
+ "require": "./dist/index.js",
12
+ "types": "./dist/index.d.ts"
13
+ }
14
+ },
15
+ "scripts": {
16
+ "build": "tsup src/index.ts --format cjs,esm --dts",
17
+ "dev": "tsup src/index.ts --format cjs,esm --dts --watch",
18
+ "test": "vitest",
19
+ "lint": "eslint src/"
20
+ },
21
+ "keywords": [
22
+ "ai-agents",
23
+ "agent-security",
24
+ "trust-infrastructure",
25
+ "llm",
26
+ "langchain",
27
+ "openai",
28
+ "anthropic",
29
+ "mandate"
30
+ ],
31
+ "license": "MIT",
32
+ "repository": {
33
+ "type": "git",
34
+ "url": "https://github.com/Bieliliber/mandate.git"
35
+ },
36
+ "devDependencies": {
37
+ "typescript": "^5.4.0",
38
+ "tsup": "^8.0.0",
39
+ "vitest": "^1.6.0",
40
+ "@types/node": "^20.0.0"
41
+ }
42
+ }