@enactprotocol/shared 1.2.3 → 1.2.5

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.
@@ -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
- await writeFile(CONFIG_FILE, JSON.stringify({ history: [] }, null, 2));
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
- return JSON.parse(data);
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 { history: [] };
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
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@enactprotocol/shared",
3
- "version": "1.2.3",
3
+ "version": "1.2.5",
4
4
  "description": "Shared utilities and core functionality for Enact Protocol",
5
5
  "type": "module",
6
6
  "main": "./dist/index.js",
@@ -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 = "https://enact.tools",
15
- supabaseUrl: string = "https://xjnhhxwxovjifdxdwzih.supabase.co",
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 const enactApi = new EnactApiClient();
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
- return new EnactApiClient(baseUrl, supabaseUrl);
586
+ const defaultFrontend = "https://enact.tools";
587
+ const defaultApi = "https://xjnhhxwxovjifdxdwzih.supabase.co";
588
+ return new EnactApiClient(baseUrl || defaultFrontend, supabaseUrl || defaultApi);
569
589
  }
package/src/api/types.ts CHANGED
@@ -97,4 +97,5 @@ export interface EnactExecOptions {
97
97
  verbose?: boolean;
98
98
  force?: boolean; // Force execution even if verification fails (legacy)
99
99
  dangerouslySkipVerification?: boolean; // Skip all signature verification (DANGEROUS)
100
+ mount?: string; // Mount local directory to container (format: "local:container")
100
101
  }
@@ -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
 
@@ -699,6 +702,77 @@ export class DaggerExecutionProvider extends ExecutionProvider {
699
702
  });
700
703
  }
701
704
 
705
+ /**
706
+ * Setup directory mounting for the container
707
+ */
708
+ private async setupDirectoryMount(
709
+ client: Client,
710
+ container: Container,
711
+ mountSpec: string,
712
+ ): Promise<Container> {
713
+ try {
714
+ // Parse mount specification (format: "localPath" or "localPath:containerPath")
715
+ let localPath: string;
716
+ let containerPath: string;
717
+
718
+ // Handle Windows drive letters (e.g., C:\path) vs mount separator (:)
719
+ const colonIndex = mountSpec.indexOf(':');
720
+
721
+ if (colonIndex > 0) {
722
+ // Check if this might be a Windows drive letter (single letter followed by colon)
723
+ const potentialDriveLetter = mountSpec.substring(0, colonIndex);
724
+ const isWindowsDrive = potentialDriveLetter.length === 1 && /[A-Za-z]/.test(potentialDriveLetter);
725
+
726
+ if (isWindowsDrive) {
727
+ // Look for the next colon that separates local from container path
728
+ const nextColonIndex = mountSpec.indexOf(':', colonIndex + 1);
729
+ if (nextColonIndex > 0) {
730
+ localPath = mountSpec.substring(0, nextColonIndex);
731
+ containerPath = mountSpec.substring(nextColonIndex + 1);
732
+ } else {
733
+ // No container path specified, use default
734
+ localPath = mountSpec;
735
+ containerPath = '/workspace/src';
736
+ }
737
+ } else {
738
+ // Regular path:container split
739
+ localPath = mountSpec.substring(0, colonIndex);
740
+ containerPath = mountSpec.substring(colonIndex + 1);
741
+ }
742
+ } else if (colonIndex === 0) {
743
+ // Starts with colon (e.g., ":/app")
744
+ localPath = '';
745
+ containerPath = mountSpec.substring(1);
746
+ } else {
747
+ localPath = mountSpec;
748
+ containerPath = '/workspace/src'; // Default container path
749
+ }
750
+
751
+ // Resolve local path to absolute path
752
+ const path = require('path');
753
+ const resolvedLocalPath = path.resolve(localPath);
754
+
755
+ // Check if local directory exists
756
+ const fs = require('fs');
757
+ if (!fs.existsSync(resolvedLocalPath)) {
758
+ throw new Error(`Mount source directory does not exist: ${resolvedLocalPath}`);
759
+ }
760
+
761
+ // Create Directory object from local path
762
+ const hostDirectory = client.host().directory(resolvedLocalPath);
763
+
764
+ // Mount directory in container using withMountedDirectory for better performance
765
+ container = container.withMountedDirectory(containerPath, hostDirectory);
766
+
767
+ logger.debug(`📂 Mounted ${resolvedLocalPath} -> ${containerPath}`);
768
+
769
+ return container;
770
+ } catch (error) {
771
+ logger.error(`Failed to setup directory mount: ${error}`);
772
+ throw error;
773
+ }
774
+ }
775
+
702
776
  /**
703
777
  * Enhanced container setup with better tool detection and installation
704
778
  */
@@ -723,6 +797,11 @@ export class DaggerExecutionProvider extends ExecutionProvider {
723
797
  container = container.withWorkdir(this.options.workdir!);
724
798
  logger.debug(`📁 Working directory set to: ${this.options.workdir}`);
725
799
 
800
+ // Handle directory mounting if specified
801
+ if (environment.mount) {
802
+ container = await this.setupDirectoryMount(client, container, environment.mount);
803
+ }
804
+
726
805
  // Add environment variables from Enact tool env config
727
806
  for (const [key, value] of Object.entries(environment.vars)) {
728
807
  container = container.withEnvVariable(key, String(value));
@@ -1240,50 +1319,7 @@ export class DaggerExecutionProvider extends ExecutionProvider {
1240
1319
  }
1241
1320
  }
1242
1321
 
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
- }
1322
+
1287
1323
 
1288
1324
  /**
1289
1325
  * Get current engine status for debugging
@@ -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;
@@ -51,6 +52,7 @@ export interface ToolExecuteOptions {
51
52
  verbose?: boolean;
52
53
  isLocalFile?: boolean;
53
54
  dangerouslySkipVerification?: boolean;
55
+ mount?: string; // Mount local directory to container (format: "localPath" or "localPath:containerPath")
54
56
  }
55
57
 
56
58
  export class EnactCore {
@@ -60,21 +62,35 @@ export class EnactCore {
60
62
 
61
63
  constructor(options: EnactCoreOptions = {}) {
62
64
  this.options = {
63
- apiUrl: "https://enact.tools",
64
- supabaseUrl: "https://xjnhhxwxovjifdxdwzih.supabase.co",
65
+ apiUrl: "https://enact.tools", // Default, will be overridden by factory
66
+ supabaseUrl: "https://xjnhhxwxovjifdxdwzih.supabase.co", // Default, will be overridden by factory
65
67
  executionProvider: "dagger",
66
68
  defaultTimeout: "30s",
67
69
  ...options,
68
70
  };
69
71
 
70
72
  this.apiClient = new EnactApiClient(
71
- this.options.apiUrl,
72
- this.options.supabaseUrl,
73
+ this.options.apiUrl!,
74
+ this.options.supabaseUrl!,
73
75
  );
74
76
 
75
77
  // Initialize the appropriate execution provider
76
78
  this.executionProvider = this.createExecutionProvider();
77
79
  }
80
+
81
+ /**
82
+ * Create EnactCore with config-based URLs
83
+ */
84
+ static async create(options: EnactCoreOptions = {}): Promise<EnactCore> {
85
+ const frontendUrl = options.apiUrl || await getFrontendUrl();
86
+ const apiUrl = options.supabaseUrl || await getApiUrl();
87
+
88
+ return new EnactCore({
89
+ ...options,
90
+ apiUrl: frontendUrl,
91
+ supabaseUrl: apiUrl,
92
+ });
93
+ }
78
94
  /**
79
95
  * Set authentication token for API operations
80
96
  */
@@ -438,7 +454,7 @@ private async verifyTool(tool: EnactTool, dangerouslySkipVerification: boolean =
438
454
  { includeFields: ['command'] }
439
455
  );
440
456
 
441
- console.log("Final verification result:", isValid);
457
+ // console.log("Final verification result:", isValid);
442
458
 
443
459
  if (!isValid) {
444
460
  throw new Error(`Tool ${tool.name} has invalid signatures`);
@@ -495,6 +511,7 @@ private async verifyTool(tool: EnactTool, dangerouslySkipVerification: boolean =
495
511
  resources: {
496
512
  timeout: options.timeout || tool.timeout || this.options.defaultTimeout,
497
513
  },
514
+ mount: options.mount,
498
515
  },
499
516
  );
500
517
  } catch (error) {
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';
@@ -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
- options.apiUrl || process.env.ENACT_API_URL || "https://enact.tools",
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
  *