@enactprotocol/shared 1.0.12

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.
Files changed (97) hide show
  1. package/dist/LocalToolResolver.d.ts +84 -0
  2. package/dist/LocalToolResolver.js +353 -0
  3. package/dist/api/enact-api.d.ts +124 -0
  4. package/dist/api/enact-api.js +406 -0
  5. package/dist/api/index.d.ts +2 -0
  6. package/dist/api/index.js +2 -0
  7. package/dist/api/types.d.ts +83 -0
  8. package/dist/api/types.js +1 -0
  9. package/dist/core/DaggerExecutionProvider.d.ts +169 -0
  10. package/dist/core/DaggerExecutionProvider.js +996 -0
  11. package/dist/core/DirectExecutionProvider.d.ts +23 -0
  12. package/dist/core/DirectExecutionProvider.js +406 -0
  13. package/dist/core/EnactCore.d.ts +138 -0
  14. package/dist/core/EnactCore.js +609 -0
  15. package/dist/core/index.d.ts +3 -0
  16. package/dist/core/index.js +3 -0
  17. package/dist/exec/index.d.ts +3 -0
  18. package/dist/exec/index.js +3 -0
  19. package/dist/exec/logger.d.ts +11 -0
  20. package/dist/exec/logger.js +57 -0
  21. package/dist/exec/validate.d.ts +5 -0
  22. package/dist/exec/validate.js +167 -0
  23. package/dist/index.d.ts +25 -0
  24. package/dist/index.js +29 -0
  25. package/dist/lib/enact-direct.d.ts +156 -0
  26. package/dist/lib/enact-direct.js +158 -0
  27. package/dist/lib/index.d.ts +1 -0
  28. package/dist/lib/index.js +1 -0
  29. package/dist/security/index.d.ts +3 -0
  30. package/dist/security/index.js +3 -0
  31. package/dist/security/security.d.ts +23 -0
  32. package/dist/security/security.js +137 -0
  33. package/dist/security/sign.d.ts +103 -0
  34. package/dist/security/sign.js +532 -0
  35. package/dist/security/verification-enforcer.d.ts +41 -0
  36. package/dist/security/verification-enforcer.js +181 -0
  37. package/dist/services/McpCoreService.d.ts +102 -0
  38. package/dist/services/McpCoreService.js +120 -0
  39. package/dist/services/index.d.ts +1 -0
  40. package/dist/services/index.js +1 -0
  41. package/dist/types.d.ts +130 -0
  42. package/dist/types.js +3 -0
  43. package/dist/utils/config.d.ts +32 -0
  44. package/dist/utils/config.js +78 -0
  45. package/dist/utils/env-loader.d.ts +54 -0
  46. package/dist/utils/env-loader.js +270 -0
  47. package/dist/utils/help.d.ts +36 -0
  48. package/dist/utils/help.js +248 -0
  49. package/dist/utils/index.d.ts +7 -0
  50. package/dist/utils/index.js +7 -0
  51. package/dist/utils/logger.d.ts +35 -0
  52. package/dist/utils/logger.js +75 -0
  53. package/dist/utils/silent-monitor.d.ts +67 -0
  54. package/dist/utils/silent-monitor.js +242 -0
  55. package/dist/utils/timeout.d.ts +5 -0
  56. package/dist/utils/timeout.js +23 -0
  57. package/dist/utils/version.d.ts +4 -0
  58. package/dist/utils/version.js +14 -0
  59. package/dist/web/env-manager-server.d.ts +29 -0
  60. package/dist/web/env-manager-server.js +367 -0
  61. package/dist/web/index.d.ts +1 -0
  62. package/dist/web/index.js +1 -0
  63. package/package.json +79 -0
  64. package/src/LocalToolResolver.ts +424 -0
  65. package/src/api/enact-api.ts +569 -0
  66. package/src/api/index.ts +2 -0
  67. package/src/api/types.ts +93 -0
  68. package/src/core/DaggerExecutionProvider.ts +1308 -0
  69. package/src/core/DirectExecutionProvider.ts +484 -0
  70. package/src/core/EnactCore.ts +833 -0
  71. package/src/core/index.ts +3 -0
  72. package/src/exec/index.ts +3 -0
  73. package/src/exec/logger.ts +63 -0
  74. package/src/exec/validate.ts +238 -0
  75. package/src/index.ts +42 -0
  76. package/src/lib/enact-direct.ts +258 -0
  77. package/src/lib/index.ts +1 -0
  78. package/src/security/index.ts +3 -0
  79. package/src/security/security.ts +188 -0
  80. package/src/security/sign.ts +797 -0
  81. package/src/security/verification-enforcer.ts +268 -0
  82. package/src/services/McpCoreService.ts +203 -0
  83. package/src/services/index.ts +1 -0
  84. package/src/types.ts +190 -0
  85. package/src/utils/config.ts +97 -0
  86. package/src/utils/env-loader.ts +370 -0
  87. package/src/utils/help.ts +257 -0
  88. package/src/utils/index.ts +7 -0
  89. package/src/utils/logger.ts +83 -0
  90. package/src/utils/silent-monitor.ts +328 -0
  91. package/src/utils/timeout.ts +26 -0
  92. package/src/utils/version.ts +16 -0
  93. package/src/web/env-manager-server.ts +465 -0
  94. package/src/web/index.ts +1 -0
  95. package/src/web/static/app.js +663 -0
  96. package/src/web/static/index.html +117 -0
  97. package/src/web/static/style.css +291 -0
@@ -0,0 +1,833 @@
1
+ // src/core/EnactCore.ts - Core library for both CLI and MCP usage
2
+ import type {
3
+ EnactTool,
4
+ ExecutionResult,
5
+ ExecutionEnvironment,
6
+ } from "../types.js";
7
+ import { EnactApiClient } from "../api/enact-api.js";
8
+ import {
9
+ verifyCommandSafety,
10
+ sanitizeEnvironmentVariables,
11
+ } from "../security/security.js";
12
+ import {
13
+ validateToolStructure,
14
+ validateInputs,
15
+ validateOutput,
16
+ } from "../exec/validate.js";
17
+ import { DirectExecutionProvider } from "./DirectExecutionProvider.js";
18
+ import { DaggerExecutionProvider } from "./DaggerExecutionProvider.js";
19
+ import { resolveToolEnvironmentVariables } from "../utils/env-loader.js";
20
+ import logger from "../exec/logger.js";
21
+ import yaml from "yaml";
22
+ import {
23
+ verifyTool as verifyToolSignatureWithPolicy,
24
+ VERIFICATION_POLICIES,
25
+ } from "../security/sign.js";
26
+ import {
27
+ enforceSignatureVerification,
28
+ createVerificationFailureResult,
29
+ logSecurityAudit,
30
+ } from "../security/verification-enforcer.js";
31
+ import fs from "fs";
32
+ import path from "path";
33
+
34
+ export interface EnactCoreOptions {
35
+ apiUrl?: string;
36
+ supabaseUrl?: string;
37
+ executionProvider?: "direct" | "docker" | "dagger" | "cloud";
38
+ authToken?: string;
39
+ verbose?: boolean;
40
+ defaultTimeout?: string;
41
+ verificationPolicy?: "permissive" | "enterprise" | "paranoid";
42
+ // Dagger-specific options
43
+ daggerOptions?: {
44
+ baseImage?: string;
45
+ enableNetwork?: boolean;
46
+ enableHostFS?: boolean;
47
+ maxMemory?: string;
48
+ maxCPU?: string;
49
+ };
50
+ }
51
+
52
+ export interface ToolSearchOptions {
53
+ query: string;
54
+ limit?: number;
55
+ tags?: string[];
56
+ author?: string;
57
+ format?: "json" | "table" | "list";
58
+ }
59
+
60
+ export interface ToolExecuteOptions {
61
+ timeout?: string;
62
+ verifyPolicy?: "permissive" | "enterprise" | "paranoid";
63
+ skipVerification?: boolean;
64
+ force?: boolean;
65
+ dryRun?: boolean;
66
+ verbose?: boolean;
67
+ }
68
+
69
+ export class EnactCore {
70
+ private apiClient: EnactApiClient;
71
+ private executionProvider: DirectExecutionProvider | DaggerExecutionProvider;
72
+ private options: EnactCoreOptions;
73
+
74
+ constructor(options: EnactCoreOptions = {}) {
75
+ this.options = {
76
+ apiUrl: "https://enact.tools",
77
+ supabaseUrl: "https://xjnhhxwxovjifdxdwzih.supabase.co",
78
+ executionProvider: "direct",
79
+ defaultTimeout: "30s",
80
+ verificationPolicy: "permissive",
81
+ ...options,
82
+ };
83
+
84
+ this.apiClient = new EnactApiClient(
85
+ this.options.apiUrl,
86
+ this.options.supabaseUrl,
87
+ );
88
+
89
+ // Initialize the appropriate execution provider
90
+ this.executionProvider = this.createExecutionProvider();
91
+ }
92
+ /**
93
+ * Set authentication token for API operations
94
+ */
95
+ setAuthToken(token: string): void {
96
+ this.options.authToken = token;
97
+ }
98
+
99
+ /**
100
+ * Search for tools
101
+ */
102
+ async searchTools(options: ToolSearchOptions): Promise<EnactTool[]> {
103
+ try {
104
+ logger.info(`Searching for tools with query: "${options.query}"`);
105
+
106
+ const searchParams = {
107
+ query: options.query,
108
+ limit: options.limit,
109
+ tags: options.tags,
110
+ };
111
+
112
+ const results = await this.apiClient.searchTools(searchParams);
113
+
114
+ // Parse and validate results
115
+ const tools: EnactTool[] = [];
116
+ for (const result of results) {
117
+ if (result.name) {
118
+ try {
119
+ const tool = await this.getToolByName(result.name);
120
+ if (tool) {
121
+ tools.push(tool);
122
+ }
123
+ } catch (error) {
124
+ logger.warn(`Failed to fetch tool ${result.name}:`, error);
125
+ }
126
+ }
127
+ }
128
+
129
+ logger.info(`Found ${tools.length} tools`);
130
+ return tools;
131
+ } catch (error) {
132
+ logger.error("Error searching tools:", error);
133
+
134
+ // If it's a 502 error (API server issue), try fallback to local filtering
135
+ if (error instanceof Error && error.message.includes("502")) {
136
+ logger.info(
137
+ "Search API unavailable, trying fallback to local filtering...",
138
+ );
139
+ return this.searchToolsFallback(options);
140
+ }
141
+
142
+ throw new Error(
143
+ `Search failed: ${error instanceof Error ? error.message : String(error)}`,
144
+ );
145
+ }
146
+ }
147
+
148
+ /**
149
+ * Fallback search method that gets all tools and filters locally
150
+ */
151
+ private async searchToolsFallback(
152
+ options: ToolSearchOptions,
153
+ ): Promise<EnactTool[]> {
154
+ try {
155
+ logger.info("Using fallback search method...");
156
+
157
+ // Get all tools (limited to avoid overwhelming the API)
158
+ const allTools = await this.apiClient.getTools({
159
+ limit: options.limit || 100,
160
+ });
161
+
162
+ // Filter tools locally based on search criteria
163
+ const filteredTools: EnactTool[] = [];
164
+ const query = options.query.toLowerCase();
165
+
166
+ for (const result of allTools) {
167
+ if (result.name) {
168
+ try {
169
+ const tool = await this.getToolByName(result.name);
170
+ if (tool) {
171
+ // Check if tool matches search criteria
172
+ const matchesQuery =
173
+ tool.name.toLowerCase().includes(query) ||
174
+ (tool.description &&
175
+ tool.description.toLowerCase().includes(query)) ||
176
+ (tool.tags &&
177
+ tool.tags.some((tag) => tag.toLowerCase().includes(query)));
178
+
179
+ const matchesTags =
180
+ !options.tags ||
181
+ !options.tags.length ||
182
+ (tool.tags &&
183
+ options.tags.some((searchTag) =>
184
+ tool.tags!.some((toolTag) =>
185
+ toolTag.toLowerCase().includes(searchTag.toLowerCase()),
186
+ ),
187
+ ));
188
+
189
+ const matchesAuthor =
190
+ !options.author ||
191
+ (tool.authors &&
192
+ tool.authors.some(
193
+ (author) =>
194
+ author.name &&
195
+ author.name
196
+ .toLowerCase()
197
+ .includes(options.author!.toLowerCase()),
198
+ ));
199
+
200
+ if (matchesQuery && matchesTags && matchesAuthor) {
201
+ filteredTools.push(tool);
202
+
203
+ // Apply limit if specified
204
+ if (options.limit && filteredTools.length >= options.limit) {
205
+ break;
206
+ }
207
+ }
208
+ }
209
+ } catch (error) {
210
+ logger.warn(
211
+ `Failed to fetch tool ${result.name} in fallback search:`,
212
+ error,
213
+ );
214
+ }
215
+ }
216
+ }
217
+
218
+ logger.info(`Fallback search found ${filteredTools.length} tools`);
219
+ return filteredTools;
220
+ } catch (fallbackError) {
221
+ logger.error("Fallback search also failed:", fallbackError);
222
+ throw new Error(
223
+ `Search failed (including fallback): ${fallbackError instanceof Error ? fallbackError.message : String(fallbackError)}`,
224
+ );
225
+ }
226
+ }
227
+
228
+ /**
229
+ * Get a specific tool by name
230
+ */
231
+ async getToolByName(
232
+ name: string,
233
+ version?: string,
234
+ ): Promise<EnactTool | null> {
235
+ try {
236
+ logger.info(`Fetching tool: ${name}${version ? `@${version}` : ""}`);
237
+
238
+ const response = await this.apiClient.getTool(name);
239
+
240
+ if (!response) {
241
+ logger.info(`Tool not found: ${name}`);
242
+ return null;
243
+ }
244
+
245
+ // Parse tool from response
246
+ let tool: EnactTool;
247
+
248
+ if (response.content && typeof response.content === "string") {
249
+ tool = yaml.parse(response.content);
250
+ } else if (
251
+ response.raw_content &&
252
+ typeof response.raw_content === "string"
253
+ ) {
254
+ try {
255
+ tool = JSON.parse(response.raw_content);
256
+ } catch {
257
+ tool = yaml.parse(response.raw_content);
258
+ }
259
+
260
+ // Merge signature information
261
+ if (response.signature || response.signatures) {
262
+ tool.signature = response.signature;
263
+ tool.signatures = response.signatures;
264
+ }
265
+ } else {
266
+ // Map database fields to tool format
267
+ tool = {
268
+ name: response.name,
269
+ description: response.description,
270
+ command: response.command,
271
+ timeout: response.timeout || "30s",
272
+ tags: response.tags || [],
273
+ license: response.license || response.spdx_license,
274
+ outputSchema: response.output_schema || response.outputSchema,
275
+ enact: response.protocol_version || response.enact || "1.0.0",
276
+ version: response.version,
277
+ inputSchema: response.input_schema || response.inputSchema,
278
+ examples: response.examples,
279
+ annotations: response.annotations,
280
+ env: response.env_vars || response.env,
281
+ resources: response.resources,
282
+ signature: response.signature,
283
+ signatures: response.signatures,
284
+ namespace: response.namespace,
285
+ };
286
+ }
287
+
288
+ logger.info(`Successfully fetched tool: ${tool.name}`);
289
+ return tool;
290
+ } catch (error) {
291
+ if (error instanceof Error && error.message.includes("404")) {
292
+ logger.info(`Tool not found: ${name}`);
293
+ return null;
294
+ }
295
+
296
+ logger.error(
297
+ `Error fetching tool: ${error instanceof Error ? error.message : String(error)}`,
298
+ );
299
+ throw error;
300
+ }
301
+ }
302
+
303
+ /**
304
+ * Execute a tool by name
305
+ */
306
+ async executeToolByName(
307
+ name: string,
308
+ inputs: Record<string, any> = {},
309
+ options: ToolExecuteOptions = {},
310
+ ): Promise<ExecutionResult> {
311
+ const executionId = this.generateExecutionId();
312
+
313
+ try {
314
+ // Fetch the tool
315
+ const tool = await this.getToolByName(name);
316
+
317
+ if (!tool) {
318
+ return {
319
+ success: false,
320
+ error: {
321
+ message: `Tool not found: ${name}`,
322
+ code: "NOT_FOUND",
323
+ },
324
+ metadata: {
325
+ executionId,
326
+ toolName: name,
327
+ executedAt: new Date().toISOString(),
328
+ environment: "direct",
329
+ },
330
+ };
331
+ }
332
+
333
+ // Execute the tool
334
+ return await this.executeTool(tool, inputs, options);
335
+ } catch (error) {
336
+ return {
337
+ success: false,
338
+ error: {
339
+ message: (error as Error).message,
340
+ code: "EXECUTION_ERROR",
341
+ },
342
+ metadata: {
343
+ executionId,
344
+ toolName: name,
345
+ executedAt: new Date().toISOString(),
346
+ environment: "direct",
347
+ },
348
+ };
349
+ }
350
+ }
351
+
352
+ /**
353
+ * Execute a tool directly
354
+ */
355
+ async executeTool(
356
+ tool: EnactTool,
357
+ inputs: Record<string, any> = {},
358
+ options: ToolExecuteOptions = {},
359
+ ): Promise<ExecutionResult> {
360
+ const executionId = this.generateExecutionId();
361
+
362
+ try {
363
+ logger.info(`Executing tool: ${tool.name}`);
364
+
365
+ // Validate tool structure
366
+ validateToolStructure(tool);
367
+
368
+ // MANDATORY SIGNATURE VERIFICATION - All tools must be verified before execution
369
+ const verificationResult = await enforceSignatureVerification(tool, {
370
+ skipVerification: options.skipVerification,
371
+ verifyPolicy: options.verifyPolicy,
372
+ force: options.force,
373
+ allowUnsigned: false, // Never allow unsigned tools in production
374
+ });
375
+
376
+ // Log security audit information
377
+ logSecurityAudit(tool, verificationResult, verificationResult.allowed, {
378
+ skipVerification: options.skipVerification,
379
+ verifyPolicy: options.verifyPolicy,
380
+ force: options.force,
381
+ });
382
+
383
+ // Block execution if verification fails
384
+ if (!verificationResult.allowed) {
385
+ return createVerificationFailureResult(
386
+ tool,
387
+ verificationResult,
388
+ executionId,
389
+ );
390
+ }
391
+
392
+ // Validate inputs
393
+ const validatedInputs = validateInputs(tool, inputs);
394
+
395
+ // Check command safety
396
+ const safetyCheck = verifyCommandSafety(tool.command, tool);
397
+ if (!safetyCheck.isSafe && !options.force) {
398
+ return {
399
+ success: false,
400
+ error: {
401
+ message: `Unsafe command blocked: ${safetyCheck.blocked?.join(", ")}`,
402
+ code: "COMMAND_UNSAFE",
403
+ details: safetyCheck,
404
+ },
405
+ metadata: {
406
+ executionId,
407
+ toolName: tool.name,
408
+ version: tool.version,
409
+ executedAt: new Date().toISOString(),
410
+ environment: "direct",
411
+ command: tool.command,
412
+ },
413
+ };
414
+ }
415
+
416
+ // Log warnings
417
+ if (safetyCheck.warnings.length > 0) {
418
+ safetyCheck.warnings.forEach((warning: string) => logger.warn(warning));
419
+ }
420
+
421
+ // Dry run - just validate and return
422
+ if (options.dryRun) {
423
+ return {
424
+ success: true,
425
+ output: {
426
+ dryRun: true,
427
+ tool: tool.name,
428
+ command: tool.command,
429
+ inputs: validatedInputs,
430
+ safetyCheck,
431
+ },
432
+ metadata: {
433
+ executionId,
434
+ toolName: tool.name,
435
+ version: tool.version,
436
+ executedAt: new Date().toISOString(),
437
+ environment: "direct",
438
+ command: tool.command,
439
+ },
440
+ };
441
+ }
442
+
443
+ // Setup execution environment
444
+ // Load package environment variables from .env files
445
+ const envResult = await resolveToolEnvironmentVariables(
446
+ tool.name,
447
+ tool.env,
448
+ );
449
+
450
+ // Log any missing required environment variables
451
+ if (envResult.missing.length > 0) {
452
+ logger.warn(
453
+ `Missing required environment variables: ${envResult.missing.join(", ")}`,
454
+ );
455
+ }
456
+
457
+ const environment: ExecutionEnvironment = {
458
+ vars: {
459
+ ...envResult.resolved,
460
+ ...sanitizeEnvironmentVariables(validatedInputs),
461
+ },
462
+ resources: tool.resources,
463
+ namespace: tool.namespace,
464
+ };
465
+
466
+ // Setup execution provider
467
+ await this.executionProvider.setup(tool);
468
+
469
+ // Execute the tool
470
+ const result = await this.executionProvider.execute(
471
+ tool,
472
+ validatedInputs,
473
+ environment,
474
+ );
475
+
476
+ // Validate output if schema is provided
477
+ if (result.success && result.output && tool.outputSchema) {
478
+ try {
479
+ result.output = validateOutput(tool, result.output);
480
+ } catch (error) {
481
+ logger.warn(`Output validation failed: ${(error as Error).message}`);
482
+ }
483
+ }
484
+
485
+ logger.info(
486
+ `Tool execution completed: ${tool.name} (success: ${result.success})`,
487
+ );
488
+ return result;
489
+ } catch (error) {
490
+ logger.error(`Error executing tool: ${(error as Error).message}`);
491
+
492
+ return {
493
+ success: false,
494
+ error: {
495
+ message: (error as Error).message,
496
+ code: "EXECUTION_ERROR",
497
+ details: error,
498
+ },
499
+ metadata: {
500
+ executionId,
501
+ toolName: tool.name,
502
+ version: tool.version,
503
+ executedAt: new Date().toISOString(),
504
+ environment: "direct",
505
+ command: tool.command,
506
+ },
507
+ };
508
+ } finally {
509
+ // Cleanup
510
+ try {
511
+ await this.executionProvider.cleanup();
512
+ } catch (cleanupError) {
513
+ logger.error("Error during cleanup:", cleanupError);
514
+ }
515
+ }
516
+ }
517
+
518
+ /**
519
+ * Execute a tool from raw YAML definition
520
+ */
521
+ async executeRawTool(
522
+ toolYaml: string,
523
+ inputs: Record<string, any> = {},
524
+ options: ToolExecuteOptions = {},
525
+ ): Promise<ExecutionResult> {
526
+ try {
527
+ // Parse the YAML
528
+ const tool = yaml.parse(toolYaml) as EnactTool;
529
+
530
+ // Validate that it's a proper tool definition
531
+ if (!tool || typeof tool !== "object") {
532
+ throw new Error(
533
+ "Invalid tool definition: YAML must contain a tool object",
534
+ );
535
+ }
536
+
537
+ // Check for required fields
538
+ if (!tool.name || !tool.description || !tool.command) {
539
+ throw new Error(
540
+ "Invalid tool definition: missing required fields (name, description, command)",
541
+ );
542
+ }
543
+
544
+ // Execute the tool
545
+ return await this.executeTool(tool, inputs, options);
546
+ } catch (error) {
547
+ const executionId = this.generateExecutionId();
548
+
549
+ return {
550
+ success: false,
551
+ error: {
552
+ message: (error as Error).message,
553
+ code: "PARSE_ERROR",
554
+ },
555
+ metadata: {
556
+ executionId,
557
+ toolName: "unknown",
558
+ executedAt: new Date().toISOString(),
559
+ environment: "direct",
560
+ },
561
+ };
562
+ }
563
+ }
564
+
565
+ /**
566
+ * Verify a tool's signature
567
+ */
568
+ async verifyTool(
569
+ name: string,
570
+ policy?: string,
571
+ ): Promise<{
572
+ verified: boolean;
573
+ signatures: any[];
574
+ policy: string;
575
+ errors?: string[];
576
+ }> {
577
+ try {
578
+ const tool = await this.getToolByName(name);
579
+
580
+ if (!tool) {
581
+ return {
582
+ verified: false,
583
+ signatures: [],
584
+ policy: policy || "permissive",
585
+ errors: [`Tool not found: ${name}`],
586
+ };
587
+ }
588
+
589
+ // Load public key from keys/file-public.pem
590
+ let publicKey: string | undefined;
591
+ try {
592
+ const keyPath = path.resolve(__dirname, "../../keys/file-public.pem");
593
+ publicKey = fs.readFileSync(keyPath, "utf8");
594
+ } catch (e) {
595
+ logger.warn("Could not load public key for signature verification:", e);
596
+ }
597
+ if (!publicKey) {
598
+ return {
599
+ verified: false,
600
+ signatures: [],
601
+ policy: policy || "permissive",
602
+ errors: ["Public key not found for signature verification"],
603
+ };
604
+ }
605
+ const policyKey = (policy || "permissive").toUpperCase() as
606
+ | "PERMISSIVE"
607
+ | "ENTERPRISE"
608
+ | "PARANOID";
609
+ const policyObj = VERIFICATION_POLICIES[policyKey];
610
+ const verificationResult = await verifyToolSignatureWithPolicy(
611
+ tool,
612
+ policyObj,
613
+ );
614
+
615
+ if (!verificationResult.isValid) {
616
+ return {
617
+ verified: false,
618
+ signatures: [],
619
+ policy: policy || "permissive",
620
+ errors: verificationResult.errors,
621
+ };
622
+ }
623
+
624
+ const signatures: any[] = [];
625
+
626
+ if (tool.signature) {
627
+ signatures.push(tool.signature);
628
+ }
629
+
630
+ if (tool.signatures) {
631
+ signatures.push(...Object.values(tool.signatures));
632
+ }
633
+
634
+ return {
635
+ verified: verificationResult.isValid,
636
+ signatures,
637
+ policy: policy || "permissive",
638
+ };
639
+ } catch (error) {
640
+ return {
641
+ verified: false,
642
+ signatures: [],
643
+ policy: policy || "permissive",
644
+ errors: [`Verification error: ${(error as Error).message}`],
645
+ };
646
+ }
647
+ }
648
+
649
+ /**
650
+ * Check if a tool exists
651
+ */
652
+ async toolExists(name: string): Promise<boolean> {
653
+ try {
654
+ const tool = await this.getToolByName(name);
655
+ return tool !== null;
656
+ } catch (error) {
657
+ return false;
658
+ }
659
+ }
660
+
661
+ /**
662
+ * Get tools by tags
663
+ */
664
+ async getToolsByTags(
665
+ tags: string[],
666
+ limit: number = 20,
667
+ ): Promise<EnactTool[]> {
668
+ return this.searchTools({
669
+ query: tags.join(" "),
670
+ tags,
671
+ limit,
672
+ });
673
+ }
674
+
675
+ /**
676
+ * Get tools by author
677
+ */
678
+ async getToolsByAuthor(
679
+ author: string,
680
+ limit: number = 20,
681
+ ): Promise<EnactTool[]> {
682
+ return this.searchTools({
683
+ query: `author:${author}`,
684
+ author,
685
+ limit,
686
+ });
687
+ }
688
+
689
+ /**
690
+ * Get all tools with filters
691
+ */
692
+ async getTools(
693
+ options: {
694
+ limit?: number;
695
+ offset?: number;
696
+ tags?: string[];
697
+ author?: string;
698
+ } = {},
699
+ ): Promise<EnactTool[]> {
700
+ try {
701
+ const apiResults = await this.apiClient.getTools(options);
702
+
703
+ // Parse and validate results
704
+ const tools: EnactTool[] = [];
705
+ for (const result of apiResults) {
706
+ if (result.name) {
707
+ try {
708
+ const tool = await this.getToolByName(result.name);
709
+ if (tool) {
710
+ tools.push(tool);
711
+ }
712
+ } catch (error) {
713
+ logger.warn(`Failed to fetch tool ${result.name}:`, error);
714
+ }
715
+ }
716
+ }
717
+
718
+ return tools;
719
+ } catch (error) {
720
+ logger.error("Error getting tools:", error);
721
+ throw new Error(
722
+ `Failed to get tools: ${error instanceof Error ? error.message : String(error)}`,
723
+ );
724
+ }
725
+ }
726
+
727
+ /**
728
+ * Get authentication status (placeholder - would need actual auth implementation)
729
+ */
730
+ async getAuthStatus(): Promise<{
731
+ authenticated: boolean;
732
+ user?: string;
733
+ server?: string;
734
+ }> {
735
+ // This would need to check actual auth state
736
+ // For now, return based on whether we have a token
737
+ return {
738
+ authenticated: !!this.options.authToken,
739
+ server: this.options.apiUrl,
740
+ };
741
+ }
742
+
743
+ /**
744
+ * Publish a tool (requires authentication)
745
+ */
746
+ async publishTool(
747
+ tool: EnactTool,
748
+ ): Promise<{ success: boolean; message: string }> {
749
+ if (!this.options.authToken) {
750
+ return {
751
+ success: false,
752
+ message: "Authentication required to publish tools",
753
+ };
754
+ }
755
+
756
+ try {
757
+ validateToolStructure(tool);
758
+
759
+ await this.apiClient.publishTool(tool, this.options.authToken);
760
+
761
+ return {
762
+ success: true,
763
+ message: `Successfully published tool: ${tool.name}`,
764
+ };
765
+ } catch (error) {
766
+ return {
767
+ success: false,
768
+ message: `Failed to publish tool: ${error instanceof Error ? error.message : String(error)}`,
769
+ };
770
+ }
771
+ }
772
+
773
+ /**
774
+ * Get tool information (alias for getToolByName for consistency)
775
+ */
776
+ async getToolInfo(name: string, version?: string): Promise<EnactTool | null> {
777
+ return this.getToolByName(name, version);
778
+ }
779
+
780
+ /**
781
+ * Get core library status
782
+ */
783
+ async getStatus(): Promise<{
784
+ executionProvider: string;
785
+ apiUrl: string;
786
+ verificationPolicy: string;
787
+ defaultTimeout: string;
788
+ authenticated: boolean;
789
+ }> {
790
+ const authStatus = await this.getAuthStatus();
791
+
792
+ return {
793
+ executionProvider: this.options.executionProvider || "direct",
794
+ apiUrl: this.options.apiUrl || "https://enact.tools",
795
+ verificationPolicy: this.options.verificationPolicy || "permissive",
796
+ defaultTimeout: this.options.defaultTimeout || "30s",
797
+ authenticated: authStatus.authenticated,
798
+ };
799
+ }
800
+
801
+ /**
802
+ * Create the appropriate execution provider based on options
803
+ */
804
+ private createExecutionProvider():
805
+ | DirectExecutionProvider
806
+ | DaggerExecutionProvider {
807
+ switch (this.options.executionProvider) {
808
+ case "dagger":
809
+ return new DaggerExecutionProvider(this.options.daggerOptions);
810
+ case "direct":
811
+ default:
812
+ return new DirectExecutionProvider();
813
+ }
814
+ }
815
+
816
+ /**
817
+ * Switch execution provider at runtime
818
+ */
819
+ switchExecutionProvider(provider: "direct" | "dagger", options?: any): void {
820
+ this.options.executionProvider = provider;
821
+ this.executionProvider = this.createExecutionProvider();
822
+ }
823
+
824
+ /**
825
+ * Generate execution ID
826
+ */
827
+ private generateExecutionId(): string {
828
+ return `exec_${Date.now()}_${Math.random().toString(36).substring(2, 9)}`;
829
+ }
830
+ }
831
+
832
+ // Export a default instance
833
+ export const enactCore = new EnactCore();