@enactprotocol/shared 2.2.4 → 2.3.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 (111) hide show
  1. package/README.md +1 -18
  2. package/dist/config.d.ts +12 -0
  3. package/dist/config.d.ts.map +1 -1
  4. package/dist/config.js +32 -6
  5. package/dist/config.js.map +1 -1
  6. package/dist/execution/action-command.d.ts +131 -0
  7. package/dist/execution/action-command.d.ts.map +1 -0
  8. package/dist/execution/action-command.js +300 -0
  9. package/dist/execution/action-command.js.map +1 -0
  10. package/dist/execution/command.d.ts +8 -8
  11. package/dist/execution/command.js +6 -6
  12. package/dist/execution/index.d.ts +1 -0
  13. package/dist/execution/index.d.ts.map +1 -1
  14. package/dist/execution/index.js +2 -0
  15. package/dist/execution/index.js.map +1 -1
  16. package/dist/execution/types.d.ts +5 -2
  17. package/dist/execution/types.d.ts.map +1 -1
  18. package/dist/execution/types.js.map +1 -1
  19. package/dist/index.d.ts +8 -6
  20. package/dist/index.d.ts.map +1 -1
  21. package/dist/index.js +11 -4
  22. package/dist/index.js.map +1 -1
  23. package/dist/manifest/actions-loader.d.ts +29 -0
  24. package/dist/manifest/actions-loader.d.ts.map +1 -0
  25. package/dist/manifest/actions-loader.js +34 -0
  26. package/dist/manifest/actions-loader.js.map +1 -0
  27. package/dist/manifest/actions-parser.d.ts +69 -0
  28. package/dist/manifest/actions-parser.d.ts.map +1 -0
  29. package/dist/manifest/actions-parser.js +265 -0
  30. package/dist/manifest/actions-parser.js.map +1 -0
  31. package/dist/manifest/index.d.ts +2 -0
  32. package/dist/manifest/index.d.ts.map +1 -1
  33. package/dist/manifest/index.js +4 -0
  34. package/dist/manifest/index.js.map +1 -1
  35. package/dist/manifest/loader.d.ts +7 -2
  36. package/dist/manifest/loader.d.ts.map +1 -1
  37. package/dist/manifest/loader.js +71 -4
  38. package/dist/manifest/loader.js.map +1 -1
  39. package/dist/manifest/parser.d.ts +1 -0
  40. package/dist/manifest/parser.d.ts.map +1 -1
  41. package/dist/manifest/parser.js +1 -0
  42. package/dist/manifest/parser.js.map +1 -1
  43. package/dist/manifest/scripts.d.ts +19 -0
  44. package/dist/manifest/scripts.d.ts.map +1 -0
  45. package/dist/manifest/scripts.js +102 -0
  46. package/dist/manifest/scripts.js.map +1 -0
  47. package/dist/manifest/validator.d.ts +1 -8
  48. package/dist/manifest/validator.d.ts.map +1 -1
  49. package/dist/manifest/validator.js +14 -13
  50. package/dist/manifest/validator.js.map +1 -1
  51. package/dist/mcp-registry.js +5 -5
  52. package/dist/mcp-registry.js.map +1 -1
  53. package/dist/paths.d.ts +9 -2
  54. package/dist/paths.d.ts.map +1 -1
  55. package/dist/paths.js +12 -3
  56. package/dist/paths.js.map +1 -1
  57. package/dist/registry.d.ts +3 -2
  58. package/dist/registry.d.ts.map +1 -1
  59. package/dist/registry.js +5 -5
  60. package/dist/registry.js.map +1 -1
  61. package/dist/resolver.d.ts +55 -4
  62. package/dist/resolver.d.ts.map +1 -1
  63. package/dist/resolver.js +133 -75
  64. package/dist/resolver.js.map +1 -1
  65. package/dist/types/actions.d.ts +194 -0
  66. package/dist/types/actions.d.ts.map +1 -0
  67. package/dist/types/actions.js +32 -0
  68. package/dist/types/actions.js.map +1 -0
  69. package/dist/types/index.d.ts +3 -1
  70. package/dist/types/index.d.ts.map +1 -1
  71. package/dist/types/index.js +1 -0
  72. package/dist/types/index.js.map +1 -1
  73. package/dist/types/manifest.d.ts +50 -5
  74. package/dist/types/manifest.d.ts.map +1 -1
  75. package/dist/types/manifest.js +10 -2
  76. package/dist/types/manifest.js.map +1 -1
  77. package/package.json +2 -2
  78. package/src/config.ts +48 -6
  79. package/src/execution/action-command.ts +417 -0
  80. package/src/execution/command.ts +8 -8
  81. package/src/execution/index.ts +17 -0
  82. package/src/execution/types.ts +13 -2
  83. package/src/index.ts +37 -0
  84. package/src/manifest/actions-loader.ts +49 -0
  85. package/src/manifest/index.ts +12 -0
  86. package/src/manifest/loader.ts +77 -4
  87. package/src/manifest/parser.ts +1 -0
  88. package/src/manifest/scripts.ts +116 -0
  89. package/src/manifest/validator.ts +15 -14
  90. package/src/mcp-registry.ts +5 -5
  91. package/src/paths.ts +13 -3
  92. package/src/registry.ts +5 -5
  93. package/src/resolver.ts +172 -77
  94. package/src/types/actions.ts +223 -0
  95. package/src/types/index.ts +11 -0
  96. package/src/types/manifest.ts +67 -6
  97. package/tests/action-command.test.ts +249 -0
  98. package/tests/config-normalization.test.ts +279 -0
  99. package/tests/config.test.ts +4 -1
  100. package/tests/effective-input-schema.test.ts +86 -0
  101. package/tests/fixtures/valid-tool.md +5 -12
  102. package/tests/fixtures/valid-tool.yaml +3 -10
  103. package/tests/hooks.test.ts +177 -0
  104. package/tests/manifest/loader.test.ts +34 -20
  105. package/tests/manifest/parser.test.ts +11 -15
  106. package/tests/manifest/validator.test.ts +7 -17
  107. package/tests/manifest-types.test.ts +9 -11
  108. package/tests/paths.test.ts +11 -4
  109. package/tests/registry.test.ts +12 -11
  110. package/tests/resolver.test.ts +11 -7
  111. package/tsconfig.tsbuildinfo +1 -1
package/src/resolver.ts CHANGED
@@ -4,19 +4,21 @@
4
4
  * Resolution order:
5
5
  * 1. Direct file path (if provided path exists)
6
6
  * 2. Project tools (.enact/tools/{name}/)
7
- * 3. Global tools (via ~/.enact/tools.json → cache)
8
- * 4. Cache (~/.enact/cache/{name}/{version}/)
7
+ * 3. Global tools (via ~/.enact/tools.json → ~/.agent/skills/)
8
+ * 4. Skills directory (~/.agent/skills/{name}/)
9
9
  */
10
10
 
11
- import { existsSync, readdirSync } from "node:fs";
11
+ import { existsSync } from "node:fs";
12
12
  import { dirname, isAbsolute, join, resolve } from "node:path";
13
13
  import {
14
14
  type LoadManifestOptions,
15
15
  ManifestLoadError,
16
16
  findManifestFile,
17
17
  loadManifest,
18
+ loadManifestFromDir,
18
19
  } from "./manifest/loader";
19
- import { getCacheDir, getProjectEnactDir } from "./paths";
20
+ import { manifestScriptsToActionsManifest } from "./manifest/scripts";
21
+ import { getProjectEnactDir, getSkillsDir } from "./paths";
20
22
  import { getInstalledVersion, getToolCachePath, resolveAlias } from "./registry";
21
23
  import type { ToolLocation, ToolResolution } from "./types/manifest";
22
24
 
@@ -66,13 +68,70 @@ export interface ResolveOptions {
66
68
  skipCache?: boolean;
67
69
  }
68
70
 
71
+ /**
72
+ * Result of parsing an action specifier
73
+ */
74
+ export interface ParsedActionSpecifier {
75
+ /** The skill name (e.g., "owner/skill" from "owner/skill:action") */
76
+ skillName: string;
77
+ /** The action name (e.g., "action" from "owner/skill:action"), or undefined if not specified */
78
+ actionName?: string;
79
+ }
80
+
81
+ /**
82
+ * Parse an action specifier into skill name and action name
83
+ *
84
+ * Specifier formats (uses colon separator):
85
+ * - "owner/skill" - skill only, no action
86
+ * - "owner/skill:action" - skill with action
87
+ * - "./path" or "/path" - file path (no action parsing)
88
+ * - "./path:action" - file path with action
89
+ *
90
+ * Examples:
91
+ * - "mendable/firecrawl" → skill="mendable/firecrawl", action=undefined
92
+ * - "mendable/firecrawl:scrape" → skill="mendable/firecrawl", action="scrape"
93
+ * - "acme/tools/greeter:hello" → skill="acme/tools/greeter", action="hello"
94
+ * - "/tmp/skill" → skill="/tmp/skill", action=undefined (file path)
95
+ * - "./skill:hello" → skill="./skill", action="hello" (file path with action)
96
+ *
97
+ * @param specifier - The tool/action specifier string
98
+ * @returns Parsed skill name and optional action name
99
+ */
100
+ export function parseActionSpecifier(specifier: string): ParsedActionSpecifier {
101
+ const normalized = specifier.replace(/\\/g, "/").trim();
102
+
103
+ // Check for colon separator (action specifier)
104
+ const colonIndex = normalized.lastIndexOf(":");
105
+
106
+ // No colon - just a skill name or file path
107
+ if (colonIndex === -1) {
108
+ return { skillName: normalized };
109
+ }
110
+
111
+ // Windows drive letter check (e.g., "C:/path")
112
+ // If colon is at index 1 and followed by /, it's a Windows path
113
+ if (colonIndex === 1 && normalized.length > 2 && normalized[2] === "/") {
114
+ return { skillName: normalized };
115
+ }
116
+
117
+ // Split on colon
118
+ const skillName = normalized.slice(0, colonIndex);
119
+ const actionName = normalized.slice(colonIndex + 1);
120
+
121
+ // Validate action name is non-empty
122
+ if (!actionName || actionName.length === 0) {
123
+ return { skillName: normalized };
124
+ }
125
+
126
+ return { skillName, actionName };
127
+ }
128
+
69
129
  /**
70
130
  * Convert tool name to directory path
71
- * e.g., "acme/utils/greeter" -> "acme/utils/greeter"
131
+ * Strips the @ prefix for disk paths: "@org/my-skill" -> "org/my-skill"
72
132
  */
73
133
  export function toolNameToPath(name: string): string {
74
- // Tool names are already path-like, just normalize
75
- return name.replace(/\\/g, "/");
134
+ return name.replace(/^@/, "").replace(/\\/g, "/");
76
135
  }
77
136
 
78
137
  /**
@@ -92,6 +151,8 @@ export function getToolPath(toolsDir: string, toolName: string): string {
92
151
  /**
93
152
  * Try to load a tool from a specific directory
94
153
  *
154
+ * Supports two-file model (skill.yaml + SKILL.md) via loadManifestFromDir().
155
+ *
95
156
  * @param dir - Directory to check
96
157
  * @param location - The location type for metadata
97
158
  * @param options - Options for loading the manifest
@@ -106,22 +167,26 @@ function tryLoadFromDir(
106
167
  return null;
107
168
  }
108
169
 
109
- const manifestPath = findManifestFile(dir);
110
- if (!manifestPath) {
111
- return null;
112
- }
113
-
114
170
  try {
115
- const loaded = loadManifest(manifestPath, options);
116
- return {
171
+ const loaded = loadManifestFromDir(dir, options);
172
+
173
+ const resolution: ToolResolution = {
117
174
  manifest: loaded.manifest,
118
175
  sourceDir: dir,
119
176
  location,
120
- manifestPath,
177
+ manifestPath: loaded.filePath,
121
178
  version: loaded.manifest.version,
122
179
  };
180
+
181
+ // Convert inline scripts to actionsManifest
182
+ const scriptsManifest = manifestScriptsToActionsManifest(loaded.manifest);
183
+ if (scriptsManifest) {
184
+ resolution.actionsManifest = scriptsManifest;
185
+ }
186
+
187
+ return resolution;
123
188
  } catch {
124
- // Invalid manifest, skip
189
+ // No manifest or invalid manifest, skip
125
190
  return null;
126
191
  }
127
192
  }
@@ -153,13 +218,23 @@ export function resolveToolFromPath(filePath: string): ToolResolution {
153
218
  }
154
219
 
155
220
  const loaded = loadManifest(absolutePath, localOptions);
156
- return {
221
+ const sourceDir = dirname(absolutePath);
222
+
223
+ const resolution: ToolResolution = {
157
224
  manifest: loaded.manifest,
158
- sourceDir: dirname(absolutePath),
225
+ sourceDir,
159
226
  location: "file",
160
227
  manifestPath: absolutePath,
161
228
  version: loaded.manifest.version,
162
229
  };
230
+
231
+ // Convert inline scripts to actionsManifest
232
+ const scriptsManifest = manifestScriptsToActionsManifest(loaded.manifest);
233
+ if (scriptsManifest) {
234
+ resolution.actionsManifest = scriptsManifest;
235
+ }
236
+
237
+ return resolution;
163
238
  }
164
239
 
165
240
  // Treat as directory
@@ -171,10 +246,76 @@ export function resolveToolFromPath(filePath: string): ToolResolution {
171
246
  throw new ToolResolveError(`No manifest found at: ${absolutePath}`, filePath);
172
247
  }
173
248
 
249
+ /**
250
+ * Resolve a specific action from a tool/skill
251
+ *
252
+ * @param resolution - Already resolved tool
253
+ * @param actionName - Name of the action to resolve
254
+ * @returns ToolResolution with action field populated
255
+ * @throws ToolResolveError if action not found
256
+ */
257
+ export function resolveAction(resolution: ToolResolution, actionName: string): ToolResolution {
258
+ // Check if the skill has actions (from scripts or ACTIONS.yaml)
259
+ if (!resolution.actionsManifest) {
260
+ throw new ToolResolveError(
261
+ `Skill "${resolution.manifest.name}" does not define any scripts or actions. ` +
262
+ `Cannot resolve "${actionName}".`,
263
+ `${resolution.manifest.name}:${actionName}`
264
+ );
265
+ }
266
+
267
+ // Find the script/action (map lookup)
268
+ const action = resolution.actionsManifest.actions[actionName];
269
+ if (!action) {
270
+ const availableActions = Object.keys(resolution.actionsManifest.actions);
271
+ throw new ToolResolveError(
272
+ `Action "${actionName}" not found in skill "${resolution.manifest.name}". ` +
273
+ `Available actions: ${availableActions.join(", ")}`,
274
+ `${resolution.manifest.name}:${actionName}`
275
+ );
276
+ }
277
+
278
+ return {
279
+ ...resolution,
280
+ action,
281
+ actionName,
282
+ };
283
+ }
284
+
285
+ /**
286
+ * Resolve a tool with optional action specifier
287
+ *
288
+ * Supports formats:
289
+ * - "owner/skill" - resolves skill only
290
+ * - "owner/skill:action" - resolves skill and specific action
291
+ *
292
+ * @param specifier - Tool/action specifier (e.g., "mendable/firecrawl:scrape")
293
+ * @param options - Resolution options
294
+ * @returns ToolResolution with action populated if specified
295
+ * @throws ToolResolveError if not found
296
+ */
297
+ export function resolveToolWithAction(
298
+ specifier: string,
299
+ options: ResolveOptions = {}
300
+ ): ToolResolution {
301
+ const { skillName, actionName } = parseActionSpecifier(specifier);
302
+
303
+ // Resolve the skill first
304
+ const resolution = resolveTool(skillName, options);
305
+
306
+ // If no action specified, return as-is
307
+ if (!actionName) {
308
+ return resolution;
309
+ }
310
+
311
+ // Resolve the action
312
+ return resolveAction(resolution, actionName);
313
+ }
314
+
174
315
  /**
175
316
  * Resolve a tool by name, searching through standard locations
176
317
  *
177
- * @param toolName - Tool name (e.g., "acme/utils/greeter") or alias (e.g., "firebase")
318
+ * @param toolName - Tool name (e.g., "acme/greeter" or "@acme/greeter") or alias (e.g., "firebase")
178
319
  * @param options - Resolution options
179
320
  * @returns ToolResolution
180
321
  * @throws ToolResolveError if not found
@@ -209,48 +350,29 @@ export function resolveTool(toolName: string, options: ResolveOptions = {}): Too
209
350
  }
210
351
  }
211
352
 
212
- // 2. Try global tools (via ~/.enact/tools.json → cache)
353
+ // 2. Try global tools (via ~/.enact/tools.json → ~/.agent/skills/)
213
354
  if (!options.skipUser) {
214
355
  const globalVersion = getInstalledVersion(normalizedName, "global");
215
356
  if (globalVersion) {
216
- const cachePath = getToolCachePath(normalizedName, globalVersion);
217
- searchedLocations.push(cachePath);
357
+ const skillPath = getToolCachePath(normalizedName, globalVersion);
358
+ searchedLocations.push(skillPath);
218
359
 
219
- const result = tryLoadFromDir(cachePath, "user");
360
+ const result = tryLoadFromDir(skillPath, "user");
220
361
  if (result) {
221
362
  return result;
222
363
  }
223
364
  }
224
365
  }
225
366
 
226
- // 3. Try cache (with optional version)
367
+ // 3. Try skills directory (~/.agent/skills/{name}/)
227
368
  if (!options.skipCache) {
228
- const cacheDir = getCacheDir();
229
- const toolCacheBase = getToolPath(cacheDir, normalizedName);
369
+ const skillsDir = getSkillsDir();
370
+ const skillDir = getToolPath(skillsDir, normalizedName);
371
+ searchedLocations.push(skillDir);
230
372
 
231
- if (options.version) {
232
- // Look for specific version
233
- const versionDir = join(toolCacheBase, `v${options.version.replace(/^v/, "")}`);
234
- searchedLocations.push(versionDir);
235
-
236
- const result = tryLoadFromDir(versionDir, "cache");
237
- if (result) {
238
- return result;
239
- }
240
- } else {
241
- // Look for latest cached version
242
- if (existsSync(toolCacheBase)) {
243
- const latestVersion = findLatestCachedVersion(toolCacheBase);
244
- if (latestVersion) {
245
- const versionDir = join(toolCacheBase, latestVersion);
246
- searchedLocations.push(versionDir);
247
-
248
- const result = tryLoadFromDir(versionDir, "cache");
249
- if (result) {
250
- return result;
251
- }
252
- }
253
- }
373
+ const result = tryLoadFromDir(skillDir, "cache");
374
+ if (result) {
375
+ return result;
254
376
  }
255
377
  }
256
378
 
@@ -261,33 +383,6 @@ export function resolveTool(toolName: string, options: ResolveOptions = {}): Too
261
383
  );
262
384
  }
263
385
 
264
- /**
265
- * Find the latest cached version directory
266
- */
267
- function findLatestCachedVersion(toolCacheBase: string): string | null {
268
- try {
269
- const entries = readdirSync(toolCacheBase, { withFileTypes: true });
270
- const versions = entries
271
- .filter((e) => e.isDirectory() && e.name.startsWith("v"))
272
- .map((e) => e.name)
273
- .sort((a, b) => {
274
- // Sort by semver (v1.0.0 format)
275
- const aVer = a.slice(1).split(".").map(Number);
276
- const bVer = b.slice(1).split(".").map(Number);
277
- for (let i = 0; i < 3; i++) {
278
- if ((aVer[i] ?? 0) !== (bVer[i] ?? 0)) {
279
- return (bVer[i] ?? 0) - (aVer[i] ?? 0);
280
- }
281
- }
282
- return 0;
283
- });
284
-
285
- return versions[0] ?? null;
286
- } catch {
287
- return null;
288
- }
289
- }
290
-
291
386
  /**
292
387
  * Try to resolve a tool, returning null instead of throwing
293
388
  *
@@ -483,7 +578,7 @@ export function getToolSearchPaths(toolName: string, options: ResolveOptions = {
483
578
 
484
579
  // Cache
485
580
  if (!options.skipCache) {
486
- const cacheDir = getCacheDir();
581
+ const cacheDir = getSkillsDir();
487
582
  paths.push(join(cacheDir, toolNameToPath(normalizedName)));
488
583
  }
489
584
 
@@ -0,0 +1,223 @@
1
+ /**
2
+ * TypeScript types for Agent Actions (ACTIONS.yaml)
3
+ *
4
+ * Agent Actions extends the Agent Skills specification with structured execution semantics,
5
+ * enabling skills to define executable actions with typed inputs, validated outputs,
6
+ * and secure credential handling.
7
+ *
8
+ * @see RFC-001-AGENT-ACTIONS.md
9
+ */
10
+
11
+ import type { JSONSchema7 } from "json-schema";
12
+ import type { ToolAnnotations } from "./manifest";
13
+
14
+ /**
15
+ * Environment variable declaration in ACTIONS.yaml
16
+ */
17
+ export interface ActionEnvVar {
18
+ /** Human-readable description of what this variable is for */
19
+ description?: string;
20
+ /** If true, value should be stored securely and masked in logs */
21
+ secret?: boolean;
22
+ /** If true, execution fails if not set */
23
+ required?: boolean;
24
+ /** Default value if not provided */
25
+ default?: string;
26
+ }
27
+
28
+ /**
29
+ * Environment variables map for actions
30
+ */
31
+ export type ActionEnvVars = Record<string, ActionEnvVar>;
32
+
33
+ /**
34
+ * A single executable action within a skill
35
+ *
36
+ * Each action maps directly to an MCP tool with:
37
+ * - (key) → tool name (action name is the map key, not a field)
38
+ * - description → tool description
39
+ * - inputSchema → tool parameters
40
+ * - outputSchema → expected response shape
41
+ * - annotations → behavioral hints
42
+ */
43
+ export interface Action {
44
+ /** Human-readable description of what this action does */
45
+ description: string;
46
+
47
+ /**
48
+ * Execution command
49
+ *
50
+ * Can be string form (simple commands without templates) or array form
51
+ * (required when using {{}} templates).
52
+ *
53
+ * Template syntax: {{param}} - each template is replaced with the literal
54
+ * value as a single argument, regardless of content.
55
+ *
56
+ * @example
57
+ * // String form (no templates)
58
+ * command: "python main.py --version"
59
+ *
60
+ * // Array form (with templates)
61
+ * command: ["python", "main.py", "scrape", "{{url}}"]
62
+ */
63
+ command: string | string[];
64
+
65
+ /**
66
+ * JSON Schema defining expected input parameters
67
+ *
68
+ * Uses standard JSON Schema conventions:
69
+ * - Required fields listed in 'required' array must be provided
70
+ * - Optional fields with 'default' use the default value if not provided
71
+ * - Optional fields without 'default' cause the argument to be omitted entirely
72
+ *
73
+ * If omitted, defaults to { type: 'object', properties: {} } (no parameters)
74
+ */
75
+ inputSchema?: JSONSchema7;
76
+
77
+ /**
78
+ * JSON Schema defining expected output structure
79
+ *
80
+ * If provided, clients must validate results against this schema.
81
+ * Results that don't conform are treated as errors.
82
+ */
83
+ outputSchema?: JSONSchema7;
84
+
85
+ /**
86
+ * Behavioral hints for AI models and clients
87
+ *
88
+ * Open-ended object for attaching metadata to actions.
89
+ * Clients may use these for UI presentation, filtering, or custom behavior.
90
+ */
91
+ annotations?: ToolAnnotations;
92
+ }
93
+
94
+ /**
95
+ * Complete ACTIONS.yaml manifest structure
96
+ *
97
+ * Defines how to execute actions for a skill, including environment
98
+ * variables, build steps, and the map of executable actions.
99
+ *
100
+ * @example
101
+ * ```yaml
102
+ * actions:
103
+ * scrape:
104
+ * description: Scrape a URL
105
+ * command: ["python", "main.py", "{{url}}"]
106
+ * inputSchema:
107
+ * type: object
108
+ * required: [url]
109
+ * properties:
110
+ * url: { type: string }
111
+ * ```
112
+ */
113
+ export interface ActionsManifest {
114
+ /**
115
+ * Environment variables and secrets required by all actions
116
+ *
117
+ * Key benefit: Unlike traditional skills where you discover missing
118
+ * credentials at runtime, ACTIONS.yaml declares requirements upfront.
119
+ */
120
+ env?: ActionEnvVars;
121
+
122
+ /**
123
+ * Map of action names to action definitions
124
+ *
125
+ * Each action becomes an MCP tool that can be executed directly.
126
+ * The key is the action name (e.g., "scrape", "crawl").
127
+ *
128
+ * @example
129
+ * actions:
130
+ * scrape:
131
+ * description: Scrape a URL
132
+ * command: ["python", "main.py", "{{url}}"]
133
+ * list-formats:
134
+ * description: List supported formats
135
+ * command: ffmpeg -formats
136
+ */
137
+ actions: Record<string, Action>;
138
+
139
+ /**
140
+ * Build commands to run before execution
141
+ *
142
+ * For projects that require setup (e.g., pip install, npm install).
143
+ * Build runs once per environment setup, not per action invocation.
144
+ * Build failures prevent action execution.
145
+ *
146
+ * @example
147
+ * build:
148
+ * - pip install -r requirements.txt
149
+ * - npm install
150
+ * - npm run build
151
+ */
152
+ build?: string | string[];
153
+ }
154
+
155
+ /**
156
+ * Result of parsing an ACTIONS.yaml file
157
+ */
158
+ export interface ParsedActionsManifest {
159
+ /** The parsed actions manifest */
160
+ actions: ActionsManifest;
161
+ /** The file path the manifest was loaded from */
162
+ filePath: string;
163
+ }
164
+
165
+ /**
166
+ * Validation error specific to actions
167
+ */
168
+ export interface ActionValidationError {
169
+ /** Path to the field with the error (e.g., "actions[0].command") */
170
+ path: string;
171
+ /** Error message */
172
+ message: string;
173
+ /** Error code for programmatic handling */
174
+ code: ActionValidationErrorCode;
175
+ }
176
+
177
+ /**
178
+ * Error codes for action validation
179
+ */
180
+ export type ActionValidationErrorCode =
181
+ | "MISSING_REQUIRED_FIELD"
182
+ | "INVALID_COMMAND_FORMAT"
183
+ | "STRING_COMMAND_WITH_TEMPLATE"
184
+ | "DUPLICATE_ACTION_NAME"
185
+ | "INVALID_INPUT_SCHEMA"
186
+ | "INVALID_OUTPUT_SCHEMA"
187
+ | "EMPTY_ACTIONS_ARRAY";
188
+
189
+ /**
190
+ * Result of validating an actions manifest
191
+ */
192
+ export interface ActionValidationResult {
193
+ /** Whether the manifest is valid */
194
+ valid: boolean;
195
+ /** Validation errors (if any) */
196
+ errors?: ActionValidationError[];
197
+ }
198
+
199
+ /**
200
+ * Actions manifest file names (in order of preference)
201
+ */
202
+ export const ACTIONS_FILES = ["ACTIONS.yaml", "ACTIONS.yml"] as const;
203
+ export type ActionsFileName = (typeof ACTIONS_FILES)[number];
204
+
205
+ /**
206
+ * Default inputSchema when not provided
207
+ *
208
+ * Actions without inputSchema default to accepting no parameters.
209
+ */
210
+ export const DEFAULT_INPUT_SCHEMA: JSONSchema7 = {
211
+ type: "object",
212
+ properties: {},
213
+ } as const;
214
+
215
+ /**
216
+ * Get the effective inputSchema for an action
217
+ *
218
+ * Returns the action's inputSchema if provided, otherwise returns
219
+ * the default empty schema.
220
+ */
221
+ export function getEffectiveInputSchema(action: Action): JSONSchema7 {
222
+ return action.inputSchema ?? DEFAULT_INPUT_SCHEMA;
223
+ }
@@ -13,6 +13,7 @@ export type {
13
13
  Author,
14
14
  ToolAnnotations,
15
15
  ResourceRequirements,
16
+ ToolHooks,
16
17
  ToolExample,
17
18
  // Validation types
18
19
  ValidationResult,
@@ -28,3 +29,13 @@ export {
28
29
  MANIFEST_FILES,
29
30
  PACKAGE_MANIFEST_FILE,
30
31
  } from "./manifest";
32
+
33
+ // Actions types (internal — used by scripts bridge and execution pipeline)
34
+ export type {
35
+ ActionEnvVar,
36
+ ActionEnvVars,
37
+ Action,
38
+ ActionsManifest,
39
+ } from "./actions";
40
+
41
+ export { DEFAULT_INPUT_SCHEMA, getEffectiveInputSchema } from "./actions";