@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.
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
@@ -0,0 +1,86 @@
1
+ /**
2
+ * Tests for getEffectiveInputSchema and DEFAULT_INPUT_SCHEMA.
3
+ */
4
+
5
+ import { describe, expect, test } from "bun:test";
6
+ import { DEFAULT_INPUT_SCHEMA, getEffectiveInputSchema } from "../src/types/actions";
7
+ import type { Action } from "../src/types/actions";
8
+
9
+ describe("DEFAULT_INPUT_SCHEMA", () => {
10
+ test("is an object type schema", () => {
11
+ expect(DEFAULT_INPUT_SCHEMA.type).toBe("object");
12
+ });
13
+
14
+ test("has empty properties", () => {
15
+ expect(DEFAULT_INPUT_SCHEMA.properties).toEqual({});
16
+ });
17
+
18
+ test("has no required fields", () => {
19
+ expect(DEFAULT_INPUT_SCHEMA.required).toBeUndefined();
20
+ });
21
+ });
22
+
23
+ describe("getEffectiveInputSchema", () => {
24
+ test("returns action inputSchema when provided", () => {
25
+ const action: Action = {
26
+ description: "test",
27
+ command: ["echo"],
28
+ inputSchema: {
29
+ type: "object",
30
+ properties: { name: { type: "string" } },
31
+ required: ["name"],
32
+ },
33
+ };
34
+
35
+ const schema = getEffectiveInputSchema(action);
36
+ expect(schema).toBe(action.inputSchema!);
37
+ expect(schema.required).toEqual(["name"]);
38
+ });
39
+
40
+ test("returns DEFAULT_INPUT_SCHEMA when no inputSchema", () => {
41
+ const action: Action = {
42
+ description: "test",
43
+ command: "echo hello",
44
+ };
45
+
46
+ const schema = getEffectiveInputSchema(action);
47
+ expect(schema).toBe(DEFAULT_INPUT_SCHEMA);
48
+ expect(schema.type).toBe("object");
49
+ expect(schema.properties).toEqual({});
50
+ });
51
+
52
+ test("returns DEFAULT_INPUT_SCHEMA when inputSchema is undefined", () => {
53
+ const action = {
54
+ description: "test",
55
+ command: ["echo", "hello"],
56
+ } as Action;
57
+
58
+ const schema = getEffectiveInputSchema(action);
59
+ expect(schema).toBe(DEFAULT_INPUT_SCHEMA);
60
+ });
61
+
62
+ test("preserves complex inputSchema with nested properties", () => {
63
+ const action: Action = {
64
+ description: "test",
65
+ command: ["cmd"],
66
+ inputSchema: {
67
+ type: "object",
68
+ properties: {
69
+ config: {
70
+ type: "object",
71
+ properties: {
72
+ depth: { type: "number", default: 3 },
73
+ format: { type: "string", enum: ["json", "csv"] },
74
+ },
75
+ },
76
+ },
77
+ required: ["config"],
78
+ },
79
+ };
80
+
81
+ const schema = getEffectiveInputSchema(action);
82
+ expect(schema.required).toEqual(["config"]);
83
+ const props = schema.properties as Record<string, { type: string }>;
84
+ expect(props.config!.type).toBe("object");
85
+ });
86
+ });
@@ -1,23 +1,16 @@
1
1
  ---
2
2
  enact: "2.0.0"
3
- name: acme/docs/analyzer
3
+ name: acme/analyzer
4
4
  description: Analyzes documentation for completeness
5
5
  version: "1.2.0"
6
6
  from: python:3.11-slim
7
- command: "python analyze.py ${file}"
8
7
  timeout: 5m
9
8
  license: Apache-2.0
10
9
  tags:
11
10
  - documentation
12
11
  - analysis
13
- inputSchema:
14
- type: object
15
- properties:
16
- file:
17
- type: string
18
- description: Path to the documentation file
19
- required:
20
- - file
12
+ scripts:
13
+ analyze: "python analyze.py {{file}}"
21
14
  outputSchema:
22
15
  type: object
23
16
  properties:
@@ -39,7 +32,7 @@ Provide a path to a documentation file and get a quality score along with
39
32
  any issues found.
40
33
 
41
34
  ```bash
42
- enact run acme/docs/analyzer --file README.md
35
+ enact run acme/analyzer --file README.md
43
36
  ```
44
37
 
45
38
  ## Output
@@ -54,7 +47,7 @@ The tool returns a JSON object with:
54
47
  ### Basic Analysis
55
48
 
56
49
  ```bash
57
- enact run acme/docs/analyzer --file docs/api.md
50
+ enact run acme/analyzer --file docs/api.md
58
51
  ```
59
52
 
60
53
  ### Integration with CI
@@ -1,22 +1,15 @@
1
1
  enact: "2.0.0"
2
- name: acme/utils/greeter
2
+ name: acme/greeter
3
3
  description: Greets users by name
4
4
  version: "1.0.0"
5
5
  from: node:18-alpine
6
- command: "echo 'Hello ${name}!'"
7
6
  timeout: 30s
8
7
  license: MIT
9
8
  tags:
10
9
  - greeting
11
10
  - utility
12
- inputSchema:
13
- type: object
14
- properties:
15
- name:
16
- type: string
17
- description: The name to greet
18
- required:
19
- - name
11
+ scripts:
12
+ greet: "echo 'Hello {{name}}!'"
20
13
  outputSchema:
21
14
  type: object
22
15
  properties:
@@ -0,0 +1,177 @@
1
+ /**
2
+ * Tests for the ToolHooks type and postinstall hook behavior.
3
+ */
4
+
5
+ import { afterAll, beforeAll, describe, expect, test } from "bun:test";
6
+ import { existsSync, mkdirSync, rmSync } from "node:fs";
7
+ import { join } from "node:path";
8
+ import type { ToolHooks, ToolManifest } from "../src/types/manifest";
9
+
10
+ const FIXTURES_DIR = join(import.meta.dir, "fixtures", "hooks-test");
11
+
12
+ beforeAll(() => {
13
+ mkdirSync(FIXTURES_DIR, { recursive: true });
14
+ });
15
+
16
+ afterAll(() => {
17
+ if (existsSync(FIXTURES_DIR)) {
18
+ rmSync(FIXTURES_DIR, { recursive: true, force: true });
19
+ }
20
+ });
21
+
22
+ describe("ToolHooks type", () => {
23
+ test("manifest accepts hooks with postinstall string", () => {
24
+ const manifest: ToolManifest = {
25
+ name: "@test/with-hook",
26
+ description: "A tool with a postinstall hook",
27
+ hooks: {
28
+ postinstall: "npm install",
29
+ },
30
+ };
31
+ expect(manifest.hooks?.postinstall).toBe("npm install");
32
+ });
33
+
34
+ test("manifest accepts hooks with postinstall array", () => {
35
+ const manifest: ToolManifest = {
36
+ name: "@test/with-hooks",
37
+ description: "A tool with multiple postinstall commands",
38
+ hooks: {
39
+ postinstall: ["npm install", "npm run build"],
40
+ },
41
+ };
42
+ expect(manifest.hooks?.postinstall).toEqual(["npm install", "npm run build"]);
43
+ });
44
+
45
+ test("manifest works without hooks", () => {
46
+ const manifest: ToolManifest = {
47
+ name: "@test/no-hooks",
48
+ description: "A tool without hooks",
49
+ };
50
+ expect(manifest.hooks).toBeUndefined();
51
+ });
52
+
53
+ test("hooks field can be empty object", () => {
54
+ const hooks: ToolHooks = {};
55
+ expect(hooks.postinstall).toBeUndefined();
56
+ });
57
+ });
58
+
59
+ describe("postinstall hook execution", () => {
60
+ test("single command creates expected side-effect", async () => {
61
+ const toolDir = join(FIXTURES_DIR, "single-cmd");
62
+ mkdirSync(toolDir, { recursive: true });
63
+
64
+ const markerFile = join(toolDir, "postinstall-ran.txt");
65
+ const cmd = `echo "hook executed" > postinstall-ran.txt`;
66
+
67
+ const proc = Bun.spawn(["sh", "-c", cmd], {
68
+ cwd: toolDir,
69
+ stdout: "pipe",
70
+ stderr: "pipe",
71
+ });
72
+ await proc.exited;
73
+
74
+ expect(existsSync(markerFile)).toBe(true);
75
+ const content = await Bun.file(markerFile).text();
76
+ expect(content.trim()).toBe("hook executed");
77
+ });
78
+
79
+ test("multiple commands run sequentially", async () => {
80
+ const toolDir = join(FIXTURES_DIR, "multi-cmd");
81
+ mkdirSync(toolDir, { recursive: true });
82
+
83
+ const commands = [
84
+ 'echo "step1" > step1.txt',
85
+ 'echo "step2" > step2.txt',
86
+ "cat step1.txt step2.txt > combined.txt",
87
+ ];
88
+
89
+ for (const cmd of commands) {
90
+ const proc = Bun.spawn(["sh", "-c", cmd], {
91
+ cwd: toolDir,
92
+ stdout: "pipe",
93
+ stderr: "pipe",
94
+ });
95
+ const exitCode = await proc.exited;
96
+ expect(exitCode).toBe(0);
97
+ }
98
+
99
+ const combined = await Bun.file(join(toolDir, "combined.txt")).text();
100
+ expect(combined).toContain("step1");
101
+ expect(combined).toContain("step2");
102
+ });
103
+
104
+ test("failing command returns non-zero exit code", async () => {
105
+ const toolDir = join(FIXTURES_DIR, "fail-cmd");
106
+ mkdirSync(toolDir, { recursive: true });
107
+
108
+ const proc = Bun.spawn(["sh", "-c", "exit 1"], {
109
+ cwd: toolDir,
110
+ stdout: "pipe",
111
+ stderr: "pipe",
112
+ });
113
+ const exitCode = await proc.exited;
114
+
115
+ expect(exitCode).toBe(1);
116
+ });
117
+
118
+ test("command runs in the tool directory (cwd)", async () => {
119
+ const toolDir = join(FIXTURES_DIR, "cwd-check");
120
+ mkdirSync(toolDir, { recursive: true });
121
+
122
+ const proc = Bun.spawn(["sh", "-c", "pwd"], {
123
+ cwd: toolDir,
124
+ stdout: "pipe",
125
+ stderr: "pipe",
126
+ });
127
+ await proc.exited;
128
+
129
+ const output = await new Response(proc.stdout).text();
130
+ expect(output.trim()).toBe(toolDir);
131
+ });
132
+
133
+ test("command inherits environment variables", async () => {
134
+ const toolDir = join(FIXTURES_DIR, "env-check");
135
+ mkdirSync(toolDir, { recursive: true });
136
+
137
+ const proc = Bun.spawn(["sh", "-c", "echo $HOME"], {
138
+ cwd: toolDir,
139
+ stdout: "pipe",
140
+ stderr: "pipe",
141
+ env: { ...process.env },
142
+ });
143
+ await proc.exited;
144
+
145
+ const output = await new Response(proc.stdout).text();
146
+ expect(output.trim()).toBeTruthy();
147
+ expect(output.trim()).toBe(process.env.HOME!);
148
+ });
149
+ });
150
+
151
+ describe("manifest with hooks serialization", () => {
152
+ test("hooks survive JSON round-trip (single command)", () => {
153
+ const manifest: ToolManifest = {
154
+ name: "@test/roundtrip",
155
+ description: "test",
156
+ hooks: { postinstall: "make build" },
157
+ };
158
+
159
+ const json = JSON.stringify(manifest);
160
+ const parsed = JSON.parse(json) as ToolManifest;
161
+
162
+ expect(parsed.hooks?.postinstall).toBe("make build");
163
+ });
164
+
165
+ test("hooks survive JSON round-trip (command array)", () => {
166
+ const manifest: ToolManifest = {
167
+ name: "@test/roundtrip-array",
168
+ description: "test",
169
+ hooks: { postinstall: ["npm ci", "npm run build"] },
170
+ };
171
+
172
+ const json = JSON.stringify(manifest);
173
+ const parsed = JSON.parse(json) as ToolManifest;
174
+
175
+ expect(parsed.hooks?.postinstall).toEqual(["npm ci", "npm run build"]);
176
+ });
177
+ });
@@ -34,7 +34,7 @@ describe("manifest loader", () => {
34
34
  const filePath = join(FIXTURES_DIR, "valid-tool.yaml");
35
35
  const result = loadManifest(filePath);
36
36
 
37
- expect(result.manifest.name).toBe("acme/utils/greeter");
37
+ expect(result.manifest.name).toBe("acme/greeter");
38
38
  expect(result.manifest.description).toBe("Greets users by name");
39
39
  expect(result.manifest.version).toBe("1.0.0");
40
40
  expect(result.format).toBe("yaml");
@@ -46,7 +46,7 @@ describe("manifest loader", () => {
46
46
  const filePath = join(FIXTURES_DIR, "valid-tool.md");
47
47
  const result = loadManifest(filePath);
48
48
 
49
- expect(result.manifest.name).toBe("acme/docs/analyzer");
49
+ expect(result.manifest.name).toBe("acme/analyzer");
50
50
  expect(result.manifest.description).toBe("Analyzes documentation for completeness");
51
51
  expect(result.format).toBe("md");
52
52
  expect(result.body).toBeDefined();
@@ -104,11 +104,11 @@ description: A minimal tool
104
104
  });
105
105
 
106
106
  describe("loadManifestFromDir", () => {
107
- test("loads manifest from directory with enact.yaml", () => {
107
+ test("loads manifest from directory with skill.yaml", () => {
108
108
  const testDir = join(TEST_DIR, "yaml-dir");
109
109
  mkdirSync(testDir, { recursive: true });
110
110
  writeFileSync(
111
- join(testDir, "enact.yaml"),
111
+ join(testDir, "skill.yaml"),
112
112
  `
113
113
  name: test/yaml
114
114
  description: Test YAML manifest
@@ -120,6 +120,22 @@ description: Test YAML manifest
120
120
  expect(result.manifest.name).toBe("test/yaml");
121
121
  });
122
122
 
123
+ test("loads manifest from directory with legacy enact.yaml", () => {
124
+ const testDir = join(TEST_DIR, "legacy-yaml-dir");
125
+ mkdirSync(testDir, { recursive: true });
126
+ writeFileSync(
127
+ join(testDir, "enact.yaml"),
128
+ `
129
+ name: test/legacy-yaml
130
+ description: Test legacy YAML manifest
131
+ `,
132
+ "utf-8"
133
+ );
134
+
135
+ const result = loadManifestFromDir(testDir);
136
+ expect(result.manifest.name).toBe("test/legacy-yaml");
137
+ });
138
+
123
139
  test("loads manifest from directory with enact.md", () => {
124
140
  const testDir = join(TEST_DIR, "md-dir");
125
141
  mkdirSync(testDir, { recursive: true });
@@ -140,31 +156,29 @@ description: Test Markdown manifest
140
156
  expect(result.format).toBe("md");
141
157
  });
142
158
 
143
- test("prefers enact.md over enact.yaml", () => {
159
+ test("prefers skill.yaml over legacy enact.yaml", () => {
144
160
  const testDir = join(TEST_DIR, "both-dir");
145
161
  mkdirSync(testDir, { recursive: true });
146
162
 
147
- // Create both files
148
163
  writeFileSync(
149
- join(testDir, "enact.md"),
150
- `---
151
- name: test/md-preferred
152
- description: Markdown version
153
- ---
164
+ join(testDir, "skill.yaml"),
165
+ `
166
+ name: test/skill-preferred
167
+ description: Skill version
154
168
  `,
155
169
  "utf-8"
156
170
  );
157
171
  writeFileSync(
158
172
  join(testDir, "enact.yaml"),
159
173
  `
160
- name: test/yaml-version
161
- description: YAML version
174
+ name: test/enact-version
175
+ description: Legacy version
162
176
  `,
163
177
  "utf-8"
164
178
  );
165
179
 
166
180
  const result = loadManifestFromDir(testDir);
167
- expect(result.manifest.name).toBe("test/md-preferred");
181
+ expect(result.manifest.name).toBe("test/skill-preferred");
168
182
  });
169
183
 
170
184
  test("throws for directory without manifest", () => {
@@ -177,13 +191,13 @@ description: YAML version
177
191
  });
178
192
 
179
193
  describe("findManifestFile", () => {
180
- test("finds enact.yaml", () => {
194
+ test("finds skill.yaml", () => {
181
195
  const testDir = join(TEST_DIR, "find-yaml");
182
196
  mkdirSync(testDir, { recursive: true });
183
- writeFileSync(join(testDir, "enact.yaml"), "name: test/find", "utf-8");
197
+ writeFileSync(join(testDir, "skill.yaml"), "name: test/find", "utf-8");
184
198
 
185
199
  const result = findManifestFile(testDir);
186
- expect(result).toBe(join(testDir, "enact.yaml"));
200
+ expect(result).toBe(join(testDir, "skill.yaml"));
187
201
  });
188
202
 
189
203
  test("finds enact.md", () => {
@@ -208,7 +222,7 @@ description: YAML version
208
222
  test("returns true when manifest exists", () => {
209
223
  const testDir = join(TEST_DIR, "has-manifest");
210
224
  mkdirSync(testDir, { recursive: true });
211
- writeFileSync(join(testDir, "enact.yaml"), "name: test/has", "utf-8");
225
+ writeFileSync(join(testDir, "skill.yaml"), "name: test/has", "utf-8");
212
226
 
213
227
  expect(hasManifest(testDir)).toBe(true);
214
228
  });
@@ -227,7 +241,7 @@ description: YAML version
227
241
  const result = tryLoadManifest(filePath);
228
242
 
229
243
  expect(result).not.toBeNull();
230
- expect(result?.manifest.name).toBe("acme/utils/greeter");
244
+ expect(result?.manifest.name).toBe("acme/greeter");
231
245
  });
232
246
 
233
247
  test("returns null on failure", () => {
@@ -250,7 +264,7 @@ description: YAML version
250
264
  const testDir = join(TEST_DIR, "try-success");
251
265
  mkdirSync(testDir, { recursive: true });
252
266
  writeFileSync(
253
- join(testDir, "enact.yaml"),
267
+ join(testDir, "skill.yaml"),
254
268
  `
255
269
  name: test/try
256
270
  description: Test try loading
@@ -25,15 +25,15 @@ version: "1.0.0"
25
25
  test("parses nested YAML", () => {
26
26
  const yaml = `
27
27
  name: org/tool
28
- inputSchema:
28
+ outputSchema:
29
29
  type: object
30
30
  properties:
31
31
  name:
32
32
  type: string
33
33
  `;
34
34
  const result = parseYaml(yaml);
35
- expect(result.inputSchema).toBeDefined();
36
- const schema = result.inputSchema as Record<string, unknown>;
35
+ expect(result.outputSchema).toBeDefined();
36
+ const schema = result.outputSchema as Record<string, unknown>;
37
37
  expect(schema.type).toBe("object");
38
38
  });
39
39
 
@@ -169,27 +169,21 @@ description: A test tool
169
169
  test("parses complete YAML manifest", () => {
170
170
  const yaml = `
171
171
  enact: "2.0.0"
172
- name: acme/utils/greeter
172
+ name: acme/greeter
173
173
  description: Greets users
174
174
  version: "1.0.0"
175
175
  from: node:18-alpine
176
- command: "echo 'Hello \${name}'"
177
176
  timeout: 30s
178
177
  license: MIT
179
178
  tags:
180
179
  - greeting
181
180
  - utility
182
- inputSchema:
183
- type: object
184
- properties:
185
- name:
186
- type: string
187
- required:
188
- - name
181
+ scripts:
182
+ greet: "echo 'Hello {{name}}'"
189
183
  `;
190
184
  const result = parseManifest(yaml, "yaml");
191
185
  expect(result.manifest.enact).toBe("2.0.0");
192
- expect(result.manifest.name).toBe("acme/utils/greeter");
186
+ expect(result.manifest.name).toBe("acme/greeter");
193
187
  expect(result.manifest.from).toBe("node:18-alpine");
194
188
  expect(result.manifest.tags).toContain("greeting");
195
189
  });
@@ -266,12 +260,14 @@ Body here.
266
260
 
267
261
  describe("detectFormat", () => {
268
262
  test("detects .yaml extension", () => {
263
+ expect(detectFormat("skill.yaml")).toBe("yaml");
269
264
  expect(detectFormat("enact.yaml")).toBe("yaml");
270
265
  expect(detectFormat("tool.yaml")).toBe("yaml");
271
- expect(detectFormat("/path/to/enact.yaml")).toBe("yaml");
266
+ expect(detectFormat("/path/to/skill.yaml")).toBe("yaml");
272
267
  });
273
268
 
274
269
  test("detects .yml extension", () => {
270
+ expect(detectFormat("skill.yml")).toBe("yaml");
275
271
  expect(detectFormat("enact.yml")).toBe("yaml");
276
272
  expect(detectFormat("/path/to/tool.yml")).toBe("yaml");
277
273
  });
@@ -302,7 +298,7 @@ Body here.
302
298
  name: org/tool
303
299
  description: Test
304
300
  `;
305
- const result = parseManifestAuto(yaml, "enact.yaml");
301
+ const result = parseManifestAuto(yaml, "skill.yaml");
306
302
  expect(result.manifest.name).toBe("org/tool");
307
303
  expect(result.format).toBe("yaml");
308
304
  });
@@ -24,7 +24,7 @@ describe("manifest validator", () => {
24
24
  test("validates complete manifest", () => {
25
25
  const manifest = {
26
26
  enact: "2.0.0",
27
- name: "acme/utils/greeter",
27
+ name: "acme/greeter",
28
28
  description: "Greets users by name",
29
29
  version: "1.0.0",
30
30
  from: "node:18-alpine",
@@ -32,12 +32,8 @@ describe("manifest validator", () => {
32
32
  timeout: "30s",
33
33
  license: "MIT",
34
34
  tags: ["greeting", "utility"],
35
- inputSchema: {
36
- type: "object",
37
- properties: {
38
- name: { type: "string" },
39
- },
40
- required: ["name"],
35
+ scripts: {
36
+ greet: "echo 'Hello {{name}}'",
41
37
  },
42
38
  outputSchema: {
43
39
  type: "object",
@@ -222,13 +218,7 @@ describe("manifest validator", () => {
222
218
  });
223
219
 
224
220
  test("accepts valid tool name formats", () => {
225
- const names = [
226
- "org/tool",
227
- "acme/utils/greeter",
228
- "my-org/category/sub-category/tool-name",
229
- "a/b",
230
- "org_name/tool_name",
231
- ];
221
+ const names = ["org/tool", "acme/greeter", "@my-org/tool-name", "a/b", "org_name/tool_name"];
232
222
 
233
223
  for (const name of names) {
234
224
  const manifest = { name, description: "Test" };
@@ -405,7 +395,7 @@ describe("manifest validator", () => {
405
395
  describe("isValidToolName", () => {
406
396
  test("returns true for valid names", () => {
407
397
  expect(isValidToolName("org/tool")).toBe(true);
408
- expect(isValidToolName("acme/utils/greeter")).toBe(true);
398
+ expect(isValidToolName("@acme/greeter")).toBe(true);
409
399
  expect(isValidToolName("my-org/my-tool")).toBe(true);
410
400
  expect(isValidToolName("org_name/tool_name")).toBe(true);
411
401
  });
@@ -425,9 +415,9 @@ describe("manifest validator", () => {
425
415
  expect(isValidLocalToolName("simple")).toBe(true);
426
416
  });
427
417
 
428
- test("returns true for hierarchical names", () => {
418
+ test("returns true for scoped names", () => {
429
419
  expect(isValidLocalToolName("org/tool")).toBe(true);
430
- expect(isValidLocalToolName("acme/utils/greeter")).toBe(true);
420
+ expect(isValidLocalToolName("@acme/greeter")).toBe(true);
431
421
  });
432
422
 
433
423
  test("returns false for invalid names", () => {
@@ -29,7 +29,7 @@ describe("manifest types", () => {
29
29
 
30
30
  test("accepts full manifest with all fields", () => {
31
31
  const manifest: ToolManifest = {
32
- name: "acme/utils/greeter",
32
+ name: "acme/greeter",
33
33
  description: "Greets users by name",
34
34
  enact: "2.0.0",
35
35
  version: "1.0.0",
@@ -38,12 +38,8 @@ describe("manifest types", () => {
38
38
  timeout: "30s",
39
39
  license: "MIT",
40
40
  tags: ["greeting", "utility"],
41
- inputSchema: {
42
- type: "object",
43
- properties: {
44
- name: { type: "string" },
45
- },
46
- required: ["name"],
41
+ scripts: {
42
+ greet: "echo 'Hello {{name}}'",
47
43
  },
48
44
  outputSchema: {
49
45
  type: "object",
@@ -90,7 +86,7 @@ describe("manifest types", () => {
90
86
  "x-custom-field": "custom value",
91
87
  };
92
88
 
93
- expect(manifest.name).toBe("acme/utils/greeter");
89
+ expect(manifest.name).toBe("acme/greeter");
94
90
  expect(manifest.enact).toBe("2.0.0");
95
91
  expect(manifest.env?.API_KEY?.secret).toBe(true);
96
92
  expect(manifest.annotations?.readOnlyHint).toBe(true);
@@ -314,7 +310,7 @@ describe("manifest types", () => {
314
310
  },
315
311
  sourceDir: "/home/user/.enact/tools/org/tool",
316
312
  location: "user",
317
- manifestPath: "/home/user/.enact/tools/org/tool/enact.yaml",
313
+ manifestPath: "/home/user/.enact/tools/org/tool/skill.yaml",
318
314
  version: "1.0.0",
319
315
  };
320
316
 
@@ -336,7 +332,7 @@ describe("manifest types", () => {
336
332
  manifest: { name: "test", description: "test" },
337
333
  sourceDir: "/test",
338
334
  location: loc,
339
- manifestPath: "/test/enact.yaml",
335
+ manifestPath: "/test/skill.yaml",
340
336
  };
341
337
  expect(resolution.location).toBe(loc);
342
338
  }
@@ -346,10 +342,12 @@ describe("manifest types", () => {
346
342
  describe("constants", () => {
347
343
  test("MANIFEST_FILES contains expected files", () => {
348
344
  expect(MANIFEST_FILES).toContain("SKILL.md");
345
+ expect(MANIFEST_FILES).toContain("skill.yaml");
346
+ expect(MANIFEST_FILES).toContain("skill.yml");
349
347
  expect(MANIFEST_FILES).toContain("enact.md");
350
348
  expect(MANIFEST_FILES).toContain("enact.yaml");
351
349
  expect(MANIFEST_FILES).toContain("enact.yml");
352
- expect(MANIFEST_FILES.length).toBe(4);
350
+ expect(MANIFEST_FILES.length).toBe(6);
353
351
  });
354
352
 
355
353
  test("PACKAGE_MANIFEST_FILE is correct", () => {