@enactprotocol/cli 2.1.4 → 2.1.6
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/dist/commands/index.d.ts +1 -0
- package/dist/commands/index.d.ts.map +1 -1
- package/dist/commands/index.js +1 -0
- package/dist/commands/index.js.map +1 -1
- package/dist/commands/init/index.d.ts.map +1 -1
- package/dist/commands/init/index.js +186 -28
- package/dist/commands/init/index.js.map +1 -1
- package/dist/commands/learn/index.d.ts +12 -0
- package/dist/commands/learn/index.d.ts.map +1 -0
- package/dist/commands/learn/index.js +90 -0
- package/dist/commands/learn/index.js.map +1 -0
- package/dist/commands/run/index.d.ts.map +1 -1
- package/dist/commands/run/index.js +36 -6
- package/dist/commands/run/index.js.map +1 -1
- package/dist/index.d.ts +1 -1
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +4 -3
- package/dist/index.js.map +1 -1
- package/package.json +5 -5
- package/src/commands/index.ts +1 -0
- package/src/commands/init/index.ts +186 -28
- package/src/commands/learn/index.ts +107 -0
- package/src/commands/run/index.ts +39 -5
- package/src/index.ts +4 -2
- package/tests/commands/init.test.ts +4 -3
- package/tests/commands/learn.test.ts +231 -0
- package/tests/commands/run.test.ts +70 -1
- package/tsconfig.tsbuildinfo +1 -1
|
@@ -0,0 +1,231 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Tests for the learn command
|
|
3
|
+
*/
|
|
4
|
+
|
|
5
|
+
import { describe, expect, test } from "bun:test";
|
|
6
|
+
import type { ToolVersionInfo } from "@enactprotocol/api";
|
|
7
|
+
import { Command } from "commander";
|
|
8
|
+
import { configureLearnCommand } from "../../src/commands/learn";
|
|
9
|
+
|
|
10
|
+
describe("learn command", () => {
|
|
11
|
+
describe("command configuration", () => {
|
|
12
|
+
test("configures learn command on program", () => {
|
|
13
|
+
const program = new Command();
|
|
14
|
+
configureLearnCommand(program);
|
|
15
|
+
|
|
16
|
+
const learnCmd = program.commands.find((cmd) => cmd.name() === "learn");
|
|
17
|
+
expect(learnCmd).toBeDefined();
|
|
18
|
+
});
|
|
19
|
+
|
|
20
|
+
test("has correct description", () => {
|
|
21
|
+
const program = new Command();
|
|
22
|
+
configureLearnCommand(program);
|
|
23
|
+
|
|
24
|
+
const learnCmd = program.commands.find((cmd) => cmd.name() === "learn");
|
|
25
|
+
expect(learnCmd?.description()).toBe("Display documentation (enact.md) for a tool");
|
|
26
|
+
});
|
|
27
|
+
|
|
28
|
+
test("accepts tool argument", () => {
|
|
29
|
+
const program = new Command();
|
|
30
|
+
configureLearnCommand(program);
|
|
31
|
+
|
|
32
|
+
const learnCmd = program.commands.find((cmd) => cmd.name() === "learn");
|
|
33
|
+
const args = learnCmd?.registeredArguments ?? [];
|
|
34
|
+
expect(args.length).toBeGreaterThan(0);
|
|
35
|
+
expect(args[0]?.name()).toBe("tool");
|
|
36
|
+
});
|
|
37
|
+
|
|
38
|
+
test("has --ver option for specifying version", () => {
|
|
39
|
+
const program = new Command();
|
|
40
|
+
configureLearnCommand(program);
|
|
41
|
+
|
|
42
|
+
const learnCmd = program.commands.find((cmd) => cmd.name() === "learn");
|
|
43
|
+
const opts = learnCmd?.options ?? [];
|
|
44
|
+
const verOpt = opts.find((o) => o.long === "--ver");
|
|
45
|
+
expect(verOpt).toBeDefined();
|
|
46
|
+
});
|
|
47
|
+
|
|
48
|
+
test("has --json option", () => {
|
|
49
|
+
const program = new Command();
|
|
50
|
+
configureLearnCommand(program);
|
|
51
|
+
|
|
52
|
+
const learnCmd = program.commands.find((cmd) => cmd.name() === "learn");
|
|
53
|
+
const opts = learnCmd?.options ?? [];
|
|
54
|
+
const jsonOpt = opts.find((o) => o.long === "--json");
|
|
55
|
+
expect(jsonOpt).toBeDefined();
|
|
56
|
+
});
|
|
57
|
+
|
|
58
|
+
test("does not have --verbose option (always shows full docs)", () => {
|
|
59
|
+
const program = new Command();
|
|
60
|
+
configureLearnCommand(program);
|
|
61
|
+
|
|
62
|
+
const learnCmd = program.commands.find((cmd) => cmd.name() === "learn");
|
|
63
|
+
const opts = learnCmd?.options ?? [];
|
|
64
|
+
const verboseOpt = opts.find((o) => o.long === "--verbose");
|
|
65
|
+
expect(verboseOpt).toBeUndefined();
|
|
66
|
+
});
|
|
67
|
+
});
|
|
68
|
+
|
|
69
|
+
describe("tool name parsing", () => {
|
|
70
|
+
test("parses simple tool name", () => {
|
|
71
|
+
const toolName = "my-tool";
|
|
72
|
+
expect(toolName).not.toContain("@");
|
|
73
|
+
expect(toolName).not.toContain("/");
|
|
74
|
+
});
|
|
75
|
+
|
|
76
|
+
test("parses namespaced tool name", () => {
|
|
77
|
+
const toolName = "enact/context7/docs";
|
|
78
|
+
const parts = toolName.split("/");
|
|
79
|
+
expect(parts[0]).toBe("enact");
|
|
80
|
+
expect(parts[1]).toBe("context7");
|
|
81
|
+
expect(parts[2]).toBe("docs");
|
|
82
|
+
});
|
|
83
|
+
|
|
84
|
+
test("parses scoped tool name", () => {
|
|
85
|
+
const toolName = "@org/my-tool";
|
|
86
|
+
expect(toolName.startsWith("@")).toBe(true);
|
|
87
|
+
const parts = toolName.slice(1).split("/");
|
|
88
|
+
expect(parts[0]).toBe("org");
|
|
89
|
+
expect(parts[1]).toBe("my-tool");
|
|
90
|
+
});
|
|
91
|
+
});
|
|
92
|
+
|
|
93
|
+
describe("documentation content", () => {
|
|
94
|
+
test("ToolVersionInfo type includes rawManifest field for enact.md content", () => {
|
|
95
|
+
const mockVersion: ToolVersionInfo = {
|
|
96
|
+
name: "test/tool",
|
|
97
|
+
version: "1.0.0",
|
|
98
|
+
description: "Test tool",
|
|
99
|
+
license: "MIT",
|
|
100
|
+
yanked: false,
|
|
101
|
+
manifest: { enact: "2.0.0" },
|
|
102
|
+
rawManifest: "---\nenact: 2.0.0\n---\n# Test Tool\n\nThis is a test tool.",
|
|
103
|
+
bundle: {
|
|
104
|
+
hash: "sha256:abc123",
|
|
105
|
+
size: 1024,
|
|
106
|
+
downloadUrl: "https://example.com/bundle.tar.gz",
|
|
107
|
+
},
|
|
108
|
+
attestations: [],
|
|
109
|
+
publishedBy: { username: "testuser" },
|
|
110
|
+
publishedAt: new Date(),
|
|
111
|
+
downloads: 100,
|
|
112
|
+
};
|
|
113
|
+
|
|
114
|
+
expect(mockVersion.rawManifest).toBeDefined();
|
|
115
|
+
expect(mockVersion.rawManifest).toContain("# Test Tool");
|
|
116
|
+
});
|
|
117
|
+
|
|
118
|
+
test("ToolVersionInfo allows undefined rawManifest", () => {
|
|
119
|
+
const mockVersion: ToolVersionInfo = {
|
|
120
|
+
name: "test/tool",
|
|
121
|
+
version: "1.0.0",
|
|
122
|
+
description: "Test tool",
|
|
123
|
+
license: "MIT",
|
|
124
|
+
yanked: false,
|
|
125
|
+
manifest: { enact: "2.0.0" },
|
|
126
|
+
// rawManifest is optional - not provided
|
|
127
|
+
bundle: {
|
|
128
|
+
hash: "sha256:abc123",
|
|
129
|
+
size: 1024,
|
|
130
|
+
downloadUrl: "https://example.com/bundle.tar.gz",
|
|
131
|
+
},
|
|
132
|
+
attestations: [],
|
|
133
|
+
publishedBy: { username: "testuser" },
|
|
134
|
+
publishedAt: new Date(),
|
|
135
|
+
downloads: 100,
|
|
136
|
+
};
|
|
137
|
+
|
|
138
|
+
expect(mockVersion.rawManifest).toBeUndefined();
|
|
139
|
+
});
|
|
140
|
+
|
|
141
|
+
test("enact.md content should contain frontmatter and markdown", () => {
|
|
142
|
+
const enactMdContent = `---
|
|
143
|
+
enact: 2.0.0
|
|
144
|
+
name: test/tool
|
|
145
|
+
version: 1.0.0
|
|
146
|
+
---
|
|
147
|
+
|
|
148
|
+
# Test Tool
|
|
149
|
+
|
|
150
|
+
Documentation here.`;
|
|
151
|
+
|
|
152
|
+
// Verify frontmatter is present
|
|
153
|
+
expect(enactMdContent).toContain("---");
|
|
154
|
+
expect(enactMdContent).toContain("enact: 2.0.0");
|
|
155
|
+
|
|
156
|
+
// Verify markdown content
|
|
157
|
+
expect(enactMdContent).toContain("# Test Tool");
|
|
158
|
+
expect(enactMdContent).toContain("Documentation here.");
|
|
159
|
+
});
|
|
160
|
+
|
|
161
|
+
test("documentation includes parameter descriptions", () => {
|
|
162
|
+
const enactMdContent = `---
|
|
163
|
+
enact: 2.0.0
|
|
164
|
+
name: enact/context7/docs
|
|
165
|
+
version: 1.0.0
|
|
166
|
+
inputSchema:
|
|
167
|
+
type: object
|
|
168
|
+
properties:
|
|
169
|
+
library_name:
|
|
170
|
+
type: string
|
|
171
|
+
description: The name of the library to fetch documentation for
|
|
172
|
+
---
|
|
173
|
+
|
|
174
|
+
# Context7 Documentation Fetcher
|
|
175
|
+
|
|
176
|
+
Fetches up-to-date documentation for any library.
|
|
177
|
+
|
|
178
|
+
## Parameters
|
|
179
|
+
|
|
180
|
+
- **library_name** (required): The name of the library to fetch documentation for
|
|
181
|
+
`;
|
|
182
|
+
|
|
183
|
+
expect(enactMdContent).toContain("library_name");
|
|
184
|
+
expect(enactMdContent).toContain("Parameters");
|
|
185
|
+
expect(enactMdContent).toContain("required");
|
|
186
|
+
});
|
|
187
|
+
|
|
188
|
+
test("documentation includes usage examples", () => {
|
|
189
|
+
const enactMdContent = `---
|
|
190
|
+
enact: 2.0.0
|
|
191
|
+
name: test/tool
|
|
192
|
+
---
|
|
193
|
+
|
|
194
|
+
# Test Tool
|
|
195
|
+
|
|
196
|
+
## Usage
|
|
197
|
+
|
|
198
|
+
\`\`\`bash
|
|
199
|
+
enact run test/tool --input '{"query": "hello"}'
|
|
200
|
+
\`\`\`
|
|
201
|
+
`;
|
|
202
|
+
|
|
203
|
+
expect(enactMdContent).toContain("## Usage");
|
|
204
|
+
expect(enactMdContent).toContain("enact run");
|
|
205
|
+
});
|
|
206
|
+
});
|
|
207
|
+
|
|
208
|
+
describe("JSON output format", () => {
|
|
209
|
+
test("JSON output includes name, version, and documentation", () => {
|
|
210
|
+
const jsonOutput = {
|
|
211
|
+
name: "enact/context7/docs",
|
|
212
|
+
version: "1.0.1",
|
|
213
|
+
documentation: "---\nenact: 2.0.0\n---\n# Context7 Docs\n\nFetches documentation.",
|
|
214
|
+
};
|
|
215
|
+
|
|
216
|
+
expect(jsonOutput.name).toBe("enact/context7/docs");
|
|
217
|
+
expect(jsonOutput.version).toBe("1.0.1");
|
|
218
|
+
expect(jsonOutput.documentation).toContain("# Context7 Docs");
|
|
219
|
+
});
|
|
220
|
+
|
|
221
|
+
test("JSON output handles missing documentation", () => {
|
|
222
|
+
const jsonOutput = {
|
|
223
|
+
name: "test/tool",
|
|
224
|
+
version: "1.0.0",
|
|
225
|
+
documentation: null,
|
|
226
|
+
};
|
|
227
|
+
|
|
228
|
+
expect(jsonOutput.documentation).toBeNull();
|
|
229
|
+
});
|
|
230
|
+
});
|
|
231
|
+
});
|
|
@@ -3,7 +3,7 @@
|
|
|
3
3
|
*/
|
|
4
4
|
|
|
5
5
|
import { afterAll, beforeAll, describe, expect, test } from "bun:test";
|
|
6
|
-
import { existsSync, mkdirSync, rmSync } from "node:fs";
|
|
6
|
+
import { existsSync, mkdirSync, rmSync, writeFileSync } from "node:fs";
|
|
7
7
|
import { join } from "node:path";
|
|
8
8
|
import { Command } from "commander";
|
|
9
9
|
import { configureRunCommand } from "../../src/commands/run";
|
|
@@ -71,6 +71,16 @@ describe("run command", () => {
|
|
|
71
71
|
expect(inputOpt).toBeDefined();
|
|
72
72
|
});
|
|
73
73
|
|
|
74
|
+
test("has --input-file option for JSON file input", () => {
|
|
75
|
+
const program = new Command();
|
|
76
|
+
configureRunCommand(program);
|
|
77
|
+
|
|
78
|
+
const runCmd = program.commands.find((cmd) => cmd.name() === "run");
|
|
79
|
+
const opts = runCmd?.options ?? [];
|
|
80
|
+
const inputFileOpt = opts.find((o) => o.long === "--input-file");
|
|
81
|
+
expect(inputFileOpt).toBeDefined();
|
|
82
|
+
});
|
|
83
|
+
|
|
74
84
|
test("has --timeout option", () => {
|
|
75
85
|
const program = new Command();
|
|
76
86
|
configureRunCommand(program);
|
|
@@ -228,4 +238,63 @@ describe("run command", () => {
|
|
|
228
238
|
expect(parseTimeout("30")).toBe(30000);
|
|
229
239
|
});
|
|
230
240
|
});
|
|
241
|
+
|
|
242
|
+
describe("input file handling", () => {
|
|
243
|
+
test("JSON input file can be parsed", () => {
|
|
244
|
+
// Create a test JSON file
|
|
245
|
+
const inputFilePath = join(FIXTURES_DIR, "test-inputs.json");
|
|
246
|
+
const inputData = { name: "Alice", count: 5, nested: { key: "value" } };
|
|
247
|
+
writeFileSync(inputFilePath, JSON.stringify(inputData));
|
|
248
|
+
|
|
249
|
+
// Verify the file can be read and parsed
|
|
250
|
+
const content = require("node:fs").readFileSync(inputFilePath, "utf-8");
|
|
251
|
+
const parsed = JSON.parse(content);
|
|
252
|
+
|
|
253
|
+
expect(parsed.name).toBe("Alice");
|
|
254
|
+
expect(parsed.count).toBe(5);
|
|
255
|
+
expect(parsed.nested.key).toBe("value");
|
|
256
|
+
});
|
|
257
|
+
|
|
258
|
+
test("JSON input file with optional params can omit them", () => {
|
|
259
|
+
// This is the recommended pattern for optional params
|
|
260
|
+
const inputFilePath = join(FIXTURES_DIR, "optional-inputs.json");
|
|
261
|
+
// Only required param provided, optional params omitted
|
|
262
|
+
const inputData = { name: "Alice" };
|
|
263
|
+
writeFileSync(inputFilePath, JSON.stringify(inputData));
|
|
264
|
+
|
|
265
|
+
const content = require("node:fs").readFileSync(inputFilePath, "utf-8");
|
|
266
|
+
const parsed = JSON.parse(content);
|
|
267
|
+
|
|
268
|
+
expect(parsed.name).toBe("Alice");
|
|
269
|
+
expect(parsed.greeting).toBeUndefined();
|
|
270
|
+
});
|
|
271
|
+
|
|
272
|
+
test("JSON input file with explicit empty values", () => {
|
|
273
|
+
// User can explicitly set empty values for optional params
|
|
274
|
+
const inputFilePath = join(FIXTURES_DIR, "explicit-empty.json");
|
|
275
|
+
const inputData = { name: "Alice", prefix: "", suffix: null };
|
|
276
|
+
writeFileSync(inputFilePath, JSON.stringify(inputData));
|
|
277
|
+
|
|
278
|
+
const content = require("node:fs").readFileSync(inputFilePath, "utf-8");
|
|
279
|
+
const parsed = JSON.parse(content);
|
|
280
|
+
|
|
281
|
+
expect(parsed.name).toBe("Alice");
|
|
282
|
+
expect(parsed.prefix).toBe("");
|
|
283
|
+
expect(parsed.suffix).toBeNull();
|
|
284
|
+
});
|
|
285
|
+
|
|
286
|
+
test("input priority: --input overrides --args overrides --input-file", () => {
|
|
287
|
+
// Simulate the merge logic from parseInputArgs
|
|
288
|
+
const fromFile = { a: "file", b: "file", c: "file" };
|
|
289
|
+
const fromArgs = { b: "args", c: "args" };
|
|
290
|
+
const fromInput = { c: "input" };
|
|
291
|
+
|
|
292
|
+
// Merge in order: file -> args -> input
|
|
293
|
+
const merged = { ...fromFile, ...fromArgs, ...fromInput };
|
|
294
|
+
|
|
295
|
+
expect(merged.a).toBe("file"); // Only from file
|
|
296
|
+
expect(merged.b).toBe("args"); // Overridden by args
|
|
297
|
+
expect(merged.c).toBe("input"); // Overridden by input
|
|
298
|
+
});
|
|
299
|
+
});
|
|
231
300
|
});
|