@enactprotocol/shared 1.2.3 → 1.2.4
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/api/enact-api.d.ts +6 -1
- package/dist/api/enact-api.js +19 -4
- package/dist/constants.d.ts +7 -0
- package/dist/constants.js +10 -0
- package/dist/core/DaggerExecutionProvider.d.ts +0 -4
- package/dist/core/DaggerExecutionProvider.js +0 -37
- package/dist/core/EnactCore.d.ts +4 -0
- package/dist/core/EnactCore.js +15 -2
- package/dist/lib/enact-direct.d.ts +9 -0
- package/dist/lib/enact-direct.js +16 -4
- package/dist/services/McpCoreService.d.ts +8 -0
- package/dist/services/McpCoreService.js +13 -0
- package/dist/utils/config.d.ts +79 -0
- package/dist/utils/config.js +267 -3
- package/package.json +1 -1
- package/src/api/enact-api.ts +25 -5
- package/src/core/DaggerExecutionProvider.ts +5 -45
- package/src/core/EnactCore.ts +19 -4
- package/src/index.ts +2 -0
- package/src/lib/enact-direct.ts +23 -6
- package/src/services/McpCoreService.ts +20 -2
- package/src/utils/config.ts +344 -3
package/dist/api/enact-api.d.ts
CHANGED
|
@@ -2,7 +2,11 @@ import { EnactToolDefinition, ToolUsage, ToolSearchQuery, CLITokenCreate, OAuthT
|
|
|
2
2
|
export declare class EnactApiClient {
|
|
3
3
|
baseUrl: string;
|
|
4
4
|
supabaseUrl: string;
|
|
5
|
-
constructor(baseUrl
|
|
5
|
+
constructor(baseUrl: string, supabaseUrl: string);
|
|
6
|
+
/**
|
|
7
|
+
* Create API client with config-based URLs
|
|
8
|
+
*/
|
|
9
|
+
static create(baseUrl?: string, supabaseUrl?: string): Promise<EnactApiClient>;
|
|
6
10
|
private makeRequest;
|
|
7
11
|
/**
|
|
8
12
|
* Get all tools (public, no auth required)
|
|
@@ -115,6 +119,7 @@ export declare class EnactApiClient {
|
|
|
115
119
|
errors: string[];
|
|
116
120
|
};
|
|
117
121
|
}
|
|
122
|
+
export declare function createDefaultApiClient(): Promise<EnactApiClient>;
|
|
118
123
|
export declare const enactApi: EnactApiClient;
|
|
119
124
|
export declare class EnactApiError extends Error {
|
|
120
125
|
statusCode?: number | undefined;
|
package/dist/api/enact-api.js
CHANGED
|
@@ -1,8 +1,17 @@
|
|
|
1
|
+
import { getFrontendUrl, getApiUrl } from "../utils/config";
|
|
1
2
|
export class EnactApiClient {
|
|
2
|
-
constructor(baseUrl
|
|
3
|
+
constructor(baseUrl, supabaseUrl) {
|
|
3
4
|
this.baseUrl = baseUrl.replace(/\/$/, ""); // Remove trailing slash
|
|
4
5
|
this.supabaseUrl = supabaseUrl.replace(/\/$/, "");
|
|
5
6
|
}
|
|
7
|
+
/**
|
|
8
|
+
* Create API client with config-based URLs
|
|
9
|
+
*/
|
|
10
|
+
static async create(baseUrl, supabaseUrl) {
|
|
11
|
+
const frontendUrl = baseUrl || await getFrontendUrl();
|
|
12
|
+
const apiUrl = supabaseUrl || await getApiUrl();
|
|
13
|
+
return new EnactApiClient(frontendUrl, apiUrl);
|
|
14
|
+
}
|
|
6
15
|
// Helper method to make authenticated requests
|
|
7
16
|
async makeRequest(endpoint, options = {}, token, tokenType = "jwt") {
|
|
8
17
|
const url = endpoint.startsWith("http")
|
|
@@ -389,8 +398,12 @@ export class EnactApiClient {
|
|
|
389
398
|
};
|
|
390
399
|
}
|
|
391
400
|
}
|
|
392
|
-
// Export a default instance
|
|
393
|
-
export
|
|
401
|
+
// Export a default instance factory
|
|
402
|
+
export async function createDefaultApiClient() {
|
|
403
|
+
return await EnactApiClient.create();
|
|
404
|
+
}
|
|
405
|
+
// Keep backward compatibility with sync usage
|
|
406
|
+
export const enactApi = new EnactApiClient("https://enact.tools", "https://xjnhhxwxovjifdxdwzih.supabase.co");
|
|
394
407
|
// Export error types for better error handling
|
|
395
408
|
export class EnactApiError extends Error {
|
|
396
409
|
constructor(message, statusCode, endpoint) {
|
|
@@ -402,5 +415,7 @@ export class EnactApiError extends Error {
|
|
|
402
415
|
}
|
|
403
416
|
// Helper function to create API client with custom configuration
|
|
404
417
|
export function createEnactApiClient(baseUrl, supabaseUrl) {
|
|
405
|
-
|
|
418
|
+
const defaultFrontend = "https://enact.tools";
|
|
419
|
+
const defaultApi = "https://xjnhhxwxovjifdxdwzih.supabase.co";
|
|
420
|
+
return new EnactApiClient(baseUrl || defaultFrontend, supabaseUrl || defaultApi);
|
|
406
421
|
}
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Shared constants for Enact CLI
|
|
3
|
+
*/
|
|
4
|
+
export declare const DEFAULT_FRONTEND_URL = "https://enact.tools";
|
|
5
|
+
export declare const DEFAULT_API_URL = "https://xjnhhxwxovjifdxdwzih.supabase.co";
|
|
6
|
+
export declare const ENV_FRONTEND_URL = "ENACT_FRONTEND_URL";
|
|
7
|
+
export declare const ENV_API_URL = "ENACT_API_URL";
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Shared constants for Enact CLI
|
|
3
|
+
*/
|
|
4
|
+
// Frontend URL - used for OAuth redirects, registry browsing, documentation links
|
|
5
|
+
export const DEFAULT_FRONTEND_URL = "https://enact.tools";
|
|
6
|
+
// Backend API URL - used for all API calls (search, publish, etc.)
|
|
7
|
+
export const DEFAULT_API_URL = "https://xjnhhxwxovjifdxdwzih.supabase.co";
|
|
8
|
+
// Environment variable names for overriding defaults
|
|
9
|
+
export const ENV_FRONTEND_URL = "ENACT_FRONTEND_URL";
|
|
10
|
+
export const ENV_API_URL = "ENACT_API_URL";
|
|
@@ -145,10 +145,6 @@ export declare class DaggerExecutionProvider extends ExecutionProvider {
|
|
|
145
145
|
* Graceful shutdown with proper async cleanup
|
|
146
146
|
*/
|
|
147
147
|
private gracefulShutdown;
|
|
148
|
-
/**
|
|
149
|
-
* Enhanced force cleanup for synchronous exit handlers
|
|
150
|
-
*/
|
|
151
|
-
private forceCleanup;
|
|
152
148
|
/**
|
|
153
149
|
* Get current engine status for debugging
|
|
154
150
|
*/
|
|
@@ -935,43 +935,6 @@ export class DaggerExecutionProvider extends ExecutionProvider {
|
|
|
935
935
|
process.exit(1);
|
|
936
936
|
}
|
|
937
937
|
}
|
|
938
|
-
/**
|
|
939
|
-
* Enhanced force cleanup for synchronous exit handlers
|
|
940
|
-
*/
|
|
941
|
-
forceCleanup() {
|
|
942
|
-
if (this.isShuttingDown)
|
|
943
|
-
return;
|
|
944
|
-
try {
|
|
945
|
-
logger.info("🔄 Force cleaning up Dagger engines...");
|
|
946
|
-
const result = spawnSync("docker", [
|
|
947
|
-
"ps",
|
|
948
|
-
"--all",
|
|
949
|
-
"--filter",
|
|
950
|
-
"name=dagger-engine",
|
|
951
|
-
"--format",
|
|
952
|
-
"{{.Names}}",
|
|
953
|
-
], {
|
|
954
|
-
encoding: "utf8",
|
|
955
|
-
timeout: 5000,
|
|
956
|
-
});
|
|
957
|
-
if (result.stdout) {
|
|
958
|
-
const names = result.stdout
|
|
959
|
-
.trim()
|
|
960
|
-
.split("\n")
|
|
961
|
-
.filter((n) => n.trim());
|
|
962
|
-
if (names.length > 0) {
|
|
963
|
-
logger.info(`Found ${names.length} engine containers, force removing...`);
|
|
964
|
-
for (const name of names) {
|
|
965
|
-
spawnSync("docker", ["rm", "-f", name.trim()], { timeout: 3000 });
|
|
966
|
-
}
|
|
967
|
-
logger.info("✅ Force cleanup completed");
|
|
968
|
-
}
|
|
969
|
-
}
|
|
970
|
-
}
|
|
971
|
-
catch (error) {
|
|
972
|
-
logger.debug("Force cleanup failed (this is usually fine):", error);
|
|
973
|
-
}
|
|
974
|
-
}
|
|
975
938
|
/**
|
|
976
939
|
* Get current engine status for debugging
|
|
977
940
|
*/
|
package/dist/core/EnactCore.d.ts
CHANGED
|
@@ -34,6 +34,10 @@ export declare class EnactCore {
|
|
|
34
34
|
private executionProvider;
|
|
35
35
|
private options;
|
|
36
36
|
constructor(options?: EnactCoreOptions);
|
|
37
|
+
/**
|
|
38
|
+
* Create EnactCore with config-based URLs
|
|
39
|
+
*/
|
|
40
|
+
static create(options?: EnactCoreOptions): Promise<EnactCore>;
|
|
37
41
|
/**
|
|
38
42
|
* Set authentication token for API operations
|
|
39
43
|
*/
|
package/dist/core/EnactCore.js
CHANGED
|
@@ -6,11 +6,12 @@ import { resolveToolEnvironmentVariables } from "../utils/env-loader.js";
|
|
|
6
6
|
import logger from "../exec/logger.js";
|
|
7
7
|
import yaml from "yaml";
|
|
8
8
|
import { CryptoUtils, SecurityConfigManager, SigningService } from "@enactprotocol/security";
|
|
9
|
+
import { getFrontendUrl, getApiUrl } from "../utils/config";
|
|
9
10
|
export class EnactCore {
|
|
10
11
|
constructor(options = {}) {
|
|
11
12
|
this.options = {
|
|
12
|
-
apiUrl: "https://enact.tools",
|
|
13
|
-
supabaseUrl: "https://xjnhhxwxovjifdxdwzih.supabase.co",
|
|
13
|
+
apiUrl: "https://enact.tools", // Default, will be overridden by factory
|
|
14
|
+
supabaseUrl: "https://xjnhhxwxovjifdxdwzih.supabase.co", // Default, will be overridden by factory
|
|
14
15
|
executionProvider: "dagger",
|
|
15
16
|
defaultTimeout: "30s",
|
|
16
17
|
...options,
|
|
@@ -19,6 +20,18 @@ export class EnactCore {
|
|
|
19
20
|
// Initialize the appropriate execution provider
|
|
20
21
|
this.executionProvider = this.createExecutionProvider();
|
|
21
22
|
}
|
|
23
|
+
/**
|
|
24
|
+
* Create EnactCore with config-based URLs
|
|
25
|
+
*/
|
|
26
|
+
static async create(options = {}) {
|
|
27
|
+
const frontendUrl = options.apiUrl || await getFrontendUrl();
|
|
28
|
+
const apiUrl = options.supabaseUrl || await getApiUrl();
|
|
29
|
+
return new EnactCore({
|
|
30
|
+
...options,
|
|
31
|
+
apiUrl: frontendUrl,
|
|
32
|
+
supabaseUrl: apiUrl,
|
|
33
|
+
});
|
|
34
|
+
}
|
|
22
35
|
/**
|
|
23
36
|
* Set authentication token for API operations
|
|
24
37
|
*/
|
|
@@ -14,6 +14,15 @@ export declare class EnactDirect {
|
|
|
14
14
|
authToken?: string;
|
|
15
15
|
defaultTimeout?: string;
|
|
16
16
|
});
|
|
17
|
+
/**
|
|
18
|
+
* Create EnactDirect with config-based URLs
|
|
19
|
+
*/
|
|
20
|
+
static create(options?: {
|
|
21
|
+
apiUrl?: string;
|
|
22
|
+
supabaseUrl?: string;
|
|
23
|
+
authToken?: string;
|
|
24
|
+
defaultTimeout?: string;
|
|
25
|
+
}): Promise<EnactDirect>;
|
|
17
26
|
/**
|
|
18
27
|
* Execute a tool by name with inputs
|
|
19
28
|
*
|
package/dist/lib/enact-direct.js
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
// src/lib/enact-direct.ts - Library interface for direct usage by MCP servers
|
|
2
2
|
import { EnactCore, } from "../core/EnactCore";
|
|
3
|
+
import { getFrontendUrl, getApiUrl } from "../utils/config";
|
|
3
4
|
/**
|
|
4
5
|
* Direct Enact Library Interface
|
|
5
6
|
*
|
|
@@ -8,16 +9,27 @@ import { EnactCore, } from "../core/EnactCore";
|
|
|
8
9
|
*/
|
|
9
10
|
export class EnactDirect {
|
|
10
11
|
constructor(options = {}) {
|
|
12
|
+
// We need to handle async config loading in a factory method
|
|
11
13
|
this.core = new EnactCore({
|
|
12
|
-
apiUrl: options.apiUrl || process.env.
|
|
13
|
-
supabaseUrl: options.supabaseUrl ||
|
|
14
|
-
process.env.ENACT_SUPABASE_URL ||
|
|
15
|
-
"https://xjnhhxwxovjifdxdwzih.supabase.co",
|
|
14
|
+
apiUrl: options.apiUrl || process.env.ENACT_FRONTEND_URL || "https://enact.tools",
|
|
15
|
+
supabaseUrl: options.supabaseUrl || process.env.ENACT_API_URL || "https://xjnhhxwxovjifdxdwzih.supabase.co",
|
|
16
16
|
executionProvider: "direct",
|
|
17
17
|
authToken: options.authToken || process.env.ENACT_AUTH_TOKEN,
|
|
18
18
|
defaultTimeout: options.defaultTimeout || "30s",
|
|
19
19
|
});
|
|
20
20
|
}
|
|
21
|
+
/**
|
|
22
|
+
* Create EnactDirect with config-based URLs
|
|
23
|
+
*/
|
|
24
|
+
static async create(options = {}) {
|
|
25
|
+
const frontendUrl = options.apiUrl || process.env.ENACT_FRONTEND_URL || await getFrontendUrl();
|
|
26
|
+
const apiUrl = options.supabaseUrl || process.env.ENACT_API_URL || await getApiUrl();
|
|
27
|
+
return new EnactDirect({
|
|
28
|
+
...options,
|
|
29
|
+
apiUrl: frontendUrl,
|
|
30
|
+
supabaseUrl: apiUrl,
|
|
31
|
+
});
|
|
32
|
+
}
|
|
21
33
|
/**
|
|
22
34
|
* Execute a tool by name with inputs
|
|
23
35
|
*
|
|
@@ -6,6 +6,14 @@ export declare class McpCoreService {
|
|
|
6
6
|
supabaseUrl?: string;
|
|
7
7
|
authToken?: string;
|
|
8
8
|
});
|
|
9
|
+
/**
|
|
10
|
+
* Create McpCoreService with config-based URLs
|
|
11
|
+
*/
|
|
12
|
+
static create(options?: {
|
|
13
|
+
apiUrl?: string;
|
|
14
|
+
supabaseUrl?: string;
|
|
15
|
+
authToken?: string;
|
|
16
|
+
}): Promise<McpCoreService>;
|
|
9
17
|
/**
|
|
10
18
|
* Set authentication token
|
|
11
19
|
*/
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
// src/services/McpCoreService.ts - Direct core integration for MCP server
|
|
2
2
|
import { EnactCore } from "../core/EnactCore";
|
|
3
|
+
import { getFrontendUrl, getApiUrl } from "../utils/config";
|
|
3
4
|
export class McpCoreService {
|
|
4
5
|
constructor(options) {
|
|
5
6
|
this.core = new EnactCore({
|
|
@@ -8,6 +9,18 @@ export class McpCoreService {
|
|
|
8
9
|
authToken: options?.authToken,
|
|
9
10
|
});
|
|
10
11
|
}
|
|
12
|
+
/**
|
|
13
|
+
* Create McpCoreService with config-based URLs
|
|
14
|
+
*/
|
|
15
|
+
static async create(options) {
|
|
16
|
+
const frontendUrl = options?.apiUrl || await getFrontendUrl();
|
|
17
|
+
const apiUrl = options?.supabaseUrl || await getApiUrl();
|
|
18
|
+
return new McpCoreService({
|
|
19
|
+
...options,
|
|
20
|
+
apiUrl: frontendUrl,
|
|
21
|
+
supabaseUrl: apiUrl,
|
|
22
|
+
});
|
|
23
|
+
}
|
|
11
24
|
/**
|
|
12
25
|
* Set authentication token
|
|
13
26
|
*/
|
package/dist/utils/config.d.ts
CHANGED
|
@@ -1,6 +1,10 @@
|
|
|
1
1
|
export interface EnactConfig {
|
|
2
2
|
defaultUrl?: string;
|
|
3
3
|
history?: string[];
|
|
4
|
+
urls?: {
|
|
5
|
+
frontend?: string;
|
|
6
|
+
api?: string;
|
|
7
|
+
};
|
|
4
8
|
}
|
|
5
9
|
/**
|
|
6
10
|
* Ensure config directory and file exist
|
|
@@ -30,3 +34,78 @@ export declare function setDefaultUrl(url: string): Promise<void>;
|
|
|
30
34
|
* Get the default publish URL
|
|
31
35
|
*/
|
|
32
36
|
export declare function getDefaultUrl(): Promise<string | undefined>;
|
|
37
|
+
export interface TrustedKeyMeta {
|
|
38
|
+
name: string;
|
|
39
|
+
description?: string;
|
|
40
|
+
addedAt: string;
|
|
41
|
+
source: "default" | "user" | "organization";
|
|
42
|
+
keyFile: string;
|
|
43
|
+
}
|
|
44
|
+
export interface TrustedKey {
|
|
45
|
+
id: string;
|
|
46
|
+
name: string;
|
|
47
|
+
publicKey: string;
|
|
48
|
+
description?: string;
|
|
49
|
+
addedAt: string;
|
|
50
|
+
source: "default" | "user" | "organization";
|
|
51
|
+
keyFile: string;
|
|
52
|
+
}
|
|
53
|
+
/**
|
|
54
|
+
* Get the frontend URL with fallbacks
|
|
55
|
+
*/
|
|
56
|
+
export declare function getFrontendUrl(): Promise<string>;
|
|
57
|
+
/**
|
|
58
|
+
* Get the API URL with fallbacks
|
|
59
|
+
*/
|
|
60
|
+
export declare function getApiUrl(): Promise<string>;
|
|
61
|
+
/**
|
|
62
|
+
* Set the frontend URL in config
|
|
63
|
+
*/
|
|
64
|
+
export declare function setFrontendUrl(url: string): Promise<void>;
|
|
65
|
+
/**
|
|
66
|
+
* Set the API URL in config
|
|
67
|
+
*/
|
|
68
|
+
export declare function setApiUrl(url: string): Promise<void>;
|
|
69
|
+
/**
|
|
70
|
+
* Reset URLs to defaults
|
|
71
|
+
*/
|
|
72
|
+
export declare function resetUrls(): Promise<void>;
|
|
73
|
+
/**
|
|
74
|
+
* Get current URL configuration
|
|
75
|
+
*/
|
|
76
|
+
export declare function getUrlConfig(): Promise<{
|
|
77
|
+
frontend: {
|
|
78
|
+
value: string;
|
|
79
|
+
source: string;
|
|
80
|
+
};
|
|
81
|
+
api: {
|
|
82
|
+
value: string;
|
|
83
|
+
source: string;
|
|
84
|
+
};
|
|
85
|
+
}>;
|
|
86
|
+
/**
|
|
87
|
+
* Read all trusted keys from directory
|
|
88
|
+
*/
|
|
89
|
+
export declare function getTrustedKeys(): Promise<TrustedKey[]>;
|
|
90
|
+
/**
|
|
91
|
+
* Add a trusted key
|
|
92
|
+
*/
|
|
93
|
+
export declare function addTrustedKey(keyData: {
|
|
94
|
+
id: string;
|
|
95
|
+
name: string;
|
|
96
|
+
publicKey: string;
|
|
97
|
+
description?: string;
|
|
98
|
+
source?: "user" | "organization";
|
|
99
|
+
}): Promise<void>;
|
|
100
|
+
/**
|
|
101
|
+
* Remove a trusted key
|
|
102
|
+
*/
|
|
103
|
+
export declare function removeTrustedKey(keyId: string): Promise<void>;
|
|
104
|
+
/**
|
|
105
|
+
* Get a specific trusted key
|
|
106
|
+
*/
|
|
107
|
+
export declare function getTrustedKey(keyId: string): Promise<TrustedKey | null>;
|
|
108
|
+
/**
|
|
109
|
+
* Check if a public key is trusted
|
|
110
|
+
*/
|
|
111
|
+
export declare function isKeyTrusted(publicKey: string): Promise<boolean>;
|
package/dist/utils/config.js
CHANGED
|
@@ -6,6 +6,7 @@ import { mkdir, readFile, writeFile } from "fs/promises";
|
|
|
6
6
|
// Define config paths
|
|
7
7
|
const CONFIG_DIR = join(homedir(), ".enact");
|
|
8
8
|
const CONFIG_FILE = join(CONFIG_DIR, "config.json");
|
|
9
|
+
const TRUSTED_KEYS_DIR = join(CONFIG_DIR, "trusted-keys");
|
|
9
10
|
/**
|
|
10
11
|
* Ensure config directory and file exist
|
|
11
12
|
*/
|
|
@@ -14,7 +15,14 @@ export async function ensureConfig() {
|
|
|
14
15
|
await mkdir(CONFIG_DIR, { recursive: true });
|
|
15
16
|
}
|
|
16
17
|
if (!existsSync(CONFIG_FILE)) {
|
|
17
|
-
|
|
18
|
+
const defaultConfig = {
|
|
19
|
+
history: [],
|
|
20
|
+
urls: {
|
|
21
|
+
frontend: DEFAULT_FRONTEND_URL,
|
|
22
|
+
api: DEFAULT_API_URL,
|
|
23
|
+
},
|
|
24
|
+
};
|
|
25
|
+
await writeFile(CONFIG_FILE, JSON.stringify(defaultConfig, null, 2));
|
|
18
26
|
}
|
|
19
27
|
}
|
|
20
28
|
/**
|
|
@@ -24,11 +32,26 @@ export async function readConfig() {
|
|
|
24
32
|
await ensureConfig();
|
|
25
33
|
try {
|
|
26
34
|
const data = await readFile(CONFIG_FILE, "utf8");
|
|
27
|
-
|
|
35
|
+
const config = JSON.parse(data);
|
|
36
|
+
// Migrate old configs that don't have URLs section
|
|
37
|
+
if (!config.urls) {
|
|
38
|
+
config.urls = {
|
|
39
|
+
frontend: DEFAULT_FRONTEND_URL,
|
|
40
|
+
api: DEFAULT_API_URL,
|
|
41
|
+
};
|
|
42
|
+
await writeConfig(config);
|
|
43
|
+
}
|
|
44
|
+
return config;
|
|
28
45
|
}
|
|
29
46
|
catch (error) {
|
|
30
47
|
console.error("Failed to read config:", error.message);
|
|
31
|
-
return {
|
|
48
|
+
return {
|
|
49
|
+
history: [],
|
|
50
|
+
urls: {
|
|
51
|
+
frontend: DEFAULT_FRONTEND_URL,
|
|
52
|
+
api: DEFAULT_API_URL,
|
|
53
|
+
},
|
|
54
|
+
};
|
|
32
55
|
}
|
|
33
56
|
}
|
|
34
57
|
/**
|
|
@@ -76,3 +99,244 @@ export async function getDefaultUrl() {
|
|
|
76
99
|
const config = await readConfig();
|
|
77
100
|
return config.defaultUrl;
|
|
78
101
|
}
|
|
102
|
+
// Default URLs
|
|
103
|
+
const DEFAULT_FRONTEND_URL = "https://enact.tools";
|
|
104
|
+
const DEFAULT_API_URL = "https://xjnhhxwxovjifdxdwzih.supabase.co";
|
|
105
|
+
// Default trusted public key (Enact Protocol official key)
|
|
106
|
+
const DEFAULT_ENACT_PUBLIC_KEY = `-----BEGIN PUBLIC KEY-----
|
|
107
|
+
MFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAE8VyE3jGm5yT2mKnPx1dQF7q8Z2Kv
|
|
108
|
+
7mX9YnE2mK8vF3tY9pL6xH2dF8sK3mN7wQ5vT2gR8sL4xN6pM9uE3wF2Qw==
|
|
109
|
+
-----END PUBLIC KEY-----`;
|
|
110
|
+
/**
|
|
111
|
+
* Get the frontend URL with fallbacks
|
|
112
|
+
*/
|
|
113
|
+
export async function getFrontendUrl() {
|
|
114
|
+
// 1. Environment variable override
|
|
115
|
+
if (process.env.ENACT_FRONTEND_URL) {
|
|
116
|
+
return process.env.ENACT_FRONTEND_URL;
|
|
117
|
+
}
|
|
118
|
+
// 2. Config file setting
|
|
119
|
+
const config = await readConfig();
|
|
120
|
+
if (config.urls?.frontend) {
|
|
121
|
+
return config.urls.frontend;
|
|
122
|
+
}
|
|
123
|
+
// 3. Default
|
|
124
|
+
return DEFAULT_FRONTEND_URL;
|
|
125
|
+
}
|
|
126
|
+
/**
|
|
127
|
+
* Get the API URL with fallbacks
|
|
128
|
+
*/
|
|
129
|
+
export async function getApiUrl() {
|
|
130
|
+
// 1. Environment variable override
|
|
131
|
+
if (process.env.ENACT_API_URL) {
|
|
132
|
+
return process.env.ENACT_API_URL;
|
|
133
|
+
}
|
|
134
|
+
// 2. Config file setting
|
|
135
|
+
const config = await readConfig();
|
|
136
|
+
if (config.urls?.api) {
|
|
137
|
+
return config.urls.api;
|
|
138
|
+
}
|
|
139
|
+
// 3. Default
|
|
140
|
+
return DEFAULT_API_URL;
|
|
141
|
+
}
|
|
142
|
+
/**
|
|
143
|
+
* Set the frontend URL in config
|
|
144
|
+
*/
|
|
145
|
+
export async function setFrontendUrl(url) {
|
|
146
|
+
const config = await readConfig();
|
|
147
|
+
if (!config.urls) {
|
|
148
|
+
config.urls = {};
|
|
149
|
+
}
|
|
150
|
+
config.urls.frontend = url;
|
|
151
|
+
await writeConfig(config);
|
|
152
|
+
}
|
|
153
|
+
/**
|
|
154
|
+
* Set the API URL in config
|
|
155
|
+
*/
|
|
156
|
+
export async function setApiUrl(url) {
|
|
157
|
+
const config = await readConfig();
|
|
158
|
+
if (!config.urls) {
|
|
159
|
+
config.urls = {};
|
|
160
|
+
}
|
|
161
|
+
config.urls.api = url;
|
|
162
|
+
await writeConfig(config);
|
|
163
|
+
}
|
|
164
|
+
/**
|
|
165
|
+
* Reset URLs to defaults
|
|
166
|
+
*/
|
|
167
|
+
export async function resetUrls() {
|
|
168
|
+
const config = await readConfig();
|
|
169
|
+
if (config.urls) {
|
|
170
|
+
delete config.urls.frontend;
|
|
171
|
+
delete config.urls.api;
|
|
172
|
+
}
|
|
173
|
+
await writeConfig(config);
|
|
174
|
+
}
|
|
175
|
+
/**
|
|
176
|
+
* Get current URL configuration
|
|
177
|
+
*/
|
|
178
|
+
export async function getUrlConfig() {
|
|
179
|
+
const config = await readConfig();
|
|
180
|
+
// Determine frontend URL source
|
|
181
|
+
let frontendValue = DEFAULT_FRONTEND_URL;
|
|
182
|
+
let frontendSource = "default";
|
|
183
|
+
if (config.urls?.frontend) {
|
|
184
|
+
frontendValue = config.urls.frontend;
|
|
185
|
+
frontendSource = "config";
|
|
186
|
+
}
|
|
187
|
+
if (process.env.ENACT_FRONTEND_URL) {
|
|
188
|
+
frontendValue = process.env.ENACT_FRONTEND_URL;
|
|
189
|
+
frontendSource = "environment";
|
|
190
|
+
}
|
|
191
|
+
// Determine API URL source
|
|
192
|
+
let apiValue = DEFAULT_API_URL;
|
|
193
|
+
let apiSource = "default";
|
|
194
|
+
if (config.urls?.api) {
|
|
195
|
+
apiValue = config.urls.api;
|
|
196
|
+
apiSource = "config";
|
|
197
|
+
}
|
|
198
|
+
if (process.env.ENACT_API_URL) {
|
|
199
|
+
apiValue = process.env.ENACT_API_URL;
|
|
200
|
+
apiSource = "environment";
|
|
201
|
+
}
|
|
202
|
+
return {
|
|
203
|
+
frontend: { value: frontendValue, source: frontendSource },
|
|
204
|
+
api: { value: apiValue, source: apiSource },
|
|
205
|
+
};
|
|
206
|
+
}
|
|
207
|
+
/**
|
|
208
|
+
* Ensure trusted keys directory exists with default key
|
|
209
|
+
*/
|
|
210
|
+
async function ensureTrustedKeysDir() {
|
|
211
|
+
if (!existsSync(CONFIG_DIR)) {
|
|
212
|
+
await mkdir(CONFIG_DIR, { recursive: true });
|
|
213
|
+
}
|
|
214
|
+
if (!existsSync(TRUSTED_KEYS_DIR)) {
|
|
215
|
+
await mkdir(TRUSTED_KEYS_DIR, { recursive: true });
|
|
216
|
+
}
|
|
217
|
+
// Create default Enact Protocol key if it doesn't exist
|
|
218
|
+
const defaultKeyFile = join(TRUSTED_KEYS_DIR, "enact-protocol-official.pem");
|
|
219
|
+
const defaultMetaFile = join(TRUSTED_KEYS_DIR, "enact-protocol-official.meta");
|
|
220
|
+
if (!existsSync(defaultKeyFile)) {
|
|
221
|
+
await writeFile(defaultKeyFile, DEFAULT_ENACT_PUBLIC_KEY);
|
|
222
|
+
}
|
|
223
|
+
if (!existsSync(defaultMetaFile)) {
|
|
224
|
+
const defaultMeta = {
|
|
225
|
+
name: "Enact Protocol Official",
|
|
226
|
+
description: "Official Enact Protocol signing key for verified tools",
|
|
227
|
+
addedAt: new Date().toISOString(),
|
|
228
|
+
source: "default",
|
|
229
|
+
keyFile: "enact-protocol-official.pem"
|
|
230
|
+
};
|
|
231
|
+
await writeFile(defaultMetaFile, JSON.stringify(defaultMeta, null, 2));
|
|
232
|
+
}
|
|
233
|
+
}
|
|
234
|
+
/**
|
|
235
|
+
* Read all trusted keys from directory
|
|
236
|
+
*/
|
|
237
|
+
export async function getTrustedKeys() {
|
|
238
|
+
await ensureTrustedKeysDir();
|
|
239
|
+
const keys = [];
|
|
240
|
+
try {
|
|
241
|
+
const { readdir } = await import('fs/promises');
|
|
242
|
+
const files = await readdir(TRUSTED_KEYS_DIR);
|
|
243
|
+
// Get all .pem files
|
|
244
|
+
const pemFiles = files.filter(f => f.endsWith('.pem'));
|
|
245
|
+
for (const pemFile of pemFiles) {
|
|
246
|
+
try {
|
|
247
|
+
const keyId = pemFile.replace('.pem', '');
|
|
248
|
+
const keyPath = join(TRUSTED_KEYS_DIR, pemFile);
|
|
249
|
+
const metaPath = join(TRUSTED_KEYS_DIR, `${keyId}.meta`);
|
|
250
|
+
// Read the public key
|
|
251
|
+
const publicKey = await readFile(keyPath, 'utf8');
|
|
252
|
+
// Read metadata if it exists
|
|
253
|
+
let meta = {
|
|
254
|
+
name: keyId,
|
|
255
|
+
addedAt: new Date().toISOString(),
|
|
256
|
+
source: "user",
|
|
257
|
+
keyFile: pemFile
|
|
258
|
+
};
|
|
259
|
+
if (existsSync(metaPath)) {
|
|
260
|
+
try {
|
|
261
|
+
const metaData = await readFile(metaPath, 'utf8');
|
|
262
|
+
meta = { ...meta, ...JSON.parse(metaData) };
|
|
263
|
+
}
|
|
264
|
+
catch {
|
|
265
|
+
// Use defaults if meta file is corrupted
|
|
266
|
+
}
|
|
267
|
+
}
|
|
268
|
+
keys.push({
|
|
269
|
+
id: keyId,
|
|
270
|
+
name: meta.name,
|
|
271
|
+
publicKey: publicKey.trim(),
|
|
272
|
+
description: meta.description,
|
|
273
|
+
addedAt: meta.addedAt,
|
|
274
|
+
source: meta.source,
|
|
275
|
+
keyFile: pemFile
|
|
276
|
+
});
|
|
277
|
+
}
|
|
278
|
+
catch (error) {
|
|
279
|
+
console.warn(`Warning: Could not read key file ${pemFile}:`, error);
|
|
280
|
+
}
|
|
281
|
+
}
|
|
282
|
+
}
|
|
283
|
+
catch (error) {
|
|
284
|
+
console.error("Failed to read trusted keys directory:", error);
|
|
285
|
+
}
|
|
286
|
+
return keys;
|
|
287
|
+
}
|
|
288
|
+
/**
|
|
289
|
+
* Add a trusted key
|
|
290
|
+
*/
|
|
291
|
+
export async function addTrustedKey(keyData) {
|
|
292
|
+
await ensureTrustedKeysDir();
|
|
293
|
+
const keyFile = `${keyData.id}.pem`;
|
|
294
|
+
const metaFile = `${keyData.id}.meta`;
|
|
295
|
+
const keyPath = join(TRUSTED_KEYS_DIR, keyFile);
|
|
296
|
+
const metaPath = join(TRUSTED_KEYS_DIR, metaFile);
|
|
297
|
+
// Check if key already exists
|
|
298
|
+
if (existsSync(keyPath)) {
|
|
299
|
+
throw new Error(`Key with ID '${keyData.id}' already exists`);
|
|
300
|
+
}
|
|
301
|
+
// Write the public key file
|
|
302
|
+
await writeFile(keyPath, keyData.publicKey);
|
|
303
|
+
// Write the metadata file
|
|
304
|
+
const meta = {
|
|
305
|
+
name: keyData.name,
|
|
306
|
+
description: keyData.description,
|
|
307
|
+
addedAt: new Date().toISOString(),
|
|
308
|
+
source: keyData.source || "user",
|
|
309
|
+
keyFile
|
|
310
|
+
};
|
|
311
|
+
await writeFile(metaPath, JSON.stringify(meta, null, 2));
|
|
312
|
+
}
|
|
313
|
+
/**
|
|
314
|
+
* Remove a trusted key
|
|
315
|
+
*/
|
|
316
|
+
export async function removeTrustedKey(keyId) {
|
|
317
|
+
const keyPath = join(TRUSTED_KEYS_DIR, `${keyId}.pem`);
|
|
318
|
+
const metaPath = join(TRUSTED_KEYS_DIR, `${keyId}.meta`);
|
|
319
|
+
if (!existsSync(keyPath)) {
|
|
320
|
+
throw new Error(`Trusted key '${keyId}' not found`);
|
|
321
|
+
}
|
|
322
|
+
// Remove both files
|
|
323
|
+
const { unlink } = await import('fs/promises');
|
|
324
|
+
await unlink(keyPath);
|
|
325
|
+
if (existsSync(metaPath)) {
|
|
326
|
+
await unlink(metaPath);
|
|
327
|
+
}
|
|
328
|
+
}
|
|
329
|
+
/**
|
|
330
|
+
* Get a specific trusted key
|
|
331
|
+
*/
|
|
332
|
+
export async function getTrustedKey(keyId) {
|
|
333
|
+
const keys = await getTrustedKeys();
|
|
334
|
+
return keys.find(k => k.id === keyId) || null;
|
|
335
|
+
}
|
|
336
|
+
/**
|
|
337
|
+
* Check if a public key is trusted
|
|
338
|
+
*/
|
|
339
|
+
export async function isKeyTrusted(publicKey) {
|
|
340
|
+
const keys = await getTrustedKeys();
|
|
341
|
+
return keys.some(k => k.publicKey.trim() === publicKey.trim());
|
|
342
|
+
}
|
package/package.json
CHANGED
package/src/api/enact-api.ts
CHANGED
|
@@ -5,19 +5,32 @@ import {
|
|
|
5
5
|
CLITokenCreate,
|
|
6
6
|
OAuthTokenExchange,
|
|
7
7
|
} from "./types";
|
|
8
|
+
import { getFrontendUrl, getApiUrl } from "../utils/config";
|
|
8
9
|
|
|
9
10
|
export class EnactApiClient {
|
|
10
11
|
baseUrl: string;
|
|
11
12
|
supabaseUrl: string;
|
|
12
13
|
|
|
13
14
|
constructor(
|
|
14
|
-
baseUrl: string
|
|
15
|
-
supabaseUrl: string
|
|
15
|
+
baseUrl: string,
|
|
16
|
+
supabaseUrl: string,
|
|
16
17
|
) {
|
|
17
18
|
this.baseUrl = baseUrl.replace(/\/$/, ""); // Remove trailing slash
|
|
18
19
|
this.supabaseUrl = supabaseUrl.replace(/\/$/, "");
|
|
19
20
|
}
|
|
20
21
|
|
|
22
|
+
/**
|
|
23
|
+
* Create API client with config-based URLs
|
|
24
|
+
*/
|
|
25
|
+
static async create(
|
|
26
|
+
baseUrl?: string,
|
|
27
|
+
supabaseUrl?: string,
|
|
28
|
+
): Promise<EnactApiClient> {
|
|
29
|
+
const frontendUrl = baseUrl || await getFrontendUrl();
|
|
30
|
+
const apiUrl = supabaseUrl || await getApiUrl();
|
|
31
|
+
return new EnactApiClient(frontendUrl, apiUrl);
|
|
32
|
+
}
|
|
33
|
+
|
|
21
34
|
// Helper method to make authenticated requests
|
|
22
35
|
private async makeRequest<T>(
|
|
23
36
|
endpoint: string,
|
|
@@ -545,8 +558,13 @@ export class EnactApiClient {
|
|
|
545
558
|
}
|
|
546
559
|
}
|
|
547
560
|
|
|
548
|
-
// Export a default instance
|
|
549
|
-
export
|
|
561
|
+
// Export a default instance factory
|
|
562
|
+
export async function createDefaultApiClient(): Promise<EnactApiClient> {
|
|
563
|
+
return await EnactApiClient.create();
|
|
564
|
+
}
|
|
565
|
+
|
|
566
|
+
// Keep backward compatibility with sync usage
|
|
567
|
+
export const enactApi = new EnactApiClient("https://enact.tools", "https://xjnhhxwxovjifdxdwzih.supabase.co");
|
|
550
568
|
|
|
551
569
|
// Export error types for better error handling
|
|
552
570
|
export class EnactApiError extends Error {
|
|
@@ -565,5 +583,7 @@ export function createEnactApiClient(
|
|
|
565
583
|
baseUrl?: string,
|
|
566
584
|
supabaseUrl?: string,
|
|
567
585
|
): EnactApiClient {
|
|
568
|
-
|
|
586
|
+
const defaultFrontend = "https://enact.tools";
|
|
587
|
+
const defaultApi = "https://xjnhhxwxovjifdxdwzih.supabase.co";
|
|
588
|
+
return new EnactApiClient(baseUrl || defaultFrontend, supabaseUrl || defaultApi);
|
|
569
589
|
}
|
|
@@ -12,6 +12,7 @@ import fs from "fs/promises";
|
|
|
12
12
|
import path from "path";
|
|
13
13
|
import crypto from "crypto";
|
|
14
14
|
import { spawn, spawnSync } from "child_process";
|
|
15
|
+
import { exit } from "process";
|
|
15
16
|
|
|
16
17
|
export interface DaggerExecutionOptions {
|
|
17
18
|
baseImage?: string; // Default container image
|
|
@@ -457,7 +458,8 @@ export class DaggerExecutionProvider extends ExecutionProvider {
|
|
|
457
458
|
logger.debug(`Waiting ${waitTime}ms before retry...`);
|
|
458
459
|
await new Promise((resolve) => setTimeout(resolve, waitTime));
|
|
459
460
|
}
|
|
460
|
-
}
|
|
461
|
+
}
|
|
462
|
+
|
|
461
463
|
}
|
|
462
464
|
|
|
463
465
|
// All retries failed
|
|
@@ -623,6 +625,7 @@ export class DaggerExecutionProvider extends ExecutionProvider {
|
|
|
623
625
|
throw error;
|
|
624
626
|
} finally {
|
|
625
627
|
this.abortController = null;
|
|
628
|
+
|
|
626
629
|
}
|
|
627
630
|
}
|
|
628
631
|
|
|
@@ -1240,50 +1243,7 @@ export class DaggerExecutionProvider extends ExecutionProvider {
|
|
|
1240
1243
|
}
|
|
1241
1244
|
}
|
|
1242
1245
|
|
|
1243
|
-
|
|
1244
|
-
* Enhanced force cleanup for synchronous exit handlers
|
|
1245
|
-
*/
|
|
1246
|
-
private forceCleanup(): void {
|
|
1247
|
-
if (this.isShuttingDown) return;
|
|
1248
|
-
|
|
1249
|
-
try {
|
|
1250
|
-
logger.info("🔄 Force cleaning up Dagger engines...");
|
|
1251
|
-
|
|
1252
|
-
const result = spawnSync(
|
|
1253
|
-
"docker",
|
|
1254
|
-
[
|
|
1255
|
-
"ps",
|
|
1256
|
-
"--all",
|
|
1257
|
-
"--filter",
|
|
1258
|
-
"name=dagger-engine",
|
|
1259
|
-
"--format",
|
|
1260
|
-
"{{.Names}}",
|
|
1261
|
-
],
|
|
1262
|
-
{
|
|
1263
|
-
encoding: "utf8",
|
|
1264
|
-
timeout: 5000,
|
|
1265
|
-
},
|
|
1266
|
-
);
|
|
1267
|
-
|
|
1268
|
-
if (result.stdout) {
|
|
1269
|
-
const names = result.stdout
|
|
1270
|
-
.trim()
|
|
1271
|
-
.split("\n")
|
|
1272
|
-
.filter((n: string) => n.trim());
|
|
1273
|
-
if (names.length > 0) {
|
|
1274
|
-
logger.info(
|
|
1275
|
-
`Found ${names.length} engine containers, force removing...`,
|
|
1276
|
-
);
|
|
1277
|
-
for (const name of names) {
|
|
1278
|
-
spawnSync("docker", ["rm", "-f", name.trim()], { timeout: 3000 });
|
|
1279
|
-
}
|
|
1280
|
-
logger.info("✅ Force cleanup completed");
|
|
1281
|
-
}
|
|
1282
|
-
}
|
|
1283
|
-
} catch (error) {
|
|
1284
|
-
logger.debug("Force cleanup failed (this is usually fine):", error);
|
|
1285
|
-
}
|
|
1286
|
-
}
|
|
1246
|
+
|
|
1287
1247
|
|
|
1288
1248
|
/**
|
|
1289
1249
|
* Get current engine status for debugging
|
package/src/core/EnactCore.ts
CHANGED
|
@@ -18,6 +18,7 @@ import yaml from "yaml";
|
|
|
18
18
|
import fs from "fs";
|
|
19
19
|
import path from "path";
|
|
20
20
|
import { CryptoUtils, KeyManager, SecurityConfigManager, SigningService } from "@enactprotocol/security";
|
|
21
|
+
import { getFrontendUrl, getApiUrl } from "../utils/config";
|
|
21
22
|
|
|
22
23
|
export interface EnactCoreOptions {
|
|
23
24
|
apiUrl?: string;
|
|
@@ -60,21 +61,35 @@ export class EnactCore {
|
|
|
60
61
|
|
|
61
62
|
constructor(options: EnactCoreOptions = {}) {
|
|
62
63
|
this.options = {
|
|
63
|
-
apiUrl: "https://enact.tools",
|
|
64
|
-
supabaseUrl: "https://xjnhhxwxovjifdxdwzih.supabase.co",
|
|
64
|
+
apiUrl: "https://enact.tools", // Default, will be overridden by factory
|
|
65
|
+
supabaseUrl: "https://xjnhhxwxovjifdxdwzih.supabase.co", // Default, will be overridden by factory
|
|
65
66
|
executionProvider: "dagger",
|
|
66
67
|
defaultTimeout: "30s",
|
|
67
68
|
...options,
|
|
68
69
|
};
|
|
69
70
|
|
|
70
71
|
this.apiClient = new EnactApiClient(
|
|
71
|
-
this.options.apiUrl
|
|
72
|
-
this.options.supabaseUrl
|
|
72
|
+
this.options.apiUrl!,
|
|
73
|
+
this.options.supabaseUrl!,
|
|
73
74
|
);
|
|
74
75
|
|
|
75
76
|
// Initialize the appropriate execution provider
|
|
76
77
|
this.executionProvider = this.createExecutionProvider();
|
|
77
78
|
}
|
|
79
|
+
|
|
80
|
+
/**
|
|
81
|
+
* Create EnactCore with config-based URLs
|
|
82
|
+
*/
|
|
83
|
+
static async create(options: EnactCoreOptions = {}): Promise<EnactCore> {
|
|
84
|
+
const frontendUrl = options.apiUrl || await getFrontendUrl();
|
|
85
|
+
const apiUrl = options.supabaseUrl || await getApiUrl();
|
|
86
|
+
|
|
87
|
+
return new EnactCore({
|
|
88
|
+
...options,
|
|
89
|
+
apiUrl: frontendUrl,
|
|
90
|
+
supabaseUrl: apiUrl,
|
|
91
|
+
});
|
|
92
|
+
}
|
|
78
93
|
/**
|
|
79
94
|
* Set authentication token for API operations
|
|
80
95
|
*/
|
package/src/index.ts
CHANGED
|
@@ -3,6 +3,8 @@ export { EnactCore } from './core/EnactCore';
|
|
|
3
3
|
export { DirectExecutionProvider } from './core/DirectExecutionProvider';
|
|
4
4
|
export { DaggerExecutionProvider } from './core/DaggerExecutionProvider';
|
|
5
5
|
|
|
6
|
+
// Constants - now handled in config utils
|
|
7
|
+
|
|
6
8
|
// Types and utilities
|
|
7
9
|
export type { EnactTool } from './types';
|
|
8
10
|
export type { EnactToolDefinition } from './api/types';
|
package/src/lib/enact-direct.ts
CHANGED
|
@@ -5,6 +5,7 @@ import {
|
|
|
5
5
|
type ToolExecuteOptions,
|
|
6
6
|
} from "../core/EnactCore";
|
|
7
7
|
import type { EnactTool, ExecutionResult } from "../types";
|
|
8
|
+
import { getFrontendUrl, getApiUrl } from "../utils/config";
|
|
8
9
|
|
|
9
10
|
/**
|
|
10
11
|
* Direct Enact Library Interface
|
|
@@ -23,19 +24,35 @@ export class EnactDirect {
|
|
|
23
24
|
defaultTimeout?: string;
|
|
24
25
|
} = {},
|
|
25
26
|
) {
|
|
27
|
+
// We need to handle async config loading in a factory method
|
|
26
28
|
this.core = new EnactCore({
|
|
27
|
-
apiUrl:
|
|
28
|
-
|
|
29
|
-
supabaseUrl:
|
|
30
|
-
options.supabaseUrl ||
|
|
31
|
-
process.env.ENACT_SUPABASE_URL ||
|
|
32
|
-
"https://xjnhhxwxovjifdxdwzih.supabase.co",
|
|
29
|
+
apiUrl: options.apiUrl || process.env.ENACT_FRONTEND_URL || "https://enact.tools",
|
|
30
|
+
supabaseUrl: options.supabaseUrl || process.env.ENACT_API_URL || "https://xjnhhxwxovjifdxdwzih.supabase.co",
|
|
33
31
|
executionProvider: "direct",
|
|
34
32
|
authToken: options.authToken || process.env.ENACT_AUTH_TOKEN,
|
|
35
33
|
defaultTimeout: options.defaultTimeout || "30s",
|
|
36
34
|
});
|
|
37
35
|
}
|
|
38
36
|
|
|
37
|
+
/**
|
|
38
|
+
* Create EnactDirect with config-based URLs
|
|
39
|
+
*/
|
|
40
|
+
static async create(options: {
|
|
41
|
+
apiUrl?: string;
|
|
42
|
+
supabaseUrl?: string;
|
|
43
|
+
authToken?: string;
|
|
44
|
+
defaultTimeout?: string;
|
|
45
|
+
} = {}): Promise<EnactDirect> {
|
|
46
|
+
const frontendUrl = options.apiUrl || process.env.ENACT_FRONTEND_URL || await getFrontendUrl();
|
|
47
|
+
const apiUrl = options.supabaseUrl || process.env.ENACT_API_URL || await getApiUrl();
|
|
48
|
+
|
|
49
|
+
return new EnactDirect({
|
|
50
|
+
...options,
|
|
51
|
+
apiUrl: frontendUrl,
|
|
52
|
+
supabaseUrl: apiUrl,
|
|
53
|
+
});
|
|
54
|
+
}
|
|
55
|
+
|
|
39
56
|
/**
|
|
40
57
|
* Execute a tool by name with inputs
|
|
41
58
|
*
|
|
@@ -2,6 +2,7 @@
|
|
|
2
2
|
import { EnactCore } from "../core/EnactCore";
|
|
3
3
|
import type { EnactTool, ExecutionResult } from "../types";
|
|
4
4
|
import type { ToolSearchOptions, ToolExecuteOptions } from "../core/EnactCore";
|
|
5
|
+
import { getFrontendUrl, getApiUrl } from "../utils/config";
|
|
5
6
|
|
|
6
7
|
export class McpCoreService {
|
|
7
8
|
private core: EnactCore;
|
|
@@ -13,12 +14,29 @@ export class McpCoreService {
|
|
|
13
14
|
}) {
|
|
14
15
|
this.core = new EnactCore({
|
|
15
16
|
apiUrl: options?.apiUrl || "https://enact.tools",
|
|
16
|
-
supabaseUrl:
|
|
17
|
-
options?.supabaseUrl || "https://xjnhhxwxovjifdxdwzih.supabase.co",
|
|
17
|
+
supabaseUrl: options?.supabaseUrl || "https://xjnhhxwxovjifdxdwzih.supabase.co",
|
|
18
18
|
authToken: options?.authToken,
|
|
19
19
|
});
|
|
20
20
|
}
|
|
21
21
|
|
|
22
|
+
/**
|
|
23
|
+
* Create McpCoreService with config-based URLs
|
|
24
|
+
*/
|
|
25
|
+
static async create(options?: {
|
|
26
|
+
apiUrl?: string;
|
|
27
|
+
supabaseUrl?: string;
|
|
28
|
+
authToken?: string;
|
|
29
|
+
}): Promise<McpCoreService> {
|
|
30
|
+
const frontendUrl = options?.apiUrl || await getFrontendUrl();
|
|
31
|
+
const apiUrl = options?.supabaseUrl || await getApiUrl();
|
|
32
|
+
|
|
33
|
+
return new McpCoreService({
|
|
34
|
+
...options,
|
|
35
|
+
apiUrl: frontendUrl,
|
|
36
|
+
supabaseUrl: apiUrl,
|
|
37
|
+
});
|
|
38
|
+
}
|
|
39
|
+
|
|
22
40
|
/**
|
|
23
41
|
* Set authentication token
|
|
24
42
|
*/
|
package/src/utils/config.ts
CHANGED
|
@@ -7,11 +7,16 @@ import { mkdir, readFile, writeFile } from "fs/promises";
|
|
|
7
7
|
// Define config paths
|
|
8
8
|
const CONFIG_DIR = join(homedir(), ".enact");
|
|
9
9
|
const CONFIG_FILE = join(CONFIG_DIR, "config.json");
|
|
10
|
+
const TRUSTED_KEYS_DIR = join(CONFIG_DIR, "trusted-keys");
|
|
10
11
|
|
|
11
12
|
// Define config interface
|
|
12
13
|
export interface EnactConfig {
|
|
13
14
|
defaultUrl?: string;
|
|
14
15
|
history?: string[];
|
|
16
|
+
urls?: {
|
|
17
|
+
frontend?: string;
|
|
18
|
+
api?: string;
|
|
19
|
+
};
|
|
15
20
|
}
|
|
16
21
|
|
|
17
22
|
/**
|
|
@@ -23,7 +28,14 @@ export async function ensureConfig(): Promise<void> {
|
|
|
23
28
|
}
|
|
24
29
|
|
|
25
30
|
if (!existsSync(CONFIG_FILE)) {
|
|
26
|
-
|
|
31
|
+
const defaultConfig = {
|
|
32
|
+
history: [],
|
|
33
|
+
urls: {
|
|
34
|
+
frontend: DEFAULT_FRONTEND_URL,
|
|
35
|
+
api: DEFAULT_API_URL,
|
|
36
|
+
},
|
|
37
|
+
};
|
|
38
|
+
await writeFile(CONFIG_FILE, JSON.stringify(defaultConfig, null, 2));
|
|
27
39
|
}
|
|
28
40
|
}
|
|
29
41
|
|
|
@@ -35,10 +47,27 @@ export async function readConfig(): Promise<EnactConfig> {
|
|
|
35
47
|
|
|
36
48
|
try {
|
|
37
49
|
const data = await readFile(CONFIG_FILE, "utf8");
|
|
38
|
-
|
|
50
|
+
const config = JSON.parse(data) as EnactConfig;
|
|
51
|
+
|
|
52
|
+
// Migrate old configs that don't have URLs section
|
|
53
|
+
if (!config.urls) {
|
|
54
|
+
config.urls = {
|
|
55
|
+
frontend: DEFAULT_FRONTEND_URL,
|
|
56
|
+
api: DEFAULT_API_URL,
|
|
57
|
+
};
|
|
58
|
+
await writeConfig(config);
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
return config;
|
|
39
62
|
} catch (error) {
|
|
40
63
|
console.error("Failed to read config:", (error as Error).message);
|
|
41
|
-
return {
|
|
64
|
+
return {
|
|
65
|
+
history: [],
|
|
66
|
+
urls: {
|
|
67
|
+
frontend: DEFAULT_FRONTEND_URL,
|
|
68
|
+
api: DEFAULT_API_URL,
|
|
69
|
+
},
|
|
70
|
+
};
|
|
42
71
|
}
|
|
43
72
|
}
|
|
44
73
|
|
|
@@ -95,3 +124,315 @@ export async function getDefaultUrl(): Promise<string | undefined> {
|
|
|
95
124
|
const config = await readConfig();
|
|
96
125
|
return config.defaultUrl;
|
|
97
126
|
}
|
|
127
|
+
|
|
128
|
+
// Default URLs
|
|
129
|
+
const DEFAULT_FRONTEND_URL = "https://enact.tools";
|
|
130
|
+
const DEFAULT_API_URL = "https://xjnhhxwxovjifdxdwzih.supabase.co";
|
|
131
|
+
|
|
132
|
+
// Default trusted public key (Enact Protocol official key)
|
|
133
|
+
const DEFAULT_ENACT_PUBLIC_KEY = `-----BEGIN PUBLIC KEY-----
|
|
134
|
+
MFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAE8VyE3jGm5yT2mKnPx1dQF7q8Z2Kv
|
|
135
|
+
7mX9YnE2mK8vF3tY9pL6xH2dF8sK3mN7wQ5vT2gR8sL4xN6pM9uE3wF2Qw==
|
|
136
|
+
-----END PUBLIC KEY-----`;
|
|
137
|
+
|
|
138
|
+
// Trusted key metadata interface (for .meta files)
|
|
139
|
+
export interface TrustedKeyMeta {
|
|
140
|
+
name: string;
|
|
141
|
+
description?: string;
|
|
142
|
+
addedAt: string;
|
|
143
|
+
source: "default" | "user" | "organization";
|
|
144
|
+
keyFile: string;
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
// Combined trusted key interface
|
|
148
|
+
export interface TrustedKey {
|
|
149
|
+
id: string;
|
|
150
|
+
name: string;
|
|
151
|
+
publicKey: string;
|
|
152
|
+
description?: string;
|
|
153
|
+
addedAt: string;
|
|
154
|
+
source: "default" | "user" | "organization";
|
|
155
|
+
keyFile: string;
|
|
156
|
+
}
|
|
157
|
+
|
|
158
|
+
/**
|
|
159
|
+
* Get the frontend URL with fallbacks
|
|
160
|
+
*/
|
|
161
|
+
export async function getFrontendUrl(): Promise<string> {
|
|
162
|
+
// 1. Environment variable override
|
|
163
|
+
if (process.env.ENACT_FRONTEND_URL) {
|
|
164
|
+
return process.env.ENACT_FRONTEND_URL;
|
|
165
|
+
}
|
|
166
|
+
|
|
167
|
+
// 2. Config file setting
|
|
168
|
+
const config = await readConfig();
|
|
169
|
+
if (config.urls?.frontend) {
|
|
170
|
+
return config.urls.frontend;
|
|
171
|
+
}
|
|
172
|
+
|
|
173
|
+
// 3. Default
|
|
174
|
+
return DEFAULT_FRONTEND_URL;
|
|
175
|
+
}
|
|
176
|
+
|
|
177
|
+
/**
|
|
178
|
+
* Get the API URL with fallbacks
|
|
179
|
+
*/
|
|
180
|
+
export async function getApiUrl(): Promise<string> {
|
|
181
|
+
// 1. Environment variable override
|
|
182
|
+
if (process.env.ENACT_API_URL) {
|
|
183
|
+
return process.env.ENACT_API_URL;
|
|
184
|
+
}
|
|
185
|
+
|
|
186
|
+
// 2. Config file setting
|
|
187
|
+
const config = await readConfig();
|
|
188
|
+
if (config.urls?.api) {
|
|
189
|
+
return config.urls.api;
|
|
190
|
+
}
|
|
191
|
+
|
|
192
|
+
// 3. Default
|
|
193
|
+
return DEFAULT_API_URL;
|
|
194
|
+
}
|
|
195
|
+
|
|
196
|
+
/**
|
|
197
|
+
* Set the frontend URL in config
|
|
198
|
+
*/
|
|
199
|
+
export async function setFrontendUrl(url: string): Promise<void> {
|
|
200
|
+
const config = await readConfig();
|
|
201
|
+
if (!config.urls) {
|
|
202
|
+
config.urls = {};
|
|
203
|
+
}
|
|
204
|
+
config.urls.frontend = url;
|
|
205
|
+
await writeConfig(config);
|
|
206
|
+
}
|
|
207
|
+
|
|
208
|
+
/**
|
|
209
|
+
* Set the API URL in config
|
|
210
|
+
*/
|
|
211
|
+
export async function setApiUrl(url: string): Promise<void> {
|
|
212
|
+
const config = await readConfig();
|
|
213
|
+
if (!config.urls) {
|
|
214
|
+
config.urls = {};
|
|
215
|
+
}
|
|
216
|
+
config.urls.api = url;
|
|
217
|
+
await writeConfig(config);
|
|
218
|
+
}
|
|
219
|
+
|
|
220
|
+
/**
|
|
221
|
+
* Reset URLs to defaults
|
|
222
|
+
*/
|
|
223
|
+
export async function resetUrls(): Promise<void> {
|
|
224
|
+
const config = await readConfig();
|
|
225
|
+
if (config.urls) {
|
|
226
|
+
delete config.urls.frontend;
|
|
227
|
+
delete config.urls.api;
|
|
228
|
+
}
|
|
229
|
+
await writeConfig(config);
|
|
230
|
+
}
|
|
231
|
+
|
|
232
|
+
/**
|
|
233
|
+
* Get current URL configuration
|
|
234
|
+
*/
|
|
235
|
+
export async function getUrlConfig(): Promise<{
|
|
236
|
+
frontend: { value: string; source: string };
|
|
237
|
+
api: { value: string; source: string };
|
|
238
|
+
}> {
|
|
239
|
+
const config = await readConfig();
|
|
240
|
+
|
|
241
|
+
// Determine frontend URL source
|
|
242
|
+
let frontendValue = DEFAULT_FRONTEND_URL;
|
|
243
|
+
let frontendSource = "default";
|
|
244
|
+
|
|
245
|
+
if (config.urls?.frontend) {
|
|
246
|
+
frontendValue = config.urls.frontend;
|
|
247
|
+
frontendSource = "config";
|
|
248
|
+
}
|
|
249
|
+
|
|
250
|
+
if (process.env.ENACT_FRONTEND_URL) {
|
|
251
|
+
frontendValue = process.env.ENACT_FRONTEND_URL;
|
|
252
|
+
frontendSource = "environment";
|
|
253
|
+
}
|
|
254
|
+
|
|
255
|
+
// Determine API URL source
|
|
256
|
+
let apiValue = DEFAULT_API_URL;
|
|
257
|
+
let apiSource = "default";
|
|
258
|
+
|
|
259
|
+
if (config.urls?.api) {
|
|
260
|
+
apiValue = config.urls.api;
|
|
261
|
+
apiSource = "config";
|
|
262
|
+
}
|
|
263
|
+
|
|
264
|
+
if (process.env.ENACT_API_URL) {
|
|
265
|
+
apiValue = process.env.ENACT_API_URL;
|
|
266
|
+
apiSource = "environment";
|
|
267
|
+
}
|
|
268
|
+
|
|
269
|
+
return {
|
|
270
|
+
frontend: { value: frontendValue, source: frontendSource },
|
|
271
|
+
api: { value: apiValue, source: apiSource },
|
|
272
|
+
};
|
|
273
|
+
}
|
|
274
|
+
|
|
275
|
+
/**
|
|
276
|
+
* Ensure trusted keys directory exists with default key
|
|
277
|
+
*/
|
|
278
|
+
async function ensureTrustedKeysDir(): Promise<void> {
|
|
279
|
+
if (!existsSync(CONFIG_DIR)) {
|
|
280
|
+
await mkdir(CONFIG_DIR, { recursive: true });
|
|
281
|
+
}
|
|
282
|
+
|
|
283
|
+
if (!existsSync(TRUSTED_KEYS_DIR)) {
|
|
284
|
+
await mkdir(TRUSTED_KEYS_DIR, { recursive: true });
|
|
285
|
+
}
|
|
286
|
+
|
|
287
|
+
// Create default Enact Protocol key if it doesn't exist
|
|
288
|
+
const defaultKeyFile = join(TRUSTED_KEYS_DIR, "enact-protocol-official.pem");
|
|
289
|
+
const defaultMetaFile = join(TRUSTED_KEYS_DIR, "enact-protocol-official.meta");
|
|
290
|
+
|
|
291
|
+
if (!existsSync(defaultKeyFile)) {
|
|
292
|
+
await writeFile(defaultKeyFile, DEFAULT_ENACT_PUBLIC_KEY);
|
|
293
|
+
}
|
|
294
|
+
|
|
295
|
+
if (!existsSync(defaultMetaFile)) {
|
|
296
|
+
const defaultMeta: TrustedKeyMeta = {
|
|
297
|
+
name: "Enact Protocol Official",
|
|
298
|
+
description: "Official Enact Protocol signing key for verified tools",
|
|
299
|
+
addedAt: new Date().toISOString(),
|
|
300
|
+
source: "default",
|
|
301
|
+
keyFile: "enact-protocol-official.pem"
|
|
302
|
+
};
|
|
303
|
+
await writeFile(defaultMetaFile, JSON.stringify(defaultMeta, null, 2));
|
|
304
|
+
}
|
|
305
|
+
}
|
|
306
|
+
|
|
307
|
+
/**
|
|
308
|
+
* Read all trusted keys from directory
|
|
309
|
+
*/
|
|
310
|
+
export async function getTrustedKeys(): Promise<TrustedKey[]> {
|
|
311
|
+
await ensureTrustedKeysDir();
|
|
312
|
+
|
|
313
|
+
const keys: TrustedKey[] = [];
|
|
314
|
+
|
|
315
|
+
try {
|
|
316
|
+
const { readdir } = await import('fs/promises');
|
|
317
|
+
const files = await readdir(TRUSTED_KEYS_DIR);
|
|
318
|
+
|
|
319
|
+
// Get all .pem files
|
|
320
|
+
const pemFiles = files.filter(f => f.endsWith('.pem'));
|
|
321
|
+
|
|
322
|
+
for (const pemFile of pemFiles) {
|
|
323
|
+
try {
|
|
324
|
+
const keyId = pemFile.replace('.pem', '');
|
|
325
|
+
const keyPath = join(TRUSTED_KEYS_DIR, pemFile);
|
|
326
|
+
const metaPath = join(TRUSTED_KEYS_DIR, `${keyId}.meta`);
|
|
327
|
+
|
|
328
|
+
// Read the public key
|
|
329
|
+
const publicKey = await readFile(keyPath, 'utf8');
|
|
330
|
+
|
|
331
|
+
// Read metadata if it exists
|
|
332
|
+
let meta: TrustedKeyMeta = {
|
|
333
|
+
name: keyId,
|
|
334
|
+
addedAt: new Date().toISOString(),
|
|
335
|
+
source: "user",
|
|
336
|
+
keyFile: pemFile
|
|
337
|
+
};
|
|
338
|
+
|
|
339
|
+
if (existsSync(metaPath)) {
|
|
340
|
+
try {
|
|
341
|
+
const metaData = await readFile(metaPath, 'utf8');
|
|
342
|
+
meta = { ...meta, ...JSON.parse(metaData) };
|
|
343
|
+
} catch {
|
|
344
|
+
// Use defaults if meta file is corrupted
|
|
345
|
+
}
|
|
346
|
+
}
|
|
347
|
+
|
|
348
|
+
keys.push({
|
|
349
|
+
id: keyId,
|
|
350
|
+
name: meta.name,
|
|
351
|
+
publicKey: publicKey.trim(),
|
|
352
|
+
description: meta.description,
|
|
353
|
+
addedAt: meta.addedAt,
|
|
354
|
+
source: meta.source,
|
|
355
|
+
keyFile: pemFile
|
|
356
|
+
});
|
|
357
|
+
} catch (error) {
|
|
358
|
+
console.warn(`Warning: Could not read key file ${pemFile}:`, error);
|
|
359
|
+
}
|
|
360
|
+
}
|
|
361
|
+
} catch (error) {
|
|
362
|
+
console.error("Failed to read trusted keys directory:", error);
|
|
363
|
+
}
|
|
364
|
+
|
|
365
|
+
return keys;
|
|
366
|
+
}
|
|
367
|
+
|
|
368
|
+
/**
|
|
369
|
+
* Add a trusted key
|
|
370
|
+
*/
|
|
371
|
+
export async function addTrustedKey(keyData: {
|
|
372
|
+
id: string;
|
|
373
|
+
name: string;
|
|
374
|
+
publicKey: string;
|
|
375
|
+
description?: string;
|
|
376
|
+
source?: "user" | "organization";
|
|
377
|
+
}): Promise<void> {
|
|
378
|
+
await ensureTrustedKeysDir();
|
|
379
|
+
|
|
380
|
+
const keyFile = `${keyData.id}.pem`;
|
|
381
|
+
const metaFile = `${keyData.id}.meta`;
|
|
382
|
+
const keyPath = join(TRUSTED_KEYS_DIR, keyFile);
|
|
383
|
+
const metaPath = join(TRUSTED_KEYS_DIR, metaFile);
|
|
384
|
+
|
|
385
|
+
// Check if key already exists
|
|
386
|
+
if (existsSync(keyPath)) {
|
|
387
|
+
throw new Error(`Key with ID '${keyData.id}' already exists`);
|
|
388
|
+
}
|
|
389
|
+
|
|
390
|
+
// Write the public key file
|
|
391
|
+
await writeFile(keyPath, keyData.publicKey);
|
|
392
|
+
|
|
393
|
+
// Write the metadata file
|
|
394
|
+
const meta: TrustedKeyMeta = {
|
|
395
|
+
name: keyData.name,
|
|
396
|
+
description: keyData.description,
|
|
397
|
+
addedAt: new Date().toISOString(),
|
|
398
|
+
source: keyData.source || "user",
|
|
399
|
+
keyFile
|
|
400
|
+
};
|
|
401
|
+
await writeFile(metaPath, JSON.stringify(meta, null, 2));
|
|
402
|
+
}
|
|
403
|
+
|
|
404
|
+
/**
|
|
405
|
+
* Remove a trusted key
|
|
406
|
+
*/
|
|
407
|
+
export async function removeTrustedKey(keyId: string): Promise<void> {
|
|
408
|
+
const keyPath = join(TRUSTED_KEYS_DIR, `${keyId}.pem`);
|
|
409
|
+
const metaPath = join(TRUSTED_KEYS_DIR, `${keyId}.meta`);
|
|
410
|
+
|
|
411
|
+
if (!existsSync(keyPath)) {
|
|
412
|
+
throw new Error(`Trusted key '${keyId}' not found`);
|
|
413
|
+
}
|
|
414
|
+
|
|
415
|
+
// Remove both files
|
|
416
|
+
const { unlink } = await import('fs/promises');
|
|
417
|
+
await unlink(keyPath);
|
|
418
|
+
|
|
419
|
+
if (existsSync(metaPath)) {
|
|
420
|
+
await unlink(metaPath);
|
|
421
|
+
}
|
|
422
|
+
}
|
|
423
|
+
|
|
424
|
+
/**
|
|
425
|
+
* Get a specific trusted key
|
|
426
|
+
*/
|
|
427
|
+
export async function getTrustedKey(keyId: string): Promise<TrustedKey | null> {
|
|
428
|
+
const keys = await getTrustedKeys();
|
|
429
|
+
return keys.find(k => k.id === keyId) || null;
|
|
430
|
+
}
|
|
431
|
+
|
|
432
|
+
/**
|
|
433
|
+
* Check if a public key is trusted
|
|
434
|
+
*/
|
|
435
|
+
export async function isKeyTrusted(publicKey: string): Promise<boolean> {
|
|
436
|
+
const keys = await getTrustedKeys();
|
|
437
|
+
return keys.some(k => k.publicKey.trim() === publicKey.trim());
|
|
438
|
+
}
|