@enactprotocol/shared 2.2.4 → 2.3.4
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +1 -18
- package/dist/config.d.ts +12 -0
- package/dist/config.d.ts.map +1 -1
- package/dist/config.js +32 -6
- package/dist/config.js.map +1 -1
- package/dist/execution/action-command.d.ts +131 -0
- package/dist/execution/action-command.d.ts.map +1 -0
- package/dist/execution/action-command.js +300 -0
- package/dist/execution/action-command.js.map +1 -0
- package/dist/execution/command.d.ts +8 -8
- package/dist/execution/command.js +6 -6
- package/dist/execution/index.d.ts +1 -0
- package/dist/execution/index.d.ts.map +1 -1
- package/dist/execution/index.js +2 -0
- package/dist/execution/index.js.map +1 -1
- package/dist/execution/types.d.ts +5 -2
- package/dist/execution/types.d.ts.map +1 -1
- package/dist/execution/types.js.map +1 -1
- package/dist/index.d.ts +8 -6
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +11 -4
- package/dist/index.js.map +1 -1
- package/dist/manifest/actions-loader.d.ts +29 -0
- package/dist/manifest/actions-loader.d.ts.map +1 -0
- package/dist/manifest/actions-loader.js +34 -0
- package/dist/manifest/actions-loader.js.map +1 -0
- package/dist/manifest/actions-parser.d.ts +69 -0
- package/dist/manifest/actions-parser.d.ts.map +1 -0
- package/dist/manifest/actions-parser.js +265 -0
- package/dist/manifest/actions-parser.js.map +1 -0
- package/dist/manifest/index.d.ts +2 -0
- package/dist/manifest/index.d.ts.map +1 -1
- package/dist/manifest/index.js +4 -0
- package/dist/manifest/index.js.map +1 -1
- package/dist/manifest/loader.d.ts +7 -2
- package/dist/manifest/loader.d.ts.map +1 -1
- package/dist/manifest/loader.js +71 -4
- package/dist/manifest/loader.js.map +1 -1
- package/dist/manifest/parser.d.ts +1 -0
- package/dist/manifest/parser.d.ts.map +1 -1
- package/dist/manifest/parser.js +1 -0
- package/dist/manifest/parser.js.map +1 -1
- package/dist/manifest/scripts.d.ts +19 -0
- package/dist/manifest/scripts.d.ts.map +1 -0
- package/dist/manifest/scripts.js +102 -0
- package/dist/manifest/scripts.js.map +1 -0
- package/dist/manifest/validator.d.ts +1 -8
- package/dist/manifest/validator.d.ts.map +1 -1
- package/dist/manifest/validator.js +14 -13
- package/dist/manifest/validator.js.map +1 -1
- package/dist/mcp-registry.js +5 -5
- package/dist/mcp-registry.js.map +1 -1
- package/dist/paths.d.ts +9 -2
- package/dist/paths.d.ts.map +1 -1
- package/dist/paths.js +12 -3
- package/dist/paths.js.map +1 -1
- package/dist/registry.d.ts +3 -2
- package/dist/registry.d.ts.map +1 -1
- package/dist/registry.js +5 -5
- package/dist/registry.js.map +1 -1
- package/dist/resolver.d.ts +55 -4
- package/dist/resolver.d.ts.map +1 -1
- package/dist/resolver.js +133 -75
- package/dist/resolver.js.map +1 -1
- package/dist/types/actions.d.ts +194 -0
- package/dist/types/actions.d.ts.map +1 -0
- package/dist/types/actions.js +32 -0
- package/dist/types/actions.js.map +1 -0
- package/dist/types/index.d.ts +3 -1
- package/dist/types/index.d.ts.map +1 -1
- package/dist/types/index.js +1 -0
- package/dist/types/index.js.map +1 -1
- package/dist/types/manifest.d.ts +50 -5
- package/dist/types/manifest.d.ts.map +1 -1
- package/dist/types/manifest.js +10 -2
- package/dist/types/manifest.js.map +1 -1
- package/package.json +2 -2
- package/src/config.ts +48 -6
- package/src/execution/action-command.ts +417 -0
- package/src/execution/command.ts +8 -8
- package/src/execution/index.ts +17 -0
- package/src/execution/types.ts +13 -2
- package/src/index.ts +37 -0
- package/src/manifest/actions-loader.ts +49 -0
- package/src/manifest/index.ts +12 -0
- package/src/manifest/loader.ts +77 -4
- package/src/manifest/parser.ts +1 -0
- package/src/manifest/scripts.ts +116 -0
- package/src/manifest/validator.ts +15 -14
- package/src/mcp-registry.ts +5 -5
- package/src/paths.ts +13 -3
- package/src/registry.ts +5 -5
- package/src/resolver.ts +172 -77
- package/src/types/actions.ts +223 -0
- package/src/types/index.ts +11 -0
- package/src/types/manifest.ts +67 -6
- package/tests/action-command.test.ts +249 -0
- package/tests/config-normalization.test.ts +279 -0
- package/tests/config.test.ts +4 -1
- package/tests/effective-input-schema.test.ts +86 -0
- package/tests/fixtures/valid-tool.md +5 -12
- package/tests/fixtures/valid-tool.yaml +3 -10
- package/tests/hooks.test.ts +177 -0
- package/tests/manifest/loader.test.ts +34 -20
- package/tests/manifest/parser.test.ts +11 -15
- package/tests/manifest/validator.test.ts +7 -17
- package/tests/manifest-types.test.ts +9 -11
- package/tests/paths.test.ts +11 -4
- package/tests/registry.test.ts +12 -11
- package/tests/resolver.test.ts +11 -7
- 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 →
|
|
8
|
-
* 4.
|
|
7
|
+
* 3. Global tools (via ~/.enact/tools.json → ~/.agent/skills/)
|
|
8
|
+
* 4. Skills directory (~/.agent/skills/{name}/)
|
|
9
9
|
*/
|
|
10
10
|
|
|
11
|
-
import { existsSync
|
|
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 {
|
|
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
|
-
*
|
|
131
|
+
* Strips the @ prefix for disk paths: "@org/my-skill" -> "org/my-skill"
|
|
72
132
|
*/
|
|
73
133
|
export function toolNameToPath(name: string): string {
|
|
74
|
-
|
|
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 =
|
|
116
|
-
|
|
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
|
-
//
|
|
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
|
-
|
|
221
|
+
const sourceDir = dirname(absolutePath);
|
|
222
|
+
|
|
223
|
+
const resolution: ToolResolution = {
|
|
157
224
|
manifest: loaded.manifest,
|
|
158
|
-
sourceDir
|
|
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/
|
|
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 →
|
|
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
|
|
217
|
-
searchedLocations.push(
|
|
357
|
+
const skillPath = getToolCachePath(normalizedName, globalVersion);
|
|
358
|
+
searchedLocations.push(skillPath);
|
|
218
359
|
|
|
219
|
-
const result = tryLoadFromDir(
|
|
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
|
|
367
|
+
// 3. Try skills directory (~/.agent/skills/{name}/)
|
|
227
368
|
if (!options.skipCache) {
|
|
228
|
-
const
|
|
229
|
-
const
|
|
369
|
+
const skillsDir = getSkillsDir();
|
|
370
|
+
const skillDir = getToolPath(skillsDir, normalizedName);
|
|
371
|
+
searchedLocations.push(skillDir);
|
|
230
372
|
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
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 =
|
|
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
|
+
}
|
package/src/types/index.ts
CHANGED
|
@@ -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";
|