@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.
- package/dist/LocalToolResolver.d.ts +84 -0
- package/dist/LocalToolResolver.js +353 -0
- package/dist/api/enact-api.d.ts +124 -0
- package/dist/api/enact-api.js +406 -0
- package/dist/api/index.d.ts +2 -0
- package/dist/api/index.js +2 -0
- package/dist/api/types.d.ts +83 -0
- package/dist/api/types.js +1 -0
- package/dist/core/DaggerExecutionProvider.d.ts +169 -0
- package/dist/core/DaggerExecutionProvider.js +996 -0
- package/dist/core/DirectExecutionProvider.d.ts +23 -0
- package/dist/core/DirectExecutionProvider.js +406 -0
- package/dist/core/EnactCore.d.ts +138 -0
- package/dist/core/EnactCore.js +609 -0
- package/dist/core/index.d.ts +3 -0
- package/dist/core/index.js +3 -0
- package/dist/exec/index.d.ts +3 -0
- package/dist/exec/index.js +3 -0
- package/dist/exec/logger.d.ts +11 -0
- package/dist/exec/logger.js +57 -0
- package/dist/exec/validate.d.ts +5 -0
- package/dist/exec/validate.js +167 -0
- package/dist/index.d.ts +25 -0
- package/dist/index.js +29 -0
- package/dist/lib/enact-direct.d.ts +156 -0
- package/dist/lib/enact-direct.js +158 -0
- package/dist/lib/index.d.ts +1 -0
- package/dist/lib/index.js +1 -0
- package/dist/security/index.d.ts +3 -0
- package/dist/security/index.js +3 -0
- package/dist/security/security.d.ts +23 -0
- package/dist/security/security.js +137 -0
- package/dist/security/sign.d.ts +103 -0
- package/dist/security/sign.js +532 -0
- package/dist/security/verification-enforcer.d.ts +41 -0
- package/dist/security/verification-enforcer.js +181 -0
- package/dist/services/McpCoreService.d.ts +102 -0
- package/dist/services/McpCoreService.js +120 -0
- package/dist/services/index.d.ts +1 -0
- package/dist/services/index.js +1 -0
- package/dist/types.d.ts +130 -0
- package/dist/types.js +3 -0
- package/dist/utils/config.d.ts +32 -0
- package/dist/utils/config.js +78 -0
- package/dist/utils/env-loader.d.ts +54 -0
- package/dist/utils/env-loader.js +270 -0
- package/dist/utils/help.d.ts +36 -0
- package/dist/utils/help.js +248 -0
- package/dist/utils/index.d.ts +7 -0
- package/dist/utils/index.js +7 -0
- package/dist/utils/logger.d.ts +35 -0
- package/dist/utils/logger.js +75 -0
- package/dist/utils/silent-monitor.d.ts +67 -0
- package/dist/utils/silent-monitor.js +242 -0
- package/dist/utils/timeout.d.ts +5 -0
- package/dist/utils/timeout.js +23 -0
- package/dist/utils/version.d.ts +4 -0
- package/dist/utils/version.js +14 -0
- package/dist/web/env-manager-server.d.ts +29 -0
- package/dist/web/env-manager-server.js +367 -0
- package/dist/web/index.d.ts +1 -0
- package/dist/web/index.js +1 -0
- package/package.json +79 -0
- package/src/LocalToolResolver.ts +424 -0
- package/src/api/enact-api.ts +569 -0
- package/src/api/index.ts +2 -0
- package/src/api/types.ts +93 -0
- package/src/core/DaggerExecutionProvider.ts +1308 -0
- package/src/core/DirectExecutionProvider.ts +484 -0
- package/src/core/EnactCore.ts +833 -0
- package/src/core/index.ts +3 -0
- package/src/exec/index.ts +3 -0
- package/src/exec/logger.ts +63 -0
- package/src/exec/validate.ts +238 -0
- package/src/index.ts +42 -0
- package/src/lib/enact-direct.ts +258 -0
- package/src/lib/index.ts +1 -0
- package/src/security/index.ts +3 -0
- package/src/security/security.ts +188 -0
- package/src/security/sign.ts +797 -0
- package/src/security/verification-enforcer.ts +268 -0
- package/src/services/McpCoreService.ts +203 -0
- package/src/services/index.ts +1 -0
- package/src/types.ts +190 -0
- package/src/utils/config.ts +97 -0
- package/src/utils/env-loader.ts +370 -0
- package/src/utils/help.ts +257 -0
- package/src/utils/index.ts +7 -0
- package/src/utils/logger.ts +83 -0
- package/src/utils/silent-monitor.ts +328 -0
- package/src/utils/timeout.ts +26 -0
- package/src/utils/version.ts +16 -0
- package/src/web/env-manager-server.ts +465 -0
- package/src/web/index.ts +1 -0
- package/src/web/static/app.js +663 -0
- package/src/web/static/index.html +117 -0
- 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();
|