@enactprotocol/shared 1.2.11 → 2.0.0

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 (134) hide show
  1. package/README.md +44 -0
  2. package/package.json +16 -58
  3. package/src/config.ts +476 -0
  4. package/src/constants.ts +36 -0
  5. package/src/execution/command.ts +314 -0
  6. package/src/execution/index.ts +73 -0
  7. package/src/execution/runtime.ts +308 -0
  8. package/src/execution/types.ts +379 -0
  9. package/src/execution/validation.ts +508 -0
  10. package/src/index.ts +237 -30
  11. package/src/manifest/index.ts +36 -0
  12. package/src/manifest/loader.ts +187 -0
  13. package/src/manifest/parser.ts +173 -0
  14. package/src/manifest/validator.ts +309 -0
  15. package/src/paths.ts +108 -0
  16. package/src/registry.ts +219 -0
  17. package/src/resolver.ts +345 -0
  18. package/src/types/index.ts +30 -0
  19. package/src/types/manifest.ts +255 -0
  20. package/src/types.ts +5 -188
  21. package/src/utils/fs.ts +281 -0
  22. package/src/utils/logger.ts +270 -59
  23. package/src/utils/version.ts +304 -36
  24. package/tests/config.test.ts +515 -0
  25. package/tests/execution/command.test.ts +317 -0
  26. package/tests/execution/validation.test.ts +384 -0
  27. package/tests/fixtures/invalid-tool.yaml +4 -0
  28. package/tests/fixtures/valid-tool.md +62 -0
  29. package/tests/fixtures/valid-tool.yaml +40 -0
  30. package/tests/index.test.ts +8 -0
  31. package/tests/manifest/loader.test.ts +291 -0
  32. package/tests/manifest/parser.test.ts +345 -0
  33. package/tests/manifest/validator.test.ts +394 -0
  34. package/tests/manifest-types.test.ts +358 -0
  35. package/tests/paths.test.ts +153 -0
  36. package/tests/registry.test.ts +231 -0
  37. package/tests/resolver.test.ts +272 -0
  38. package/tests/utils/fs.test.ts +388 -0
  39. package/tests/utils/logger.test.ts +480 -0
  40. package/tests/utils/version.test.ts +390 -0
  41. package/tsconfig.json +12 -0
  42. package/tsconfig.tsbuildinfo +1 -0
  43. package/dist/LocalToolResolver.d.ts +0 -84
  44. package/dist/LocalToolResolver.js +0 -353
  45. package/dist/api/enact-api.d.ts +0 -130
  46. package/dist/api/enact-api.js +0 -428
  47. package/dist/api/index.d.ts +0 -2
  48. package/dist/api/index.js +0 -2
  49. package/dist/api/types.d.ts +0 -103
  50. package/dist/api/types.js +0 -1
  51. package/dist/constants.d.ts +0 -7
  52. package/dist/constants.js +0 -10
  53. package/dist/core/DaggerExecutionProvider.d.ts +0 -169
  54. package/dist/core/DaggerExecutionProvider.js +0 -1029
  55. package/dist/core/DirectExecutionProvider.d.ts +0 -23
  56. package/dist/core/DirectExecutionProvider.js +0 -406
  57. package/dist/core/EnactCore.d.ts +0 -162
  58. package/dist/core/EnactCore.js +0 -597
  59. package/dist/core/NativeExecutionProvider.d.ts +0 -9
  60. package/dist/core/NativeExecutionProvider.js +0 -16
  61. package/dist/core/index.d.ts +0 -3
  62. package/dist/core/index.js +0 -3
  63. package/dist/exec/index.d.ts +0 -3
  64. package/dist/exec/index.js +0 -3
  65. package/dist/exec/logger.d.ts +0 -11
  66. package/dist/exec/logger.js +0 -57
  67. package/dist/exec/validate.d.ts +0 -5
  68. package/dist/exec/validate.js +0 -167
  69. package/dist/index.d.ts +0 -21
  70. package/dist/index.js +0 -25
  71. package/dist/lib/enact-direct.d.ts +0 -150
  72. package/dist/lib/enact-direct.js +0 -159
  73. package/dist/lib/index.d.ts +0 -1
  74. package/dist/lib/index.js +0 -1
  75. package/dist/security/index.d.ts +0 -3
  76. package/dist/security/index.js +0 -3
  77. package/dist/security/security.d.ts +0 -23
  78. package/dist/security/security.js +0 -137
  79. package/dist/security/sign.d.ts +0 -103
  80. package/dist/security/sign.js +0 -666
  81. package/dist/security/verification-enforcer.d.ts +0 -53
  82. package/dist/security/verification-enforcer.js +0 -204
  83. package/dist/services/McpCoreService.d.ts +0 -98
  84. package/dist/services/McpCoreService.js +0 -124
  85. package/dist/services/index.d.ts +0 -1
  86. package/dist/services/index.js +0 -1
  87. package/dist/types.d.ts +0 -132
  88. package/dist/types.js +0 -3
  89. package/dist/utils/config.d.ts +0 -111
  90. package/dist/utils/config.js +0 -342
  91. package/dist/utils/env-loader.d.ts +0 -54
  92. package/dist/utils/env-loader.js +0 -270
  93. package/dist/utils/help.d.ts +0 -36
  94. package/dist/utils/help.js +0 -248
  95. package/dist/utils/index.d.ts +0 -7
  96. package/dist/utils/index.js +0 -7
  97. package/dist/utils/logger.d.ts +0 -35
  98. package/dist/utils/logger.js +0 -75
  99. package/dist/utils/silent-monitor.d.ts +0 -67
  100. package/dist/utils/silent-monitor.js +0 -242
  101. package/dist/utils/timeout.d.ts +0 -5
  102. package/dist/utils/timeout.js +0 -23
  103. package/dist/utils/version.d.ts +0 -4
  104. package/dist/utils/version.js +0 -35
  105. package/dist/web/env-manager-server.d.ts +0 -29
  106. package/dist/web/env-manager-server.js +0 -367
  107. package/dist/web/index.d.ts +0 -1
  108. package/dist/web/index.js +0 -1
  109. package/src/LocalToolResolver.ts +0 -424
  110. package/src/api/enact-api.ts +0 -604
  111. package/src/api/index.ts +0 -2
  112. package/src/api/types.ts +0 -114
  113. package/src/core/DaggerExecutionProvider.ts +0 -1357
  114. package/src/core/DirectExecutionProvider.ts +0 -484
  115. package/src/core/EnactCore.ts +0 -847
  116. package/src/core/index.ts +0 -3
  117. package/src/exec/index.ts +0 -3
  118. package/src/exec/logger.ts +0 -63
  119. package/src/exec/validate.ts +0 -238
  120. package/src/lib/enact-direct.ts +0 -254
  121. package/src/lib/index.ts +0 -1
  122. package/src/services/McpCoreService.ts +0 -201
  123. package/src/services/index.ts +0 -1
  124. package/src/utils/config.ts +0 -438
  125. package/src/utils/env-loader.ts +0 -370
  126. package/src/utils/help.ts +0 -257
  127. package/src/utils/index.ts +0 -7
  128. package/src/utils/silent-monitor.ts +0 -328
  129. package/src/utils/timeout.ts +0 -26
  130. package/src/web/env-manager-server.ts +0 -465
  131. package/src/web/index.ts +0 -1
  132. package/src/web/static/app.js +0 -663
  133. package/src/web/static/index.html +0 -117
  134. package/src/web/static/style.css +0 -291
package/src/index.ts CHANGED
@@ -1,39 +1,246 @@
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
+ DEFAULT_CONFIG,
44
+ // Local trust management (new unified API)
45
+ getTrustedIdentities,
46
+ addTrustedIdentity,
47
+ removeTrustedIdentity,
48
+ isIdentityTrusted,
49
+ getMinimumAttestations,
50
+ getTrustPolicy,
51
+ emailToProviderIdentity,
52
+ // Legacy aliases (deprecated)
53
+ getTrustedAuditors,
54
+ addTrustedAuditor,
55
+ removeTrustedAuditor,
56
+ isAuditorTrusted,
57
+ type EnactConfig,
58
+ type TrustConfig,
59
+ type CacheConfig,
60
+ type ExecutionConfig,
61
+ type RegistryConfig,
62
+ } from "./config";
26
63
 
64
+ // Manifest types
65
+ export type {
66
+ ToolManifest,
67
+ PackageManifest,
68
+ ParsedManifest,
69
+ EnvVariable,
70
+ EnvVariables,
71
+ Author,
72
+ ToolAnnotations,
73
+ ResourceRequirements,
74
+ ToolExample,
75
+ ValidationResult,
76
+ ValidationError,
77
+ ValidationWarning,
78
+ ToolLocation,
79
+ ToolResolution,
80
+ ManifestFileName,
81
+ } from "./types/manifest";
27
82
 
28
- // Services
29
- export * from './services/McpCoreService';
83
+ export { MANIFEST_FILES, PACKAGE_MANIFEST_FILE } from "./types/manifest";
30
84
 
31
- // Web
32
- export * from './web/env-manager-server';
85
+ // Manifest parsing, validation, and loading
86
+ export {
87
+ // Parser
88
+ ManifestParseError,
89
+ parseManifest,
90
+ parseManifestAuto,
91
+ parseYaml,
92
+ extractFrontmatter,
93
+ detectFormat,
94
+ type ManifestFormat,
95
+ // Validator
96
+ validateManifest,
97
+ validateManifestStrict,
98
+ isValidToolName,
99
+ isValidVersion,
100
+ isValidTimeout,
101
+ ToolManifestSchema,
102
+ // Loader
103
+ ManifestLoadError,
104
+ loadManifest,
105
+ loadManifestFromDir,
106
+ findManifestFile,
107
+ hasManifest,
108
+ tryLoadManifest,
109
+ tryLoadManifestFromDir,
110
+ type LoadedManifest,
111
+ } from "./manifest";
33
112
 
34
- // API
35
- export * from './api/enact-api';
36
- export type { ApiResponse, ToolSearchQuery, ToolUsage, CLITokenCreate, OAuthTokenExchange } from './api/types';
113
+ // Tool resolver
114
+ export {
115
+ ToolResolveError,
116
+ resolveTool,
117
+ resolveToolAuto,
118
+ resolveToolFromPath,
119
+ tryResolveTool,
120
+ normalizeToolName,
121
+ toolNameToPath,
122
+ getToolPath,
123
+ getToolSearchPaths,
124
+ type ResolveOptions,
125
+ } from "./resolver";
37
126
 
38
- // Lib
39
- export * from './lib/enact-direct';
127
+ // Local tool registry (tools.json management)
128
+ export {
129
+ loadToolsRegistry,
130
+ saveToolsRegistry,
131
+ addToolToRegistry,
132
+ removeToolFromRegistry,
133
+ isToolInstalled,
134
+ getInstalledVersion,
135
+ getToolCachePath,
136
+ listInstalledTools,
137
+ getInstalledToolInfo,
138
+ getToolsJsonPath,
139
+ type ToolsRegistry,
140
+ type RegistryScope,
141
+ type InstalledToolInfo,
142
+ } from "./registry";
143
+
144
+ // Logger utility
145
+ export {
146
+ Logger,
147
+ createLogger,
148
+ configureLogger,
149
+ getLogger,
150
+ debug,
151
+ info,
152
+ warn,
153
+ error,
154
+ type LogLevel,
155
+ type LogEntry,
156
+ type LoggerOptions,
157
+ } from "./utils/logger";
158
+
159
+ // Version utilities
160
+ export {
161
+ parseVersion,
162
+ isValidVersion as isValidSemver,
163
+ compareVersions,
164
+ parseRange,
165
+ satisfiesRange,
166
+ sortVersions,
167
+ getHighestVersion,
168
+ incrementVersion,
169
+ coerceVersion,
170
+ formatVersion,
171
+ type ParsedVersion,
172
+ type VersionRange,
173
+ } from "./utils/version";
174
+
175
+ // File system helpers
176
+ export {
177
+ ensureDir,
178
+ ensureParentDir,
179
+ pathExists,
180
+ isDirectory,
181
+ isFile,
182
+ readJsonFile,
183
+ tryReadJsonFile,
184
+ writeJsonFile,
185
+ readTextFile,
186
+ tryReadTextFile,
187
+ writeTextFile,
188
+ copyFile,
189
+ copyDir,
190
+ remove,
191
+ listDir,
192
+ listDirEntries,
193
+ findFiles,
194
+ findFilesRecursive,
195
+ getStats,
196
+ getFileSize,
197
+ touchFile,
198
+ } from "./utils/fs";
199
+
200
+ // Execution engine (browser-safe utilities only)
201
+ // NOTE: DaggerExecutionProvider moved to @enactprotocol/execution package
202
+ export {
203
+ // Types
204
+ type ExecutionInput,
205
+ type FileInput,
206
+ type ExecutionOutput,
207
+ type ExecutionResult,
208
+ type ExecutionMetadata,
209
+ type ExecutionError,
210
+ type ExecutionErrorCode,
211
+ type ExecutionOptions,
212
+ type RetryConfig,
213
+ type ContainerRuntime,
214
+ type RuntimeDetection,
215
+ type RuntimeStatus,
216
+ type EngineHealth,
217
+ type EngineState,
218
+ type ExecutionProvider,
219
+ type ParsedCommand,
220
+ type CommandToken,
221
+ type InterpolationOptions,
222
+ type InputValidationResult,
223
+ type InputValidationError,
224
+ type DryRunResult,
225
+ // Constants
226
+ DEFAULT_RETRY_CONFIG,
227
+ // Runtime
228
+ detectRuntime,
229
+ clearRuntimeCache,
230
+ isRuntimeAvailable,
231
+ getAvailableRuntimes,
232
+ RuntimeStatusTracker,
233
+ createRuntimeTracker,
234
+ // Command
235
+ parseCommand,
236
+ interpolateCommand,
237
+ shellEscape,
238
+ parseCommandArgs,
239
+ prepareCommand,
240
+ getMissingParams,
241
+ // Validation
242
+ validateInputs,
243
+ applyDefaults,
244
+ getRequiredParams,
245
+ getParamInfo,
246
+ } 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
+ }