@enactprotocol/shared 1.2.13 → 2.0.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (207) hide show
  1. package/README.md +44 -0
  2. package/dist/config.d.ts +164 -0
  3. package/dist/config.d.ts.map +1 -0
  4. package/dist/config.js +386 -0
  5. package/dist/config.js.map +1 -0
  6. package/dist/constants.d.ts +15 -5
  7. package/dist/constants.d.ts.map +1 -0
  8. package/dist/constants.js +24 -8
  9. package/dist/constants.js.map +1 -0
  10. package/dist/execution/command.d.ts +102 -0
  11. package/dist/execution/command.d.ts.map +1 -0
  12. package/dist/execution/command.js +262 -0
  13. package/dist/execution/command.js.map +1 -0
  14. package/dist/execution/index.d.ts +12 -0
  15. package/dist/execution/index.d.ts.map +1 -0
  16. package/dist/execution/index.js +17 -0
  17. package/dist/execution/index.js.map +1 -0
  18. package/dist/execution/runtime.d.ts +82 -0
  19. package/dist/execution/runtime.d.ts.map +1 -0
  20. package/dist/execution/runtime.js +273 -0
  21. package/dist/execution/runtime.js.map +1 -0
  22. package/dist/execution/types.d.ts +306 -0
  23. package/dist/execution/types.d.ts.map +1 -0
  24. package/dist/execution/types.js +14 -0
  25. package/dist/execution/types.js.map +1 -0
  26. package/dist/execution/validation.d.ts +43 -0
  27. package/dist/execution/validation.d.ts.map +1 -0
  28. package/dist/execution/validation.js +430 -0
  29. package/dist/execution/validation.js.map +1 -0
  30. package/dist/index.d.ts +21 -21
  31. package/dist/index.d.ts.map +1 -0
  32. package/dist/index.js +49 -25
  33. package/dist/index.js.map +1 -0
  34. package/dist/manifest/index.d.ts +7 -0
  35. package/dist/manifest/index.d.ts.map +1 -0
  36. package/dist/manifest/index.js +10 -0
  37. package/dist/manifest/index.js.map +1 -0
  38. package/dist/manifest/loader.d.ts +76 -0
  39. package/dist/manifest/loader.d.ts.map +1 -0
  40. package/dist/manifest/loader.js +146 -0
  41. package/dist/manifest/loader.js.map +1 -0
  42. package/dist/manifest/parser.d.ts +64 -0
  43. package/dist/manifest/parser.d.ts.map +1 -0
  44. package/dist/manifest/parser.js +135 -0
  45. package/dist/manifest/parser.js.map +1 -0
  46. package/dist/manifest/validator.d.ts +95 -0
  47. package/dist/manifest/validator.d.ts.map +1 -0
  48. package/dist/manifest/validator.js +258 -0
  49. package/dist/manifest/validator.js.map +1 -0
  50. package/dist/paths.d.ts +57 -0
  51. package/dist/paths.d.ts.map +1 -0
  52. package/dist/paths.js +93 -0
  53. package/dist/paths.js.map +1 -0
  54. package/dist/registry.d.ts +73 -0
  55. package/dist/registry.d.ts.map +1 -0
  56. package/dist/registry.js +147 -0
  57. package/dist/registry.js.map +1 -0
  58. package/dist/resolver.d.ts +89 -0
  59. package/dist/resolver.d.ts.map +1 -0
  60. package/dist/resolver.js +282 -0
  61. package/dist/resolver.js.map +1 -0
  62. package/dist/types/index.d.ts +6 -0
  63. package/dist/types/index.d.ts.map +1 -0
  64. package/dist/types/index.js +5 -0
  65. package/dist/types/index.js.map +1 -0
  66. package/dist/types/manifest.d.ts +201 -0
  67. package/dist/types/manifest.d.ts.map +1 -0
  68. package/dist/types/manifest.js +13 -0
  69. package/dist/types/manifest.js.map +1 -0
  70. package/dist/types.d.ts +5 -132
  71. package/dist/types.d.ts.map +1 -0
  72. package/dist/types.js +5 -3
  73. package/dist/types.js.map +1 -0
  74. package/dist/utils/fs.d.ts +105 -0
  75. package/dist/utils/fs.d.ts.map +1 -0
  76. package/dist/utils/fs.js +233 -0
  77. package/dist/utils/fs.js.map +1 -0
  78. package/dist/utils/logger.d.ts +102 -25
  79. package/dist/utils/logger.d.ts.map +1 -0
  80. package/dist/utils/logger.js +214 -57
  81. package/dist/utils/logger.js.map +1 -0
  82. package/dist/utils/version.d.ts +60 -2
  83. package/dist/utils/version.d.ts.map +1 -0
  84. package/dist/utils/version.js +255 -31
  85. package/dist/utils/version.js.map +1 -0
  86. package/package.json +16 -58
  87. package/src/config.ts +510 -0
  88. package/src/constants.ts +36 -0
  89. package/src/execution/command.ts +314 -0
  90. package/src/execution/index.ts +73 -0
  91. package/src/execution/runtime.ts +308 -0
  92. package/src/execution/types.ts +379 -0
  93. package/src/execution/validation.ts +508 -0
  94. package/src/index.ts +238 -30
  95. package/src/manifest/index.ts +36 -0
  96. package/src/manifest/loader.ts +187 -0
  97. package/src/manifest/parser.ts +173 -0
  98. package/src/manifest/validator.ts +309 -0
  99. package/src/paths.ts +108 -0
  100. package/src/registry.ts +219 -0
  101. package/src/resolver.ts +345 -0
  102. package/src/types/index.ts +30 -0
  103. package/src/types/manifest.ts +255 -0
  104. package/src/types.ts +5 -188
  105. package/src/utils/fs.ts +281 -0
  106. package/src/utils/logger.ts +270 -59
  107. package/src/utils/version.ts +304 -36
  108. package/tests/config.test.ts +515 -0
  109. package/tests/execution/command.test.ts +317 -0
  110. package/tests/execution/validation.test.ts +384 -0
  111. package/tests/fixtures/invalid-tool.yaml +4 -0
  112. package/tests/fixtures/valid-tool.md +62 -0
  113. package/tests/fixtures/valid-tool.yaml +40 -0
  114. package/tests/index.test.ts +8 -0
  115. package/tests/manifest/loader.test.ts +291 -0
  116. package/tests/manifest/parser.test.ts +345 -0
  117. package/tests/manifest/validator.test.ts +394 -0
  118. package/tests/manifest-types.test.ts +358 -0
  119. package/tests/paths.test.ts +153 -0
  120. package/tests/registry.test.ts +231 -0
  121. package/tests/resolver.test.ts +272 -0
  122. package/tests/utils/fs.test.ts +388 -0
  123. package/tests/utils/logger.test.ts +480 -0
  124. package/tests/utils/version.test.ts +390 -0
  125. package/tsconfig.json +12 -0
  126. package/dist/LocalToolResolver.d.ts +0 -84
  127. package/dist/LocalToolResolver.js +0 -353
  128. package/dist/api/enact-api.d.ts +0 -130
  129. package/dist/api/enact-api.js +0 -428
  130. package/dist/api/index.d.ts +0 -2
  131. package/dist/api/index.js +0 -2
  132. package/dist/api/types.d.ts +0 -103
  133. package/dist/api/types.js +0 -1
  134. package/dist/core/DaggerExecutionProvider.d.ts +0 -169
  135. package/dist/core/DaggerExecutionProvider.js +0 -1029
  136. package/dist/core/DirectExecutionProvider.d.ts +0 -23
  137. package/dist/core/DirectExecutionProvider.js +0 -406
  138. package/dist/core/EnactCore.d.ts +0 -162
  139. package/dist/core/EnactCore.js +0 -597
  140. package/dist/core/NativeExecutionProvider.d.ts +0 -9
  141. package/dist/core/NativeExecutionProvider.js +0 -16
  142. package/dist/core/index.d.ts +0 -3
  143. package/dist/core/index.js +0 -3
  144. package/dist/exec/index.d.ts +0 -3
  145. package/dist/exec/index.js +0 -3
  146. package/dist/exec/logger.d.ts +0 -11
  147. package/dist/exec/logger.js +0 -57
  148. package/dist/exec/validate.d.ts +0 -5
  149. package/dist/exec/validate.js +0 -167
  150. package/dist/lib/enact-direct.d.ts +0 -150
  151. package/dist/lib/enact-direct.js +0 -159
  152. package/dist/lib/index.d.ts +0 -1
  153. package/dist/lib/index.js +0 -1
  154. package/dist/security/index.d.ts +0 -3
  155. package/dist/security/index.js +0 -3
  156. package/dist/security/security.d.ts +0 -23
  157. package/dist/security/security.js +0 -137
  158. package/dist/security/sign.d.ts +0 -103
  159. package/dist/security/sign.js +0 -666
  160. package/dist/security/verification-enforcer.d.ts +0 -53
  161. package/dist/security/verification-enforcer.js +0 -204
  162. package/dist/services/McpCoreService.d.ts +0 -98
  163. package/dist/services/McpCoreService.js +0 -124
  164. package/dist/services/index.d.ts +0 -1
  165. package/dist/services/index.js +0 -1
  166. package/dist/utils/config.d.ts +0 -111
  167. package/dist/utils/config.js +0 -342
  168. package/dist/utils/env-loader.d.ts +0 -54
  169. package/dist/utils/env-loader.js +0 -270
  170. package/dist/utils/help.d.ts +0 -36
  171. package/dist/utils/help.js +0 -248
  172. package/dist/utils/index.d.ts +0 -7
  173. package/dist/utils/index.js +0 -7
  174. package/dist/utils/silent-monitor.d.ts +0 -67
  175. package/dist/utils/silent-monitor.js +0 -242
  176. package/dist/utils/timeout.d.ts +0 -5
  177. package/dist/utils/timeout.js +0 -23
  178. package/dist/web/env-manager-server.d.ts +0 -29
  179. package/dist/web/env-manager-server.js +0 -367
  180. package/dist/web/index.d.ts +0 -1
  181. package/dist/web/index.js +0 -1
  182. package/src/LocalToolResolver.ts +0 -424
  183. package/src/api/enact-api.ts +0 -604
  184. package/src/api/index.ts +0 -2
  185. package/src/api/types.ts +0 -114
  186. package/src/core/DaggerExecutionProvider.ts +0 -1357
  187. package/src/core/DirectExecutionProvider.ts +0 -484
  188. package/src/core/EnactCore.ts +0 -847
  189. package/src/core/index.ts +0 -3
  190. package/src/exec/index.ts +0 -3
  191. package/src/exec/logger.ts +0 -63
  192. package/src/exec/validate.ts +0 -238
  193. package/src/lib/enact-direct.ts +0 -254
  194. package/src/lib/index.ts +0 -1
  195. package/src/services/McpCoreService.ts +0 -201
  196. package/src/services/index.ts +0 -1
  197. package/src/utils/config.ts +0 -438
  198. package/src/utils/env-loader.ts +0 -370
  199. package/src/utils/help.ts +0 -257
  200. package/src/utils/index.ts +0 -7
  201. package/src/utils/silent-monitor.ts +0 -328
  202. package/src/utils/timeout.ts +0 -26
  203. package/src/web/env-manager-server.ts +0 -465
  204. package/src/web/index.ts +0 -1
  205. package/src/web/static/app.js +0 -663
  206. package/src/web/static/index.html +0 -117
  207. package/src/web/static/style.css +0 -291
package/src/index.ts CHANGED
@@ -1,39 +1,247 @@
1
- // Core exports
2
- export { EnactCore } from './core/EnactCore';
3
- export { DirectExecutionProvider } from './core/DirectExecutionProvider';
4
- export { DaggerExecutionProvider } from './core/DaggerExecutionProvider';
1
+ /**
2
+ * @enactprotocol/shared
3
+ *
4
+ * Core business logic and utilities for Enact.
5
+ * Provides manifest parsing, configuration management, tool resolution,
6
+ * and execution engine interfaces.
7
+ */
5
8
 
6
- // Constants - now handled in config utils
9
+ export const version = "0.1.0";
7
10
 
8
- // Types and utilities
9
- export type { EnactTool } from './types';
10
- export type { EnactToolDefinition } from './api/types';
11
- export { default as LocalToolResolver } from './LocalToolResolver';
12
- export { default } from './LocalToolResolver';
11
+ // Constants
12
+ export {
13
+ ENACT_BASE_URL,
14
+ ENACT_API_URL,
15
+ ENACT_WEB_URL,
16
+ ENACT_TOOL_TYPE,
17
+ ENACT_AUDIT_TYPE,
18
+ ENACT_BUILD_TYPE,
19
+ INTOTO_STATEMENT_TYPE,
20
+ SLSA_PROVENANCE_TYPE,
21
+ } from "./constants";
13
22
 
14
- // Exec utilities
15
- export { default as logger } from './exec/logger';
16
- export * from './exec/validate';
23
+ // Path utilities
24
+ export {
25
+ getEnactHome,
26
+ getProjectEnactDir,
27
+ getToolsDir,
28
+ getCacheDir,
29
+ getConfigPath,
30
+ getGlobalEnvPath,
31
+ getProjectEnvPath,
32
+ type ToolScope,
33
+ } from "./paths";
17
34
 
18
- // Utils
19
- export * from './utils/config';
20
- export * from './utils/env-loader';
21
- export { showHelp } from './utils/help';
22
- export { showVersion as utilsShowVersion } from './utils/version';
23
- export * from './utils/logger';
24
- export * from './utils/silent-monitor';
25
- export * from './utils/timeout';
35
+ // Configuration manager
36
+ export {
37
+ loadConfig,
38
+ saveConfig,
39
+ getConfigValue,
40
+ setConfigValue,
41
+ resetConfig,
42
+ configExists,
43
+ ensureGlobalSetup,
44
+ DEFAULT_CONFIG,
45
+ // Local trust management (new unified API)
46
+ getTrustedIdentities,
47
+ addTrustedIdentity,
48
+ removeTrustedIdentity,
49
+ isIdentityTrusted,
50
+ getMinimumAttestations,
51
+ getTrustPolicy,
52
+ emailToProviderIdentity,
53
+ // Legacy aliases (deprecated)
54
+ getTrustedAuditors,
55
+ addTrustedAuditor,
56
+ removeTrustedAuditor,
57
+ isAuditorTrusted,
58
+ type EnactConfig,
59
+ type TrustConfig,
60
+ type CacheConfig,
61
+ type ExecutionConfig,
62
+ type RegistryConfig,
63
+ } from "./config";
26
64
 
65
+ // Manifest types
66
+ export type {
67
+ ToolManifest,
68
+ PackageManifest,
69
+ ParsedManifest,
70
+ EnvVariable,
71
+ EnvVariables,
72
+ Author,
73
+ ToolAnnotations,
74
+ ResourceRequirements,
75
+ ToolExample,
76
+ ValidationResult,
77
+ ValidationError,
78
+ ValidationWarning,
79
+ ToolLocation,
80
+ ToolResolution,
81
+ ManifestFileName,
82
+ } from "./types/manifest";
27
83
 
28
- // Services
29
- export * from './services/McpCoreService';
84
+ export { MANIFEST_FILES, PACKAGE_MANIFEST_FILE } from "./types/manifest";
30
85
 
31
- // Web
32
- export * from './web/env-manager-server';
86
+ // Manifest parsing, validation, and loading
87
+ export {
88
+ // Parser
89
+ ManifestParseError,
90
+ parseManifest,
91
+ parseManifestAuto,
92
+ parseYaml,
93
+ extractFrontmatter,
94
+ detectFormat,
95
+ type ManifestFormat,
96
+ // Validator
97
+ validateManifest,
98
+ validateManifestStrict,
99
+ isValidToolName,
100
+ isValidVersion,
101
+ isValidTimeout,
102
+ ToolManifestSchema,
103
+ // Loader
104
+ ManifestLoadError,
105
+ loadManifest,
106
+ loadManifestFromDir,
107
+ findManifestFile,
108
+ hasManifest,
109
+ tryLoadManifest,
110
+ tryLoadManifestFromDir,
111
+ type LoadedManifest,
112
+ } from "./manifest";
33
113
 
34
- // API
35
- export * from './api/enact-api';
36
- export type { ApiResponse, ToolSearchQuery, ToolUsage, CLITokenCreate, OAuthTokenExchange } from './api/types';
114
+ // Tool resolver
115
+ export {
116
+ ToolResolveError,
117
+ resolveTool,
118
+ resolveToolAuto,
119
+ resolveToolFromPath,
120
+ tryResolveTool,
121
+ normalizeToolName,
122
+ toolNameToPath,
123
+ getToolPath,
124
+ getToolSearchPaths,
125
+ type ResolveOptions,
126
+ } from "./resolver";
37
127
 
38
- // Lib
39
- export * from './lib/enact-direct';
128
+ // Local tool registry (tools.json management)
129
+ export {
130
+ loadToolsRegistry,
131
+ saveToolsRegistry,
132
+ addToolToRegistry,
133
+ removeToolFromRegistry,
134
+ isToolInstalled,
135
+ getInstalledVersion,
136
+ getToolCachePath,
137
+ listInstalledTools,
138
+ getInstalledToolInfo,
139
+ getToolsJsonPath,
140
+ type ToolsRegistry,
141
+ type RegistryScope,
142
+ type InstalledToolInfo,
143
+ } from "./registry";
144
+
145
+ // Logger utility
146
+ export {
147
+ Logger,
148
+ createLogger,
149
+ configureLogger,
150
+ getLogger,
151
+ debug,
152
+ info,
153
+ warn,
154
+ error,
155
+ type LogLevel,
156
+ type LogEntry,
157
+ type LoggerOptions,
158
+ } from "./utils/logger";
159
+
160
+ // Version utilities
161
+ export {
162
+ parseVersion,
163
+ isValidVersion as isValidSemver,
164
+ compareVersions,
165
+ parseRange,
166
+ satisfiesRange,
167
+ sortVersions,
168
+ getHighestVersion,
169
+ incrementVersion,
170
+ coerceVersion,
171
+ formatVersion,
172
+ type ParsedVersion,
173
+ type VersionRange,
174
+ } from "./utils/version";
175
+
176
+ // File system helpers
177
+ export {
178
+ ensureDir,
179
+ ensureParentDir,
180
+ pathExists,
181
+ isDirectory,
182
+ isFile,
183
+ readJsonFile,
184
+ tryReadJsonFile,
185
+ writeJsonFile,
186
+ readTextFile,
187
+ tryReadTextFile,
188
+ writeTextFile,
189
+ copyFile,
190
+ copyDir,
191
+ remove,
192
+ listDir,
193
+ listDirEntries,
194
+ findFiles,
195
+ findFilesRecursive,
196
+ getStats,
197
+ getFileSize,
198
+ touchFile,
199
+ } from "./utils/fs";
200
+
201
+ // Execution engine (browser-safe utilities only)
202
+ // NOTE: DaggerExecutionProvider moved to @enactprotocol/execution package
203
+ export {
204
+ // Types
205
+ type ExecutionInput,
206
+ type FileInput,
207
+ type ExecutionOutput,
208
+ type ExecutionResult,
209
+ type ExecutionMetadata,
210
+ type ExecutionError,
211
+ type ExecutionErrorCode,
212
+ type ExecutionOptions,
213
+ type RetryConfig,
214
+ type ContainerRuntime,
215
+ type RuntimeDetection,
216
+ type RuntimeStatus,
217
+ type EngineHealth,
218
+ type EngineState,
219
+ type ExecutionProvider,
220
+ type ParsedCommand,
221
+ type CommandToken,
222
+ type InterpolationOptions,
223
+ type InputValidationResult,
224
+ type InputValidationError,
225
+ type DryRunResult,
226
+ // Constants
227
+ DEFAULT_RETRY_CONFIG,
228
+ // Runtime
229
+ detectRuntime,
230
+ clearRuntimeCache,
231
+ isRuntimeAvailable,
232
+ getAvailableRuntimes,
233
+ RuntimeStatusTracker,
234
+ createRuntimeTracker,
235
+ // Command
236
+ parseCommand,
237
+ interpolateCommand,
238
+ shellEscape,
239
+ parseCommandArgs,
240
+ prepareCommand,
241
+ getMissingParams,
242
+ // Validation
243
+ validateInputs,
244
+ applyDefaults,
245
+ getRequiredParams,
246
+ getParamInfo,
247
+ } from "./execution";
@@ -0,0 +1,36 @@
1
+ /**
2
+ * Manifest module exports
3
+ */
4
+
5
+ // Parser
6
+ export {
7
+ ManifestParseError,
8
+ parseManifest,
9
+ parseManifestAuto,
10
+ parseYaml,
11
+ extractFrontmatter,
12
+ detectFormat,
13
+ type ManifestFormat,
14
+ } from "./parser";
15
+
16
+ // Validator
17
+ export {
18
+ validateManifest,
19
+ validateManifestStrict,
20
+ isValidToolName,
21
+ isValidVersion,
22
+ isValidTimeout,
23
+ ToolManifestSchema,
24
+ } from "./validator";
25
+
26
+ // Loader
27
+ export {
28
+ ManifestLoadError,
29
+ loadManifest,
30
+ loadManifestFromDir,
31
+ findManifestFile,
32
+ hasManifest,
33
+ tryLoadManifest,
34
+ tryLoadManifestFromDir,
35
+ type LoadedManifest,
36
+ } from "./loader";
@@ -0,0 +1,187 @@
1
+ /**
2
+ * Manifest loader - combines parsing and validation
3
+ *
4
+ * Provides high-level functions to load tool manifests from files
5
+ */
6
+
7
+ import { existsSync, readFileSync } from "node:fs";
8
+ import { basename, join } from "node:path";
9
+ import type { ParsedManifest, ToolManifest, ValidationResult } from "../types/manifest";
10
+ import { MANIFEST_FILES } from "../types/manifest";
11
+ import { ManifestParseError, parseManifestAuto } from "./parser";
12
+ import { validateManifest } from "./validator";
13
+
14
+ /**
15
+ * Error thrown when loading a manifest fails
16
+ */
17
+ export class ManifestLoadError extends Error {
18
+ constructor(
19
+ message: string,
20
+ public readonly filePath: string,
21
+ public readonly originalError?: Error
22
+ ) {
23
+ super(message);
24
+ this.name = "ManifestLoadError";
25
+ }
26
+ }
27
+
28
+ /**
29
+ * Result of loading a manifest
30
+ */
31
+ export interface LoadedManifest {
32
+ /** The validated manifest */
33
+ manifest: ToolManifest;
34
+ /** The markdown body (if from .md file) */
35
+ body?: string;
36
+ /** The format the manifest was loaded from */
37
+ format: "yaml" | "md";
38
+ /** The file path the manifest was loaded from */
39
+ filePath: string;
40
+ /** Validation warnings (if any) */
41
+ warnings?: ValidationResult["warnings"];
42
+ }
43
+
44
+ /**
45
+ * Load a manifest from a file path
46
+ *
47
+ * @param filePath - Path to the manifest file (enact.yaml, enact.yml, or enact.md)
48
+ * @returns LoadedManifest with validated manifest and metadata
49
+ * @throws ManifestLoadError if file doesn't exist, parse fails, or validation fails
50
+ */
51
+ export function loadManifest(filePath: string): LoadedManifest {
52
+ // Check file exists
53
+ if (!existsSync(filePath)) {
54
+ throw new ManifestLoadError(`Manifest file not found: ${filePath}`, filePath);
55
+ }
56
+
57
+ // Read file content
58
+ let content: string;
59
+ try {
60
+ content = readFileSync(filePath, "utf-8");
61
+ } catch (error) {
62
+ throw new ManifestLoadError(
63
+ `Failed to read manifest file: ${(error as Error).message}`,
64
+ filePath,
65
+ error as Error
66
+ );
67
+ }
68
+
69
+ // Parse the manifest
70
+ let parsed: ParsedManifest;
71
+ try {
72
+ parsed = parseManifestAuto(content, basename(filePath));
73
+ } catch (error) {
74
+ if (error instanceof ManifestParseError) {
75
+ throw new ManifestLoadError(`Failed to parse manifest: ${error.message}`, filePath, error);
76
+ }
77
+ throw new ManifestLoadError(
78
+ `Failed to parse manifest: ${(error as Error).message}`,
79
+ filePath,
80
+ error as Error
81
+ );
82
+ }
83
+
84
+ // Validate the manifest
85
+ const validation = validateManifest(parsed.manifest);
86
+
87
+ if (!validation.valid) {
88
+ const errorMessages =
89
+ validation.errors?.map((e) => ` - ${e.path}: ${e.message}`).join("\n") ?? "";
90
+ throw new ManifestLoadError(`Manifest validation failed:\n${errorMessages}`, filePath);
91
+ }
92
+
93
+ // Build result
94
+ const result: LoadedManifest = {
95
+ manifest: parsed.manifest,
96
+ format: parsed.format,
97
+ filePath,
98
+ };
99
+
100
+ if (parsed.body) {
101
+ result.body = parsed.body;
102
+ }
103
+
104
+ if (validation.warnings && validation.warnings.length > 0) {
105
+ result.warnings = validation.warnings;
106
+ }
107
+
108
+ return result;
109
+ }
110
+
111
+ /**
112
+ * Find and load a manifest from a directory
113
+ *
114
+ * Searches for enact.md, enact.yaml, or enact.yml in the given directory
115
+ *
116
+ * @param dir - Directory to search for manifest
117
+ * @returns LoadedManifest if found
118
+ * @throws ManifestLoadError if no manifest found or loading fails
119
+ */
120
+ export function loadManifestFromDir(dir: string): LoadedManifest {
121
+ // Try each manifest filename in order of preference
122
+ for (const filename of MANIFEST_FILES) {
123
+ const filePath = join(dir, filename);
124
+ if (existsSync(filePath)) {
125
+ return loadManifest(filePath);
126
+ }
127
+ }
128
+
129
+ throw new ManifestLoadError(
130
+ `No manifest found in directory: ${dir}. Expected one of: ${MANIFEST_FILES.join(", ")}`,
131
+ dir
132
+ );
133
+ }
134
+
135
+ /**
136
+ * Find a manifest file in a directory without loading it
137
+ *
138
+ * @param dir - Directory to search
139
+ * @returns Path to manifest file or null if not found
140
+ */
141
+ export function findManifestFile(dir: string): string | null {
142
+ for (const filename of MANIFEST_FILES) {
143
+ const filePath = join(dir, filename);
144
+ if (existsSync(filePath)) {
145
+ return filePath;
146
+ }
147
+ }
148
+ return null;
149
+ }
150
+
151
+ /**
152
+ * Check if a directory contains a manifest file
153
+ *
154
+ * @param dir - Directory to check
155
+ * @returns true if a manifest file exists
156
+ */
157
+ export function hasManifest(dir: string): boolean {
158
+ return findManifestFile(dir) !== null;
159
+ }
160
+
161
+ /**
162
+ * Try to load a manifest, returning null instead of throwing
163
+ *
164
+ * @param filePath - Path to the manifest file
165
+ * @returns LoadedManifest or null if loading fails
166
+ */
167
+ export function tryLoadManifest(filePath: string): LoadedManifest | null {
168
+ try {
169
+ return loadManifest(filePath);
170
+ } catch {
171
+ return null;
172
+ }
173
+ }
174
+
175
+ /**
176
+ * Try to load a manifest from a directory, returning null instead of throwing
177
+ *
178
+ * @param dir - Directory to search
179
+ * @returns LoadedManifest or null if no manifest found or loading fails
180
+ */
181
+ export function tryLoadManifestFromDir(dir: string): LoadedManifest | null {
182
+ try {
183
+ return loadManifestFromDir(dir);
184
+ } catch {
185
+ return null;
186
+ }
187
+ }
@@ -0,0 +1,173 @@
1
+ /**
2
+ * YAML and Markdown parser for Enact tool manifests
3
+ *
4
+ * Handles parsing of:
5
+ * - enact.yaml files (pure YAML)
6
+ * - enact.md files (YAML frontmatter + Markdown body)
7
+ */
8
+
9
+ import yaml from "js-yaml";
10
+ import type { ParsedManifest, ToolManifest } from "../types/manifest";
11
+
12
+ /**
13
+ * Error thrown when parsing fails
14
+ */
15
+ export class ManifestParseError extends Error {
16
+ public readonly originalError: Error | undefined;
17
+
18
+ constructor(message: string, originalError?: Error) {
19
+ super(message);
20
+ this.name = "ManifestParseError";
21
+ this.originalError = originalError;
22
+ }
23
+ }
24
+
25
+ /**
26
+ * Regex to match YAML frontmatter in Markdown files
27
+ * Matches content between --- delimiters at the start of the file
28
+ */
29
+ const FRONTMATTER_REGEX = /^---\r?\n([\s\S]*?)\r?\n---\r?\n?([\s\S]*)$/;
30
+
31
+ /**
32
+ * Parse format type
33
+ */
34
+ export type ManifestFormat = "yaml" | "md";
35
+
36
+ /**
37
+ * Extract YAML frontmatter from Markdown content
38
+ *
39
+ * @param content - The full Markdown file content
40
+ * @returns Object with frontmatter YAML and body Markdown, or null if no frontmatter
41
+ */
42
+ export function extractFrontmatter(content: string): {
43
+ frontmatter: string;
44
+ body: string;
45
+ } | null {
46
+ const match = content.match(FRONTMATTER_REGEX);
47
+
48
+ if (!match) {
49
+ return null;
50
+ }
51
+
52
+ const frontmatter = match[1];
53
+ const body = match[2];
54
+
55
+ return {
56
+ frontmatter: frontmatter ? frontmatter.trim() : "",
57
+ body: body ? body.trim() : "",
58
+ };
59
+ }
60
+
61
+ /**
62
+ * Parse YAML content into a ToolManifest object
63
+ *
64
+ * @param yamlContent - Raw YAML string
65
+ * @returns Parsed object (not yet validated)
66
+ * @throws ManifestParseError if YAML parsing fails
67
+ */
68
+ export function parseYaml(yamlContent: string): Record<string, unknown> {
69
+ try {
70
+ const parsed = yaml.load(yamlContent);
71
+
72
+ if (parsed === null || parsed === undefined) {
73
+ throw new ManifestParseError("YAML content is empty or null");
74
+ }
75
+
76
+ if (typeof parsed !== "object" || Array.isArray(parsed)) {
77
+ throw new ManifestParseError("YAML content must be an object, not an array or primitive");
78
+ }
79
+
80
+ return parsed as Record<string, unknown>;
81
+ } catch (error) {
82
+ if (error instanceof ManifestParseError) {
83
+ throw error;
84
+ }
85
+
86
+ const yamlError = error as Error;
87
+ throw new ManifestParseError(`Failed to parse YAML: ${yamlError.message}`, yamlError);
88
+ }
89
+ }
90
+
91
+ /**
92
+ * Parse a manifest from content string
93
+ *
94
+ * @param content - The file content (YAML or Markdown with frontmatter)
95
+ * @param format - The format of the content ('yaml' or 'md')
96
+ * @returns ParsedManifest with manifest object and optional body
97
+ * @throws ManifestParseError if parsing fails
98
+ */
99
+ export function parseManifest(content: string, format: ManifestFormat): ParsedManifest {
100
+ if (!content || content.trim() === "") {
101
+ throw new ManifestParseError("Manifest content is empty");
102
+ }
103
+
104
+ if (format === "yaml") {
105
+ const parsed = parseYaml(content);
106
+ return {
107
+ manifest: parsed as unknown as ToolManifest,
108
+ format: "yaml",
109
+ };
110
+ }
111
+
112
+ // Handle Markdown format
113
+ const extracted = extractFrontmatter(content);
114
+
115
+ if (!extracted) {
116
+ throw new ManifestParseError(
117
+ "Markdown file must contain YAML frontmatter between --- delimiters"
118
+ );
119
+ }
120
+
121
+ if (!extracted.frontmatter) {
122
+ throw new ManifestParseError("YAML frontmatter is empty");
123
+ }
124
+
125
+ const parsed = parseYaml(extracted.frontmatter);
126
+
127
+ const result: ParsedManifest = {
128
+ manifest: parsed as unknown as ToolManifest,
129
+ format: "md",
130
+ };
131
+
132
+ if (extracted.body) {
133
+ result.body = extracted.body;
134
+ }
135
+
136
+ return result;
137
+ }
138
+
139
+ /**
140
+ * Detect manifest format from filename
141
+ *
142
+ * @param filename - The manifest filename
143
+ * @returns The detected format
144
+ * @throws ManifestParseError if format cannot be detected
145
+ */
146
+ export function detectFormat(filename: string): ManifestFormat {
147
+ const lower = filename.toLowerCase();
148
+
149
+ if (lower.endsWith(".yaml") || lower.endsWith(".yml")) {
150
+ return "yaml";
151
+ }
152
+
153
+ if (lower.endsWith(".md")) {
154
+ return "md";
155
+ }
156
+
157
+ throw new ManifestParseError(
158
+ `Cannot detect manifest format from filename: ${filename}. Expected .yaml, .yml, or .md extension.`
159
+ );
160
+ }
161
+
162
+ /**
163
+ * Parse manifest content with automatic format detection
164
+ *
165
+ * @param content - The file content
166
+ * @param filename - The filename (for format detection)
167
+ * @returns ParsedManifest
168
+ * @throws ManifestParseError if parsing fails
169
+ */
170
+ export function parseManifestAuto(content: string, filename: string): ParsedManifest {
171
+ const format = detectFormat(filename);
172
+ return parseManifest(content, format);
173
+ }