@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,609 @@
|
|
|
1
|
+
import { EnactApiClient } from "../api/enact-api.js";
|
|
2
|
+
import { verifyCommandSafety, sanitizeEnvironmentVariables, } from "../security/security.js";
|
|
3
|
+
import { validateToolStructure, validateInputs, validateOutput, } from "../exec/validate.js";
|
|
4
|
+
import { DirectExecutionProvider } from "./DirectExecutionProvider.js";
|
|
5
|
+
import { DaggerExecutionProvider } from "./DaggerExecutionProvider.js";
|
|
6
|
+
import { resolveToolEnvironmentVariables } from "../utils/env-loader.js";
|
|
7
|
+
import logger from "../exec/logger.js";
|
|
8
|
+
import yaml from "yaml";
|
|
9
|
+
import { verifyTool as verifyToolSignatureWithPolicy, VERIFICATION_POLICIES, } from "../security/sign.js";
|
|
10
|
+
import { enforceSignatureVerification, createVerificationFailureResult, logSecurityAudit, } from "../security/verification-enforcer.js";
|
|
11
|
+
import fs from "fs";
|
|
12
|
+
import path from "path";
|
|
13
|
+
export class EnactCore {
|
|
14
|
+
constructor(options = {}) {
|
|
15
|
+
this.options = {
|
|
16
|
+
apiUrl: "https://enact.tools",
|
|
17
|
+
supabaseUrl: "https://xjnhhxwxovjifdxdwzih.supabase.co",
|
|
18
|
+
executionProvider: "direct",
|
|
19
|
+
defaultTimeout: "30s",
|
|
20
|
+
verificationPolicy: "permissive",
|
|
21
|
+
...options,
|
|
22
|
+
};
|
|
23
|
+
this.apiClient = new EnactApiClient(this.options.apiUrl, this.options.supabaseUrl);
|
|
24
|
+
// Initialize the appropriate execution provider
|
|
25
|
+
this.executionProvider = this.createExecutionProvider();
|
|
26
|
+
}
|
|
27
|
+
/**
|
|
28
|
+
* Set authentication token for API operations
|
|
29
|
+
*/
|
|
30
|
+
setAuthToken(token) {
|
|
31
|
+
this.options.authToken = token;
|
|
32
|
+
}
|
|
33
|
+
/**
|
|
34
|
+
* Search for tools
|
|
35
|
+
*/
|
|
36
|
+
async searchTools(options) {
|
|
37
|
+
try {
|
|
38
|
+
logger.info(`Searching for tools with query: "${options.query}"`);
|
|
39
|
+
const searchParams = {
|
|
40
|
+
query: options.query,
|
|
41
|
+
limit: options.limit,
|
|
42
|
+
tags: options.tags,
|
|
43
|
+
};
|
|
44
|
+
const results = await this.apiClient.searchTools(searchParams);
|
|
45
|
+
// Parse and validate results
|
|
46
|
+
const tools = [];
|
|
47
|
+
for (const result of results) {
|
|
48
|
+
if (result.name) {
|
|
49
|
+
try {
|
|
50
|
+
const tool = await this.getToolByName(result.name);
|
|
51
|
+
if (tool) {
|
|
52
|
+
tools.push(tool);
|
|
53
|
+
}
|
|
54
|
+
}
|
|
55
|
+
catch (error) {
|
|
56
|
+
logger.warn(`Failed to fetch tool ${result.name}:`, error);
|
|
57
|
+
}
|
|
58
|
+
}
|
|
59
|
+
}
|
|
60
|
+
logger.info(`Found ${tools.length} tools`);
|
|
61
|
+
return tools;
|
|
62
|
+
}
|
|
63
|
+
catch (error) {
|
|
64
|
+
logger.error("Error searching tools:", error);
|
|
65
|
+
// If it's a 502 error (API server issue), try fallback to local filtering
|
|
66
|
+
if (error instanceof Error && error.message.includes("502")) {
|
|
67
|
+
logger.info("Search API unavailable, trying fallback to local filtering...");
|
|
68
|
+
return this.searchToolsFallback(options);
|
|
69
|
+
}
|
|
70
|
+
throw new Error(`Search failed: ${error instanceof Error ? error.message : String(error)}`);
|
|
71
|
+
}
|
|
72
|
+
}
|
|
73
|
+
/**
|
|
74
|
+
* Fallback search method that gets all tools and filters locally
|
|
75
|
+
*/
|
|
76
|
+
async searchToolsFallback(options) {
|
|
77
|
+
try {
|
|
78
|
+
logger.info("Using fallback search method...");
|
|
79
|
+
// Get all tools (limited to avoid overwhelming the API)
|
|
80
|
+
const allTools = await this.apiClient.getTools({
|
|
81
|
+
limit: options.limit || 100,
|
|
82
|
+
});
|
|
83
|
+
// Filter tools locally based on search criteria
|
|
84
|
+
const filteredTools = [];
|
|
85
|
+
const query = options.query.toLowerCase();
|
|
86
|
+
for (const result of allTools) {
|
|
87
|
+
if (result.name) {
|
|
88
|
+
try {
|
|
89
|
+
const tool = await this.getToolByName(result.name);
|
|
90
|
+
if (tool) {
|
|
91
|
+
// Check if tool matches search criteria
|
|
92
|
+
const matchesQuery = tool.name.toLowerCase().includes(query) ||
|
|
93
|
+
(tool.description &&
|
|
94
|
+
tool.description.toLowerCase().includes(query)) ||
|
|
95
|
+
(tool.tags &&
|
|
96
|
+
tool.tags.some((tag) => tag.toLowerCase().includes(query)));
|
|
97
|
+
const matchesTags = !options.tags ||
|
|
98
|
+
!options.tags.length ||
|
|
99
|
+
(tool.tags &&
|
|
100
|
+
options.tags.some((searchTag) => tool.tags.some((toolTag) => toolTag.toLowerCase().includes(searchTag.toLowerCase()))));
|
|
101
|
+
const matchesAuthor = !options.author ||
|
|
102
|
+
(tool.authors &&
|
|
103
|
+
tool.authors.some((author) => author.name &&
|
|
104
|
+
author.name
|
|
105
|
+
.toLowerCase()
|
|
106
|
+
.includes(options.author.toLowerCase())));
|
|
107
|
+
if (matchesQuery && matchesTags && matchesAuthor) {
|
|
108
|
+
filteredTools.push(tool);
|
|
109
|
+
// Apply limit if specified
|
|
110
|
+
if (options.limit && filteredTools.length >= options.limit) {
|
|
111
|
+
break;
|
|
112
|
+
}
|
|
113
|
+
}
|
|
114
|
+
}
|
|
115
|
+
}
|
|
116
|
+
catch (error) {
|
|
117
|
+
logger.warn(`Failed to fetch tool ${result.name} in fallback search:`, error);
|
|
118
|
+
}
|
|
119
|
+
}
|
|
120
|
+
}
|
|
121
|
+
logger.info(`Fallback search found ${filteredTools.length} tools`);
|
|
122
|
+
return filteredTools;
|
|
123
|
+
}
|
|
124
|
+
catch (fallbackError) {
|
|
125
|
+
logger.error("Fallback search also failed:", fallbackError);
|
|
126
|
+
throw new Error(`Search failed (including fallback): ${fallbackError instanceof Error ? fallbackError.message : String(fallbackError)}`);
|
|
127
|
+
}
|
|
128
|
+
}
|
|
129
|
+
/**
|
|
130
|
+
* Get a specific tool by name
|
|
131
|
+
*/
|
|
132
|
+
async getToolByName(name, version) {
|
|
133
|
+
try {
|
|
134
|
+
logger.info(`Fetching tool: ${name}${version ? `@${version}` : ""}`);
|
|
135
|
+
const response = await this.apiClient.getTool(name);
|
|
136
|
+
if (!response) {
|
|
137
|
+
logger.info(`Tool not found: ${name}`);
|
|
138
|
+
return null;
|
|
139
|
+
}
|
|
140
|
+
// Parse tool from response
|
|
141
|
+
let tool;
|
|
142
|
+
if (response.content && typeof response.content === "string") {
|
|
143
|
+
tool = yaml.parse(response.content);
|
|
144
|
+
}
|
|
145
|
+
else if (response.raw_content &&
|
|
146
|
+
typeof response.raw_content === "string") {
|
|
147
|
+
try {
|
|
148
|
+
tool = JSON.parse(response.raw_content);
|
|
149
|
+
}
|
|
150
|
+
catch {
|
|
151
|
+
tool = yaml.parse(response.raw_content);
|
|
152
|
+
}
|
|
153
|
+
// Merge signature information
|
|
154
|
+
if (response.signature || response.signatures) {
|
|
155
|
+
tool.signature = response.signature;
|
|
156
|
+
tool.signatures = response.signatures;
|
|
157
|
+
}
|
|
158
|
+
}
|
|
159
|
+
else {
|
|
160
|
+
// Map database fields to tool format
|
|
161
|
+
tool = {
|
|
162
|
+
name: response.name,
|
|
163
|
+
description: response.description,
|
|
164
|
+
command: response.command,
|
|
165
|
+
timeout: response.timeout || "30s",
|
|
166
|
+
tags: response.tags || [],
|
|
167
|
+
license: response.license || response.spdx_license,
|
|
168
|
+
outputSchema: response.output_schema || response.outputSchema,
|
|
169
|
+
enact: response.protocol_version || response.enact || "1.0.0",
|
|
170
|
+
version: response.version,
|
|
171
|
+
inputSchema: response.input_schema || response.inputSchema,
|
|
172
|
+
examples: response.examples,
|
|
173
|
+
annotations: response.annotations,
|
|
174
|
+
env: response.env_vars || response.env,
|
|
175
|
+
resources: response.resources,
|
|
176
|
+
signature: response.signature,
|
|
177
|
+
signatures: response.signatures,
|
|
178
|
+
namespace: response.namespace,
|
|
179
|
+
};
|
|
180
|
+
}
|
|
181
|
+
logger.info(`Successfully fetched tool: ${tool.name}`);
|
|
182
|
+
return tool;
|
|
183
|
+
}
|
|
184
|
+
catch (error) {
|
|
185
|
+
if (error instanceof Error && error.message.includes("404")) {
|
|
186
|
+
logger.info(`Tool not found: ${name}`);
|
|
187
|
+
return null;
|
|
188
|
+
}
|
|
189
|
+
logger.error(`Error fetching tool: ${error instanceof Error ? error.message : String(error)}`);
|
|
190
|
+
throw error;
|
|
191
|
+
}
|
|
192
|
+
}
|
|
193
|
+
/**
|
|
194
|
+
* Execute a tool by name
|
|
195
|
+
*/
|
|
196
|
+
async executeToolByName(name, inputs = {}, options = {}) {
|
|
197
|
+
const executionId = this.generateExecutionId();
|
|
198
|
+
try {
|
|
199
|
+
// Fetch the tool
|
|
200
|
+
const tool = await this.getToolByName(name);
|
|
201
|
+
if (!tool) {
|
|
202
|
+
return {
|
|
203
|
+
success: false,
|
|
204
|
+
error: {
|
|
205
|
+
message: `Tool not found: ${name}`,
|
|
206
|
+
code: "NOT_FOUND",
|
|
207
|
+
},
|
|
208
|
+
metadata: {
|
|
209
|
+
executionId,
|
|
210
|
+
toolName: name,
|
|
211
|
+
executedAt: new Date().toISOString(),
|
|
212
|
+
environment: "direct",
|
|
213
|
+
},
|
|
214
|
+
};
|
|
215
|
+
}
|
|
216
|
+
// Execute the tool
|
|
217
|
+
return await this.executeTool(tool, inputs, options);
|
|
218
|
+
}
|
|
219
|
+
catch (error) {
|
|
220
|
+
return {
|
|
221
|
+
success: false,
|
|
222
|
+
error: {
|
|
223
|
+
message: error.message,
|
|
224
|
+
code: "EXECUTION_ERROR",
|
|
225
|
+
},
|
|
226
|
+
metadata: {
|
|
227
|
+
executionId,
|
|
228
|
+
toolName: name,
|
|
229
|
+
executedAt: new Date().toISOString(),
|
|
230
|
+
environment: "direct",
|
|
231
|
+
},
|
|
232
|
+
};
|
|
233
|
+
}
|
|
234
|
+
}
|
|
235
|
+
/**
|
|
236
|
+
* Execute a tool directly
|
|
237
|
+
*/
|
|
238
|
+
async executeTool(tool, inputs = {}, options = {}) {
|
|
239
|
+
const executionId = this.generateExecutionId();
|
|
240
|
+
try {
|
|
241
|
+
logger.info(`Executing tool: ${tool.name}`);
|
|
242
|
+
// Validate tool structure
|
|
243
|
+
validateToolStructure(tool);
|
|
244
|
+
// MANDATORY SIGNATURE VERIFICATION - All tools must be verified before execution
|
|
245
|
+
const verificationResult = await enforceSignatureVerification(tool, {
|
|
246
|
+
skipVerification: options.skipVerification,
|
|
247
|
+
verifyPolicy: options.verifyPolicy,
|
|
248
|
+
force: options.force,
|
|
249
|
+
allowUnsigned: false, // Never allow unsigned tools in production
|
|
250
|
+
});
|
|
251
|
+
// Log security audit information
|
|
252
|
+
logSecurityAudit(tool, verificationResult, verificationResult.allowed, {
|
|
253
|
+
skipVerification: options.skipVerification,
|
|
254
|
+
verifyPolicy: options.verifyPolicy,
|
|
255
|
+
force: options.force,
|
|
256
|
+
});
|
|
257
|
+
// Block execution if verification fails
|
|
258
|
+
if (!verificationResult.allowed) {
|
|
259
|
+
return createVerificationFailureResult(tool, verificationResult, executionId);
|
|
260
|
+
}
|
|
261
|
+
// Validate inputs
|
|
262
|
+
const validatedInputs = validateInputs(tool, inputs);
|
|
263
|
+
// Check command safety
|
|
264
|
+
const safetyCheck = verifyCommandSafety(tool.command, tool);
|
|
265
|
+
if (!safetyCheck.isSafe && !options.force) {
|
|
266
|
+
return {
|
|
267
|
+
success: false,
|
|
268
|
+
error: {
|
|
269
|
+
message: `Unsafe command blocked: ${safetyCheck.blocked?.join(", ")}`,
|
|
270
|
+
code: "COMMAND_UNSAFE",
|
|
271
|
+
details: safetyCheck,
|
|
272
|
+
},
|
|
273
|
+
metadata: {
|
|
274
|
+
executionId,
|
|
275
|
+
toolName: tool.name,
|
|
276
|
+
version: tool.version,
|
|
277
|
+
executedAt: new Date().toISOString(),
|
|
278
|
+
environment: "direct",
|
|
279
|
+
command: tool.command,
|
|
280
|
+
},
|
|
281
|
+
};
|
|
282
|
+
}
|
|
283
|
+
// Log warnings
|
|
284
|
+
if (safetyCheck.warnings.length > 0) {
|
|
285
|
+
safetyCheck.warnings.forEach((warning) => logger.warn(warning));
|
|
286
|
+
}
|
|
287
|
+
// Dry run - just validate and return
|
|
288
|
+
if (options.dryRun) {
|
|
289
|
+
return {
|
|
290
|
+
success: true,
|
|
291
|
+
output: {
|
|
292
|
+
dryRun: true,
|
|
293
|
+
tool: tool.name,
|
|
294
|
+
command: tool.command,
|
|
295
|
+
inputs: validatedInputs,
|
|
296
|
+
safetyCheck,
|
|
297
|
+
},
|
|
298
|
+
metadata: {
|
|
299
|
+
executionId,
|
|
300
|
+
toolName: tool.name,
|
|
301
|
+
version: tool.version,
|
|
302
|
+
executedAt: new Date().toISOString(),
|
|
303
|
+
environment: "direct",
|
|
304
|
+
command: tool.command,
|
|
305
|
+
},
|
|
306
|
+
};
|
|
307
|
+
}
|
|
308
|
+
// Setup execution environment
|
|
309
|
+
// Load package environment variables from .env files
|
|
310
|
+
const envResult = await resolveToolEnvironmentVariables(tool.name, tool.env);
|
|
311
|
+
// Log any missing required environment variables
|
|
312
|
+
if (envResult.missing.length > 0) {
|
|
313
|
+
logger.warn(`Missing required environment variables: ${envResult.missing.join(", ")}`);
|
|
314
|
+
}
|
|
315
|
+
const environment = {
|
|
316
|
+
vars: {
|
|
317
|
+
...envResult.resolved,
|
|
318
|
+
...sanitizeEnvironmentVariables(validatedInputs),
|
|
319
|
+
},
|
|
320
|
+
resources: tool.resources,
|
|
321
|
+
namespace: tool.namespace,
|
|
322
|
+
};
|
|
323
|
+
// Setup execution provider
|
|
324
|
+
await this.executionProvider.setup(tool);
|
|
325
|
+
// Execute the tool
|
|
326
|
+
const result = await this.executionProvider.execute(tool, validatedInputs, environment);
|
|
327
|
+
// Validate output if schema is provided
|
|
328
|
+
if (result.success && result.output && tool.outputSchema) {
|
|
329
|
+
try {
|
|
330
|
+
result.output = validateOutput(tool, result.output);
|
|
331
|
+
}
|
|
332
|
+
catch (error) {
|
|
333
|
+
logger.warn(`Output validation failed: ${error.message}`);
|
|
334
|
+
}
|
|
335
|
+
}
|
|
336
|
+
logger.info(`Tool execution completed: ${tool.name} (success: ${result.success})`);
|
|
337
|
+
return result;
|
|
338
|
+
}
|
|
339
|
+
catch (error) {
|
|
340
|
+
logger.error(`Error executing tool: ${error.message}`);
|
|
341
|
+
return {
|
|
342
|
+
success: false,
|
|
343
|
+
error: {
|
|
344
|
+
message: error.message,
|
|
345
|
+
code: "EXECUTION_ERROR",
|
|
346
|
+
details: error,
|
|
347
|
+
},
|
|
348
|
+
metadata: {
|
|
349
|
+
executionId,
|
|
350
|
+
toolName: tool.name,
|
|
351
|
+
version: tool.version,
|
|
352
|
+
executedAt: new Date().toISOString(),
|
|
353
|
+
environment: "direct",
|
|
354
|
+
command: tool.command,
|
|
355
|
+
},
|
|
356
|
+
};
|
|
357
|
+
}
|
|
358
|
+
finally {
|
|
359
|
+
// Cleanup
|
|
360
|
+
try {
|
|
361
|
+
await this.executionProvider.cleanup();
|
|
362
|
+
}
|
|
363
|
+
catch (cleanupError) {
|
|
364
|
+
logger.error("Error during cleanup:", cleanupError);
|
|
365
|
+
}
|
|
366
|
+
}
|
|
367
|
+
}
|
|
368
|
+
/**
|
|
369
|
+
* Execute a tool from raw YAML definition
|
|
370
|
+
*/
|
|
371
|
+
async executeRawTool(toolYaml, inputs = {}, options = {}) {
|
|
372
|
+
try {
|
|
373
|
+
// Parse the YAML
|
|
374
|
+
const tool = yaml.parse(toolYaml);
|
|
375
|
+
// Validate that it's a proper tool definition
|
|
376
|
+
if (!tool || typeof tool !== "object") {
|
|
377
|
+
throw new Error("Invalid tool definition: YAML must contain a tool object");
|
|
378
|
+
}
|
|
379
|
+
// Check for required fields
|
|
380
|
+
if (!tool.name || !tool.description || !tool.command) {
|
|
381
|
+
throw new Error("Invalid tool definition: missing required fields (name, description, command)");
|
|
382
|
+
}
|
|
383
|
+
// Execute the tool
|
|
384
|
+
return await this.executeTool(tool, inputs, options);
|
|
385
|
+
}
|
|
386
|
+
catch (error) {
|
|
387
|
+
const executionId = this.generateExecutionId();
|
|
388
|
+
return {
|
|
389
|
+
success: false,
|
|
390
|
+
error: {
|
|
391
|
+
message: error.message,
|
|
392
|
+
code: "PARSE_ERROR",
|
|
393
|
+
},
|
|
394
|
+
metadata: {
|
|
395
|
+
executionId,
|
|
396
|
+
toolName: "unknown",
|
|
397
|
+
executedAt: new Date().toISOString(),
|
|
398
|
+
environment: "direct",
|
|
399
|
+
},
|
|
400
|
+
};
|
|
401
|
+
}
|
|
402
|
+
}
|
|
403
|
+
/**
|
|
404
|
+
* Verify a tool's signature
|
|
405
|
+
*/
|
|
406
|
+
async verifyTool(name, policy) {
|
|
407
|
+
try {
|
|
408
|
+
const tool = await this.getToolByName(name);
|
|
409
|
+
if (!tool) {
|
|
410
|
+
return {
|
|
411
|
+
verified: false,
|
|
412
|
+
signatures: [],
|
|
413
|
+
policy: policy || "permissive",
|
|
414
|
+
errors: [`Tool not found: ${name}`],
|
|
415
|
+
};
|
|
416
|
+
}
|
|
417
|
+
// Load public key from keys/file-public.pem
|
|
418
|
+
let publicKey;
|
|
419
|
+
try {
|
|
420
|
+
const keyPath = path.resolve(__dirname, "../../keys/file-public.pem");
|
|
421
|
+
publicKey = fs.readFileSync(keyPath, "utf8");
|
|
422
|
+
}
|
|
423
|
+
catch (e) {
|
|
424
|
+
logger.warn("Could not load public key for signature verification:", e);
|
|
425
|
+
}
|
|
426
|
+
if (!publicKey) {
|
|
427
|
+
return {
|
|
428
|
+
verified: false,
|
|
429
|
+
signatures: [],
|
|
430
|
+
policy: policy || "permissive",
|
|
431
|
+
errors: ["Public key not found for signature verification"],
|
|
432
|
+
};
|
|
433
|
+
}
|
|
434
|
+
const policyKey = (policy || "permissive").toUpperCase();
|
|
435
|
+
const policyObj = VERIFICATION_POLICIES[policyKey];
|
|
436
|
+
const verificationResult = await verifyToolSignatureWithPolicy(tool, policyObj);
|
|
437
|
+
if (!verificationResult.isValid) {
|
|
438
|
+
return {
|
|
439
|
+
verified: false,
|
|
440
|
+
signatures: [],
|
|
441
|
+
policy: policy || "permissive",
|
|
442
|
+
errors: verificationResult.errors,
|
|
443
|
+
};
|
|
444
|
+
}
|
|
445
|
+
const signatures = [];
|
|
446
|
+
if (tool.signature) {
|
|
447
|
+
signatures.push(tool.signature);
|
|
448
|
+
}
|
|
449
|
+
if (tool.signatures) {
|
|
450
|
+
signatures.push(...Object.values(tool.signatures));
|
|
451
|
+
}
|
|
452
|
+
return {
|
|
453
|
+
verified: verificationResult.isValid,
|
|
454
|
+
signatures,
|
|
455
|
+
policy: policy || "permissive",
|
|
456
|
+
};
|
|
457
|
+
}
|
|
458
|
+
catch (error) {
|
|
459
|
+
return {
|
|
460
|
+
verified: false,
|
|
461
|
+
signatures: [],
|
|
462
|
+
policy: policy || "permissive",
|
|
463
|
+
errors: [`Verification error: ${error.message}`],
|
|
464
|
+
};
|
|
465
|
+
}
|
|
466
|
+
}
|
|
467
|
+
/**
|
|
468
|
+
* Check if a tool exists
|
|
469
|
+
*/
|
|
470
|
+
async toolExists(name) {
|
|
471
|
+
try {
|
|
472
|
+
const tool = await this.getToolByName(name);
|
|
473
|
+
return tool !== null;
|
|
474
|
+
}
|
|
475
|
+
catch (error) {
|
|
476
|
+
return false;
|
|
477
|
+
}
|
|
478
|
+
}
|
|
479
|
+
/**
|
|
480
|
+
* Get tools by tags
|
|
481
|
+
*/
|
|
482
|
+
async getToolsByTags(tags, limit = 20) {
|
|
483
|
+
return this.searchTools({
|
|
484
|
+
query: tags.join(" "),
|
|
485
|
+
tags,
|
|
486
|
+
limit,
|
|
487
|
+
});
|
|
488
|
+
}
|
|
489
|
+
/**
|
|
490
|
+
* Get tools by author
|
|
491
|
+
*/
|
|
492
|
+
async getToolsByAuthor(author, limit = 20) {
|
|
493
|
+
return this.searchTools({
|
|
494
|
+
query: `author:${author}`,
|
|
495
|
+
author,
|
|
496
|
+
limit,
|
|
497
|
+
});
|
|
498
|
+
}
|
|
499
|
+
/**
|
|
500
|
+
* Get all tools with filters
|
|
501
|
+
*/
|
|
502
|
+
async getTools(options = {}) {
|
|
503
|
+
try {
|
|
504
|
+
const apiResults = await this.apiClient.getTools(options);
|
|
505
|
+
// Parse and validate results
|
|
506
|
+
const tools = [];
|
|
507
|
+
for (const result of apiResults) {
|
|
508
|
+
if (result.name) {
|
|
509
|
+
try {
|
|
510
|
+
const tool = await this.getToolByName(result.name);
|
|
511
|
+
if (tool) {
|
|
512
|
+
tools.push(tool);
|
|
513
|
+
}
|
|
514
|
+
}
|
|
515
|
+
catch (error) {
|
|
516
|
+
logger.warn(`Failed to fetch tool ${result.name}:`, error);
|
|
517
|
+
}
|
|
518
|
+
}
|
|
519
|
+
}
|
|
520
|
+
return tools;
|
|
521
|
+
}
|
|
522
|
+
catch (error) {
|
|
523
|
+
logger.error("Error getting tools:", error);
|
|
524
|
+
throw new Error(`Failed to get tools: ${error instanceof Error ? error.message : String(error)}`);
|
|
525
|
+
}
|
|
526
|
+
}
|
|
527
|
+
/**
|
|
528
|
+
* Get authentication status (placeholder - would need actual auth implementation)
|
|
529
|
+
*/
|
|
530
|
+
async getAuthStatus() {
|
|
531
|
+
// This would need to check actual auth state
|
|
532
|
+
// For now, return based on whether we have a token
|
|
533
|
+
return {
|
|
534
|
+
authenticated: !!this.options.authToken,
|
|
535
|
+
server: this.options.apiUrl,
|
|
536
|
+
};
|
|
537
|
+
}
|
|
538
|
+
/**
|
|
539
|
+
* Publish a tool (requires authentication)
|
|
540
|
+
*/
|
|
541
|
+
async publishTool(tool) {
|
|
542
|
+
if (!this.options.authToken) {
|
|
543
|
+
return {
|
|
544
|
+
success: false,
|
|
545
|
+
message: "Authentication required to publish tools",
|
|
546
|
+
};
|
|
547
|
+
}
|
|
548
|
+
try {
|
|
549
|
+
validateToolStructure(tool);
|
|
550
|
+
await this.apiClient.publishTool(tool, this.options.authToken);
|
|
551
|
+
return {
|
|
552
|
+
success: true,
|
|
553
|
+
message: `Successfully published tool: ${tool.name}`,
|
|
554
|
+
};
|
|
555
|
+
}
|
|
556
|
+
catch (error) {
|
|
557
|
+
return {
|
|
558
|
+
success: false,
|
|
559
|
+
message: `Failed to publish tool: ${error instanceof Error ? error.message : String(error)}`,
|
|
560
|
+
};
|
|
561
|
+
}
|
|
562
|
+
}
|
|
563
|
+
/**
|
|
564
|
+
* Get tool information (alias for getToolByName for consistency)
|
|
565
|
+
*/
|
|
566
|
+
async getToolInfo(name, version) {
|
|
567
|
+
return this.getToolByName(name, version);
|
|
568
|
+
}
|
|
569
|
+
/**
|
|
570
|
+
* Get core library status
|
|
571
|
+
*/
|
|
572
|
+
async getStatus() {
|
|
573
|
+
const authStatus = await this.getAuthStatus();
|
|
574
|
+
return {
|
|
575
|
+
executionProvider: this.options.executionProvider || "direct",
|
|
576
|
+
apiUrl: this.options.apiUrl || "https://enact.tools",
|
|
577
|
+
verificationPolicy: this.options.verificationPolicy || "permissive",
|
|
578
|
+
defaultTimeout: this.options.defaultTimeout || "30s",
|
|
579
|
+
authenticated: authStatus.authenticated,
|
|
580
|
+
};
|
|
581
|
+
}
|
|
582
|
+
/**
|
|
583
|
+
* Create the appropriate execution provider based on options
|
|
584
|
+
*/
|
|
585
|
+
createExecutionProvider() {
|
|
586
|
+
switch (this.options.executionProvider) {
|
|
587
|
+
case "dagger":
|
|
588
|
+
return new DaggerExecutionProvider(this.options.daggerOptions);
|
|
589
|
+
case "direct":
|
|
590
|
+
default:
|
|
591
|
+
return new DirectExecutionProvider();
|
|
592
|
+
}
|
|
593
|
+
}
|
|
594
|
+
/**
|
|
595
|
+
* Switch execution provider at runtime
|
|
596
|
+
*/
|
|
597
|
+
switchExecutionProvider(provider, options) {
|
|
598
|
+
this.options.executionProvider = provider;
|
|
599
|
+
this.executionProvider = this.createExecutionProvider();
|
|
600
|
+
}
|
|
601
|
+
/**
|
|
602
|
+
* Generate execution ID
|
|
603
|
+
*/
|
|
604
|
+
generateExecutionId() {
|
|
605
|
+
return `exec_${Date.now()}_${Math.random().toString(36).substring(2, 9)}`;
|
|
606
|
+
}
|
|
607
|
+
}
|
|
608
|
+
// Export a default instance
|
|
609
|
+
export const enactCore = new EnactCore();
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
declare const logger: import("pino").Logger<never, boolean>;
|
|
2
|
+
declare const wrappedLogger: {
|
|
3
|
+
info: (msg: string, ...args: any[]) => void;
|
|
4
|
+
warn: (msg: string, ...args: any[]) => void;
|
|
5
|
+
error: (msg: string, ...args: any[]) => void;
|
|
6
|
+
debug: (msg: string, ...args: any[]) => void;
|
|
7
|
+
clientLoggingEnabled: () => boolean;
|
|
8
|
+
isLevelEnabled: (level: string) => boolean;
|
|
9
|
+
pino: import("pino").Logger<never, boolean>;
|
|
10
|
+
};
|
|
11
|
+
export default wrappedLogger;
|
|
@@ -0,0 +1,57 @@
|
|
|
1
|
+
import pino from "pino";
|
|
2
|
+
// Determine if the environment is silent (e.g., CI, testing, or specific env var)
|
|
3
|
+
const isSilentMode = () => process.env.CI === "true" ||
|
|
4
|
+
process.env.NODE_ENV === "test" ||
|
|
5
|
+
process.env.ENACT_SILENT === "true" ||
|
|
6
|
+
process.env.ENACT_SKIP_INTERACTIVE === "true";
|
|
7
|
+
// Base logger configuration
|
|
8
|
+
const logger = pino({
|
|
9
|
+
level: process.env.LOG_LEVEL || "info",
|
|
10
|
+
// In tests, we don't want the pretty transport, as it adds noise.
|
|
11
|
+
// The output is captured anyway.
|
|
12
|
+
...(process.env.NODE_ENV !== "test" && {
|
|
13
|
+
transport: {
|
|
14
|
+
target: "pino-pretty",
|
|
15
|
+
options: {
|
|
16
|
+
colorize: true,
|
|
17
|
+
ignore: "pid,hostname",
|
|
18
|
+
translateTime: "SYS:standard",
|
|
19
|
+
},
|
|
20
|
+
},
|
|
21
|
+
}),
|
|
22
|
+
});
|
|
23
|
+
// Wrapper to dynamically check silent mode on each call
|
|
24
|
+
const wrappedLogger = {
|
|
25
|
+
info: (...args) => {
|
|
26
|
+
if (!isSilentMode()) {
|
|
27
|
+
logger.info(...args);
|
|
28
|
+
}
|
|
29
|
+
},
|
|
30
|
+
warn: (...args) => {
|
|
31
|
+
if (!isSilentMode()) {
|
|
32
|
+
logger.warn(...args);
|
|
33
|
+
}
|
|
34
|
+
},
|
|
35
|
+
error: (...args) => {
|
|
36
|
+
// The silent tests expect errors to be silent too.
|
|
37
|
+
if (!isSilentMode()) {
|
|
38
|
+
logger.error(...args);
|
|
39
|
+
}
|
|
40
|
+
},
|
|
41
|
+
debug: (...args) => {
|
|
42
|
+
if (!isSilentMode() && (process.env.DEBUG || process.env.VERBOSE)) {
|
|
43
|
+
logger.debug(...args);
|
|
44
|
+
}
|
|
45
|
+
},
|
|
46
|
+
// Expose a way to check if client logging is enabled (for MCP)
|
|
47
|
+
clientLoggingEnabled: () => !process.env.ENACT_MCP_CLIENT,
|
|
48
|
+
isLevelEnabled: (level) => {
|
|
49
|
+
if (isSilentMode()) {
|
|
50
|
+
return false;
|
|
51
|
+
}
|
|
52
|
+
return logger.isLevelEnabled(level);
|
|
53
|
+
},
|
|
54
|
+
// Keep original pino instance available if needed
|
|
55
|
+
pino: logger,
|
|
56
|
+
};
|
|
57
|
+
export default wrappedLogger;
|