@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
@@ -5,14 +5,14 @@
5
5
  *
6
6
  * ## Parameter Recognition
7
7
  *
8
- * Only `${...}` patterns that match parameters defined in the tool's inputSchema
9
- * are substituted. Other `${...}` patterns (like bash variables, arrays, etc.)
10
- * are passed through unchanged to the shell.
8
+ * Only `${...}` patterns that match known parameter names are substituted.
9
+ * Other `${...}` patterns (like bash variables, arrays, etc.) are passed
10
+ * through unchanged to the shell.
11
11
  *
12
12
  * This allows natural use of shell syntax:
13
- * - `${name}` - Substituted if "name" is in inputSchema
14
- * - `${MY_VAR}` - Passed through to bash (not in inputSchema)
15
- * - `${array[$i]}` - Passed through to bash (not in inputSchema)
13
+ * - `${name}` - Substituted if "name" is a known parameter
14
+ * - `${MY_VAR}` - Passed through to bash (not a known parameter)
15
+ * - `${array[$i]}` - Passed through to bash (not a known parameter)
16
16
  *
17
17
  * ## Quoting Behavior
18
18
  *
@@ -50,9 +50,9 @@ const PARAM_PATTERN = /\$\{([^}:]+)(?::([^}]+))?\}/g;
50
50
  */
51
51
  export interface ParseCommandOptions {
52
52
  /**
53
- * Set of known parameter names from the inputSchema.
53
+ * Set of known parameter names.
54
54
  * Only ${...} patterns matching these names will be treated as parameters.
55
- * If not provided, ALL ${...} patterns are treated as parameters (legacy behavior).
55
+ * If not provided, ALL ${...} patterns are treated as parameters.
56
56
  */
57
57
  knownParameters?: Set<string>;
58
58
  }
@@ -73,5 +73,22 @@ export {
73
73
  getParamInfo,
74
74
  } from "./validation.js";
75
75
 
76
+ // Action command interpolation ({{param}} templates)
77
+ export {
78
+ parseActionCommand,
79
+ parseActionArgument,
80
+ interpolateActionCommand,
81
+ prepareActionCommand,
82
+ getMissingRequiredParams,
83
+ getActionCommandParams,
84
+ hasActionTemplates,
85
+ type ActionCommandToken,
86
+ type ActionCommandLiteralToken,
87
+ type ActionCommandParamToken,
88
+ type ParsedArgument,
89
+ type ParsedActionCommand,
90
+ type ActionInterpolationOptions,
91
+ } from "./action-command.js";
92
+
76
93
  // NOTE: Dagger provider moved to @enactprotocol/execution package
77
94
  // This keeps @enactprotocol/shared browser-safe (no Dagger SDK dependency)
@@ -3,6 +3,7 @@
3
3
  * Provides interfaces for tool execution, container management, and results
4
4
  */
5
5
 
6
+ import type { Action, ActionsManifest } from "../types/actions";
6
7
  import type { ToolManifest } from "../types/manifest";
7
8
 
8
9
  // ============================================================================
@@ -302,6 +303,16 @@ export interface ExecutionProvider {
302
303
  options?: ExecutionOptions
303
304
  ): Promise<ExecutionResult>;
304
305
 
306
+ /** Execute an action from ACTIONS.yaml */
307
+ executeAction(
308
+ manifest: ToolManifest,
309
+ actionsManifest: ActionsManifest,
310
+ actionName: string,
311
+ action: Action,
312
+ input: ExecutionInput,
313
+ options?: ExecutionOptions
314
+ ): Promise<ExecutionResult>;
315
+
305
316
  /** Shutdown the provider */
306
317
  shutdown(): Promise<void>;
307
318
  }
@@ -342,9 +353,9 @@ export interface InterpolationOptions {
342
353
  /** Callback for warnings (e.g., potential double-quoting) */
343
354
  onWarning?: (warning: CommandWarning) => void;
344
355
  /**
345
- * Set of known parameter names from the inputSchema.
356
+ * Set of known parameter names.
346
357
  * Only ${...} patterns matching these names will be substituted.
347
- * If not provided, ALL ${...} patterns are treated as parameters (legacy behavior).
358
+ * If not provided, ALL ${...} patterns are treated as parameters.
348
359
  */
349
360
  knownParameters?: Set<string>;
350
361
  }
package/src/index.ts CHANGED
@@ -25,6 +25,7 @@ export {
25
25
  getEnactHome,
26
26
  getProjectEnactDir,
27
27
  getToolsDir,
28
+ getSkillsDir,
28
29
  getCacheDir,
29
30
  getConfigPath,
30
31
  getGlobalEnvPath,
@@ -60,6 +61,7 @@ export {
60
61
  type TrustConfig,
61
62
  type CacheConfig,
62
63
  type ExecutionConfig,
64
+ type ExecutionBackend,
63
65
  type RegistryConfig,
64
66
  } from "./config";
65
67
 
@@ -80,10 +82,21 @@ export type {
80
82
  ToolLocation,
81
83
  ToolResolution,
82
84
  ManifestFileName,
85
+ ScriptDefinition,
83
86
  } from "./types/manifest";
84
87
 
85
88
  export { MANIFEST_FILES, PACKAGE_MANIFEST_FILE } from "./types/manifest";
86
89
 
90
+ // Actions types (internal — used by scripts bridge and execution pipeline)
91
+ export type {
92
+ ActionEnvVar,
93
+ ActionEnvVars,
94
+ Action,
95
+ ActionsManifest,
96
+ } from "./types/actions";
97
+
98
+ export { DEFAULT_INPUT_SCHEMA, getEffectiveInputSchema } from "./types/actions";
99
+
87
100
  // Manifest parsing, validation, and loading
88
101
  export {
89
102
  // Parser
@@ -110,6 +123,12 @@ export {
110
123
  tryLoadManifest,
111
124
  tryLoadManifestFromDir,
112
125
  type LoadedManifest,
126
+ // Manifest with scripts
127
+ loadManifestWithActions,
128
+ type LoadedManifestWithActions,
129
+ // Scripts (inline scripts → Action bridge)
130
+ scriptToAction,
131
+ manifestScriptsToActionsManifest,
113
132
  } from "./manifest";
114
133
 
115
134
  // Tool resolver
@@ -118,6 +137,9 @@ export {
118
137
  resolveTool,
119
138
  resolveToolAuto,
120
139
  resolveToolFromPath,
140
+ resolveToolWithAction,
141
+ resolveAction,
142
+ parseActionSpecifier,
121
143
  tryResolveTool,
122
144
  tryResolveToolDetailed,
123
145
  normalizeToolName,
@@ -126,6 +148,7 @@ export {
126
148
  getToolSearchPaths,
127
149
  type ResolveOptions,
128
150
  type TryResolveResult,
151
+ type ParsedActionSpecifier,
129
152
  } from "./resolver";
130
153
 
131
154
  // Local tool registry (tools.json management)
@@ -281,4 +304,18 @@ export {
281
304
  applyDefaults,
282
305
  getRequiredParams,
283
306
  getParamInfo,
307
+ // Action command ({{param}} templates)
308
+ parseActionCommand,
309
+ parseActionArgument,
310
+ interpolateActionCommand,
311
+ prepareActionCommand,
312
+ getMissingRequiredParams,
313
+ getActionCommandParams,
314
+ hasActionTemplates,
315
+ type ActionCommandToken,
316
+ type ActionCommandLiteralToken,
317
+ type ActionCommandParamToken,
318
+ type ParsedArgument,
319
+ type ParsedActionCommand,
320
+ type ActionInterpolationOptions,
284
321
  } from "./execution";
@@ -0,0 +1,49 @@
1
+ /**
2
+ * Manifest loader with scripts support
3
+ *
4
+ * Loads SKILL.md manifests and converts inline scripts to ActionsManifest
5
+ * objects for the execution pipeline.
6
+ */
7
+
8
+ import type { ActionsManifest } from "../types/actions";
9
+ import { type LoadManifestOptions, type LoadedManifest, loadManifestFromDir } from "./loader";
10
+ import { manifestScriptsToActionsManifest } from "./scripts";
11
+
12
+ /**
13
+ * Result of loading a manifest with its scripts
14
+ */
15
+ export interface LoadedManifestWithActions extends LoadedManifest {
16
+ /** The scripts manifest (converted from inline scripts) */
17
+ actionsManifest?: ActionsManifest;
18
+ }
19
+
20
+ /**
21
+ * Load a SKILL.md manifest and convert inline scripts to an ActionsManifest
22
+ *
23
+ * This is the primary function for loading a complete skill with scripts.
24
+ * It loads the SKILL.md for documentation and metadata, then converts any
25
+ * inline `scripts` field into an ActionsManifest for the execution pipeline.
26
+ *
27
+ * @param dir - Directory containing SKILL.md
28
+ * @param options - Options for loading and validation
29
+ * @returns LoadedManifestWithActions containing manifest and optional scripts
30
+ * @throws ManifestLoadError if SKILL.md loading fails
31
+ */
32
+ export function loadManifestWithActions(
33
+ dir: string,
34
+ options: LoadManifestOptions = {}
35
+ ): LoadedManifestWithActions {
36
+ // Load the primary manifest (SKILL.md)
37
+ const loaded = loadManifestFromDir(dir, options);
38
+
39
+ // Convert inline scripts to ActionsManifest
40
+ const scriptsManifest = manifestScriptsToActionsManifest(loaded.manifest);
41
+ if (scriptsManifest) {
42
+ return {
43
+ ...loaded,
44
+ actionsManifest: scriptsManifest,
45
+ };
46
+ }
47
+
48
+ return loaded;
49
+ }
@@ -37,3 +37,15 @@ export {
37
37
  type LoadedManifest,
38
38
  type LoadManifestOptions,
39
39
  } from "./loader";
40
+
41
+ // Manifest with scripts
42
+ export {
43
+ loadManifestWithActions,
44
+ type LoadedManifestWithActions,
45
+ } from "./actions-loader";
46
+
47
+ // Scripts (inline scripts → Action bridge)
48
+ export {
49
+ scriptToAction,
50
+ manifestScriptsToActionsManifest,
51
+ } from "./scripts";
@@ -6,9 +6,10 @@
6
6
 
7
7
  import { existsSync, readFileSync } from "node:fs";
8
8
  import { basename, join } from "node:path";
9
+ import yaml from "js-yaml";
9
10
  import type { ParsedManifest, ToolManifest, ValidationResult } from "../types/manifest";
10
11
  import { MANIFEST_FILES } from "../types/manifest";
11
- import { ManifestParseError, parseManifestAuto } from "./parser";
12
+ import { ManifestParseError, extractFrontmatter, parseManifestAuto } from "./parser";
12
13
  import { type ValidateManifestOptions, validateManifest } from "./validator";
13
14
 
14
15
  /**
@@ -51,7 +52,7 @@ export interface LoadManifestOptions extends ValidateManifestOptions {
51
52
  /**
52
53
  * Load a manifest from a file path
53
54
  *
54
- * @param filePath - Path to the manifest file (SKILL.md, enact.md, enact.yaml, or enact.yml)
55
+ * @param filePath - Path to the manifest file (SKILL.md, skill.yaml, skill.yml, enact.md, enact.yaml, or enact.yml)
55
56
  * @param options - Options for loading and validation
56
57
  * @returns LoadedManifest with validated manifest and metadata
57
58
  * @throws ManifestLoadError if file doesn't exist, parse fails, or validation fails
@@ -116,10 +117,36 @@ export function loadManifest(filePath: string, options: LoadManifestOptions = {}
116
117
  return result;
117
118
  }
118
119
 
120
+ /**
121
+ * Load SKILL.md for its body content and frontmatter fields.
122
+ * Does NOT validate as a full ToolManifest — used only to extract
123
+ * agent-facing documentation in two-file mode (skill.yaml + SKILL.md).
124
+ */
125
+ function loadSkillDoc(
126
+ filePath: string
127
+ ): { body: string; frontmatter: Record<string, unknown> } | null {
128
+ try {
129
+ const content = readFileSync(filePath, "utf-8");
130
+ const extracted = extractFrontmatter(content);
131
+ if (!extracted) return null;
132
+ const frontmatter = extracted.frontmatter
133
+ ? ((yaml.load(extracted.frontmatter) as Record<string, unknown>) ?? {})
134
+ : {};
135
+ return { body: extracted.body, frontmatter };
136
+ } catch {
137
+ return null;
138
+ }
139
+ }
140
+
119
141
  /**
120
142
  * Find and load a manifest from a directory
121
143
  *
122
- * Searches for SKILL.md, enact.md, enact.yaml, or enact.yml in the given directory
144
+ * Supports two modes:
145
+ * 1. **Two-file model**: If both `skill.yaml` and `SKILL.md` exist, `skill.yaml` is
146
+ * the package manifest and `SKILL.md` provides agent-facing documentation (body → `doc`).
147
+ * Also supports legacy `enact.yaml` in place of `skill.yaml`.
148
+ * 2. **Single-file fallback**: Searches for SKILL.md, skill.yaml, skill.yml, enact.md, enact.yaml, or enact.yml
149
+ * and uses the first match as the complete manifest.
123
150
  *
124
151
  * @param dir - Directory to search for manifest
125
152
  * @param options - Options for loading and validation
@@ -130,7 +157,53 @@ export function loadManifestFromDir(
130
157
  dir: string,
131
158
  options: LoadManifestOptions = {}
132
159
  ): LoadedManifest {
133
- // Try each manifest filename in order of preference
160
+ const skillMdPath = join(dir, "SKILL.md");
161
+ const hasSkillMd = existsSync(skillMdPath);
162
+
163
+ // Find config file (skill.yaml, skill.yml, or legacy enact.yaml/enact.yml)
164
+ let enactConfigPath: string | null = null;
165
+ for (const f of ["skill.yaml", "skill.yml", "enact.yaml", "enact.yml"]) {
166
+ const p = join(dir, f);
167
+ if (existsSync(p)) {
168
+ enactConfigPath = p;
169
+ break;
170
+ }
171
+ }
172
+
173
+ // Two-file model: skill.yaml + SKILL.md
174
+ if (enactConfigPath && hasSkillMd) {
175
+ const loaded = loadManifest(enactConfigPath, options);
176
+ const skillDoc = loadSkillDoc(skillMdPath);
177
+
178
+ if (skillDoc) {
179
+ // Merge SKILL.md body into manifest.doc and LoadedManifest.body
180
+ loaded.manifest = { ...loaded.manifest };
181
+ if (skillDoc.body) {
182
+ if (!loaded.manifest.doc) {
183
+ loaded.manifest.doc = skillDoc.body;
184
+ }
185
+ loaded.body = skillDoc.body;
186
+ }
187
+ // Use SKILL.md frontmatter as fallbacks for shared fields
188
+ const fm = skillDoc.frontmatter;
189
+ if (!loaded.manifest.description && typeof fm.description === "string") {
190
+ loaded.manifest.description = fm.description;
191
+ }
192
+ if (!loaded.manifest.license && typeof fm.license === "string") {
193
+ loaded.manifest.license = fm.license;
194
+ }
195
+ if (!loaded.manifest.compatibility && typeof fm.compatibility === "string") {
196
+ loaded.manifest.compatibility = fm.compatibility;
197
+ }
198
+ if (!loaded.manifest.metadata && fm.metadata && typeof fm.metadata === "object") {
199
+ loaded.manifest.metadata = fm.metadata as Record<string, string>;
200
+ }
201
+ }
202
+
203
+ return loaded;
204
+ }
205
+
206
+ // Single-file fallback (existing behavior)
134
207
  for (const filename of MANIFEST_FILES) {
135
208
  const filePath = join(dir, filename);
136
209
  if (existsSync(filePath)) {
@@ -3,6 +3,7 @@
3
3
  *
4
4
  * Handles parsing of:
5
5
  * - SKILL.md files (YAML frontmatter + Markdown body) - primary format
6
+ * - skill.yaml/yml files (pure YAML) - package manifest
6
7
  * - enact.yaml/yml files (pure YAML) - legacy format
7
8
  * - enact.md files (YAML frontmatter + Markdown body) - legacy format
8
9
  */
@@ -0,0 +1,116 @@
1
+ /**
2
+ * Script-to-Action bridge
3
+ *
4
+ * Converts inline `scripts` from ToolManifest into Action/ActionsManifest
5
+ * objects so the existing execution pipeline works unchanged.
6
+ */
7
+
8
+ import type { JSONSchema7 } from "json-schema";
9
+ import { getActionCommandParams } from "../execution/action-command";
10
+ import type { Action, ActionEnvVars, ActionsManifest } from "../types/actions";
11
+ import type { ScriptDefinition, ToolManifest } from "../types/manifest";
12
+
13
+ /**
14
+ * Convert a script command string to array form
15
+ *
16
+ * Splits on whitespace while preserving {{param}} tokens as single elements.
17
+ */
18
+ function scriptCommandToArray(command: string): string[] {
19
+ return command.split(/\s+/).filter((s) => s.length > 0);
20
+ }
21
+
22
+ /**
23
+ * Auto-infer an inputSchema from {{param}} patterns in a command array
24
+ *
25
+ * Every parameter found becomes a required string property.
26
+ */
27
+ function inferInputSchema(commandArray: string[]): JSONSchema7 {
28
+ const params = getActionCommandParams(commandArray);
29
+
30
+ if (params.length === 0) {
31
+ return { type: "object", properties: {} };
32
+ }
33
+
34
+ const properties: Record<string, JSONSchema7> = {};
35
+ for (const param of params) {
36
+ properties[param] = { type: "string" };
37
+ }
38
+
39
+ return {
40
+ type: "object",
41
+ required: params,
42
+ properties,
43
+ };
44
+ }
45
+
46
+ /**
47
+ * Convert a single script entry to an Action object
48
+ */
49
+ export function scriptToAction(name: string, script: ScriptDefinition): Action {
50
+ if (typeof script === "string") {
51
+ const commandArray = scriptCommandToArray(script);
52
+ return {
53
+ description: name,
54
+ command: commandArray,
55
+ inputSchema: inferInputSchema(commandArray),
56
+ };
57
+ }
58
+
59
+ // Expanded form
60
+ const commandArray = scriptCommandToArray(script.command);
61
+ return {
62
+ description: script.description ?? name,
63
+ command: commandArray,
64
+ inputSchema: script.inputSchema ?? inferInputSchema(commandArray),
65
+ ...(script.outputSchema && { outputSchema: script.outputSchema }),
66
+ ...(script.annotations && { annotations: script.annotations }),
67
+ };
68
+ }
69
+
70
+ /**
71
+ * Convert manifest env (EnvVariables) to action env (ActionEnvVars)
72
+ */
73
+ function convertEnv(env: ToolManifest["env"]): ActionEnvVars | undefined {
74
+ if (!env || Object.keys(env).length === 0) return undefined;
75
+
76
+ const actionEnv: ActionEnvVars = {};
77
+ for (const [key, value] of Object.entries(env)) {
78
+ const envVar: ActionEnvVars[string] = {
79
+ description: value.description,
80
+ };
81
+ if (value.secret !== undefined) {
82
+ envVar.secret = value.secret;
83
+ }
84
+ if (value.default !== undefined) {
85
+ envVar.default = value.default;
86
+ }
87
+ actionEnv[key] = envVar;
88
+ }
89
+ return actionEnv;
90
+ }
91
+
92
+ /**
93
+ * Convert all inline scripts from a manifest to an ActionsManifest
94
+ *
95
+ * Returns null if the manifest has no scripts.
96
+ */
97
+ export function manifestScriptsToActionsManifest(manifest: ToolManifest): ActionsManifest | null {
98
+ if (!manifest.scripts || Object.keys(manifest.scripts).length === 0) {
99
+ return null;
100
+ }
101
+
102
+ const actions: Record<string, Action> = {};
103
+ for (const [name, script] of Object.entries(manifest.scripts)) {
104
+ actions[name] = scriptToAction(name, script);
105
+ }
106
+
107
+ const result: ActionsManifest = { actions };
108
+ const env = convertEnv(manifest.env);
109
+ if (env) {
110
+ result.env = env;
111
+ }
112
+ if (manifest.hooks?.build) {
113
+ result.build = manifest.hooks.build;
114
+ }
115
+ return result;
116
+ }
@@ -20,6 +20,7 @@ import type {
20
20
  const EnvVariableSchema = z.object({
21
21
  description: z.string().min(1, "Description is required"),
22
22
  secret: z.boolean().optional(),
23
+ required: z.boolean().optional(),
23
24
  default: z.string().optional(),
24
25
  });
25
26
 
@@ -83,15 +84,16 @@ const SEMVER_REGEX =
83
84
  /^(0|[1-9]\d*)\.(0|[1-9]\d*)\.(0|[1-9]\d*)(?:-((?:0|[1-9]\d*|\d*[a-zA-Z-][0-9a-zA-Z-]*)(?:\.(?:0|[1-9]\d*|\d*[a-zA-Z-][0-9a-zA-Z-]*))*))?(?:\+([0-9a-zA-Z-]+(?:\.[0-9a-zA-Z-]+)*))?$/;
84
85
 
85
86
  /**
86
- * Tool name regex - hierarchical path format (required for publishing)
87
+ * Tool name regex - @scope/name format (required for publishing)
88
+ * Exactly 2 segments with optional @ prefix: "org/tool" or "@org/tool"
87
89
  */
88
- const TOOL_NAME_REGEX = /^[a-z0-9_-]+(?:\/[a-z0-9_-]+)+$/;
90
+ const TOOL_NAME_REGEX = /^@?[a-z0-9_-]+\/[a-z0-9_-]+$/;
89
91
 
90
92
  /**
91
93
  * Tool name regex - simple format (allowed for local tools)
92
- * Allows both hierarchical (org/tool) and simple (my-tool) names
94
+ * Allows simple names ("my-tool") or scope/name ("org/tool", "@org/tool")
93
95
  */
94
- const TOOL_NAME_REGEX_LOCAL = /^[a-z0-9_-]+(?:\/[a-z0-9_-]+)*$/;
96
+ const TOOL_NAME_REGEX_LOCAL = /^@?[a-z0-9_-]+(?:\/[a-z0-9_-]+)?$/;
95
97
 
96
98
  /**
97
99
  * Go duration regex (used for timeout)
@@ -105,7 +107,7 @@ function createToolManifestSchema(allowSimpleNames: boolean) {
105
107
  const nameRegex = allowSimpleNames ? TOOL_NAME_REGEX_LOCAL : TOOL_NAME_REGEX;
106
108
  const nameMessage = allowSimpleNames
107
109
  ? "Tool name must contain only lowercase letters, numbers, hyphens, and underscores"
108
- : "Tool name must be hierarchical path format (e.g., 'org/tool' or 'org/category/tool')";
110
+ : "Tool name must be scope/name format (e.g., 'org/tool' or '@org/tool')";
109
111
 
110
112
  return z
111
113
  .object({
@@ -133,7 +135,6 @@ function createToolManifestSchema(allowSimpleNames: boolean) {
133
135
  tags: z.array(z.string()).optional(),
134
136
 
135
137
  // Schema fields
136
- inputSchema: JsonSchemaSchema.optional(),
137
138
  outputSchema: JsonSchemaSchema.optional(),
138
139
 
139
140
  // Environment variables
@@ -223,14 +224,6 @@ function generateWarnings(manifest: ToolManifest): ValidationWarning[] {
223
224
  });
224
225
  }
225
226
 
226
- if (!manifest.inputSchema && manifest.command) {
227
- warnings.push({
228
- path: "inputSchema",
229
- message: "Input schema is recommended for tools with parameters",
230
- code: "MISSING_RECOMMENDED",
231
- });
232
- }
233
-
234
227
  if (!manifest.outputSchema) {
235
228
  warnings.push({
236
229
  path: "outputSchema",
@@ -249,6 +242,14 @@ function generateWarnings(manifest: ToolManifest): ValidationWarning[] {
249
242
  code: "SECRET_WITH_DEFAULT",
250
243
  });
251
244
  }
245
+ if (value.required && value.default !== undefined) {
246
+ warnings.push({
247
+ path: `env.${key}`,
248
+ message:
249
+ "Required variables with a default value are always satisfied — 'required' has no effect",
250
+ code: "REQUIRED_WITH_DEFAULT",
251
+ });
252
+ }
252
253
  }
253
254
  }
254
255
 
@@ -132,12 +132,12 @@ export function getMcpToolVersion(toolName: string): string | null {
132
132
  }
133
133
 
134
134
  /**
135
- * Get the cache path for an MCP tool
135
+ * Get the install path for an MCP tool
136
+ * Skills are stored at ~/.agent/skills/{name}/ (flat, no version subdirectory)
136
137
  */
137
- function getMcpToolCachePath(toolName: string, version: string): string {
138
- const cacheDir = getCacheDir();
139
- const normalizedVersion = version.startsWith("v") ? version.slice(1) : version;
140
- return join(cacheDir, toolName, `v${normalizedVersion}`);
138
+ function getMcpToolCachePath(toolName: string, _version: string): string {
139
+ const skillsDir = getCacheDir();
140
+ return join(skillsDir, toolName);
141
141
  }
142
142
 
143
143
  /**
package/src/paths.ts CHANGED
@@ -74,11 +74,21 @@ export function getToolsDir(scope: ToolScope, startDir?: string): string | null
74
74
  }
75
75
 
76
76
  /**
77
- * Get the cache directory (~/.enact/cache/)
78
- * @returns Absolute path to ~/.enact/cache/
77
+ * Get the skills directory (~/.agent/skills/)
78
+ * This is the standard Agent Skills location for installed skills.
79
+ * @returns Absolute path to ~/.agent/skills/
80
+ */
81
+ export function getSkillsDir(): string {
82
+ return join(homedir(), ".agent", "skills");
83
+ }
84
+
85
+ /**
86
+ * Get the cache directory (~/.agent/skills/)
87
+ * @deprecated Use getSkillsDir() instead
88
+ * @returns Absolute path to ~/.agent/skills/
79
89
  */
80
90
  export function getCacheDir(): string {
81
- return join(getEnactHome(), "cache");
91
+ return getSkillsDir();
82
92
  }
83
93
 
84
94
  /**
package/src/registry.ts CHANGED
@@ -165,12 +165,12 @@ export function getInstalledVersion(
165
165
  }
166
166
 
167
167
  /**
168
- * Get the cache path for an installed tool
168
+ * Get the install path for a skill
169
+ * Skills are stored at ~/.agent/skills/{name}/ (flat, no version subdirectory)
169
170
  */
170
- export function getToolCachePath(toolName: string, version: string): string {
171
- const cacheDir = getCacheDir();
172
- const normalizedVersion = version.startsWith("v") ? version.slice(1) : version;
173
- return join(cacheDir, toolName, `v${normalizedVersion}`);
171
+ export function getToolCachePath(toolName: string, _version: string): string {
172
+ const skillsDir = getCacheDir();
173
+ return join(skillsDir, toolName);
174
174
  }
175
175
 
176
176
  /**