@enactprotocol/cli 2.1.5 → 2.1.7
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 +100 -58
- 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 +112 -62
- 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 +199 -12
- package/tests/commands/learn.test.ts +231 -0
- package/tests/commands/run.test.ts +70 -1
- package/tsconfig.tsbuildinfo +1 -1
|
@@ -3,7 +3,7 @@
|
|
|
3
3
|
*/
|
|
4
4
|
|
|
5
5
|
import { afterEach, beforeEach, describe, expect, test } from "bun:test";
|
|
6
|
-
import { existsSync, mkdirSync, readFileSync, rmSync } from "node:fs";
|
|
6
|
+
import { existsSync, mkdirSync, readFileSync, rmSync, writeFileSync } from "node:fs";
|
|
7
7
|
import { join } from "node:path";
|
|
8
8
|
import { Command } from "commander";
|
|
9
9
|
import { configureInitCommand } from "../../src/commands/init";
|
|
@@ -107,7 +107,59 @@ describe("init command", () => {
|
|
|
107
107
|
}
|
|
108
108
|
});
|
|
109
109
|
|
|
110
|
-
test("default mode creates
|
|
110
|
+
test("default mode creates AGENTS.md for tool consumers", async () => {
|
|
111
|
+
const program = new Command();
|
|
112
|
+
program.exitOverride();
|
|
113
|
+
configureInitCommand(program);
|
|
114
|
+
|
|
115
|
+
const originalCwd = process.cwd();
|
|
116
|
+
process.chdir(testDir);
|
|
117
|
+
|
|
118
|
+
try {
|
|
119
|
+
await program.parseAsync(["node", "test", "init"]);
|
|
120
|
+
} catch {
|
|
121
|
+
// Command may throw due to exitOverride
|
|
122
|
+
} finally {
|
|
123
|
+
process.chdir(originalCwd);
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
const agentsPath = join(testDir, "AGENTS.md");
|
|
127
|
+
expect(existsSync(agentsPath)).toBe(true);
|
|
128
|
+
|
|
129
|
+
const content = readFileSync(agentsPath, "utf-8");
|
|
130
|
+
expect(content).toContain("enact search");
|
|
131
|
+
expect(content).toContain("enact install");
|
|
132
|
+
expect(content).toContain("Finding & Installing Tools");
|
|
133
|
+
|
|
134
|
+
// Should NOT create enact.md in default mode
|
|
135
|
+
const manifestPath = join(testDir, "enact.md");
|
|
136
|
+
expect(existsSync(manifestPath)).toBe(false);
|
|
137
|
+
});
|
|
138
|
+
|
|
139
|
+
test("default mode creates .enact/tools.json", async () => {
|
|
140
|
+
const program = new Command();
|
|
141
|
+
program.exitOverride();
|
|
142
|
+
configureInitCommand(program);
|
|
143
|
+
|
|
144
|
+
const originalCwd = process.cwd();
|
|
145
|
+
process.chdir(testDir);
|
|
146
|
+
|
|
147
|
+
try {
|
|
148
|
+
await program.parseAsync(["node", "test", "init"]);
|
|
149
|
+
} catch {
|
|
150
|
+
// Command may throw due to exitOverride
|
|
151
|
+
} finally {
|
|
152
|
+
process.chdir(originalCwd);
|
|
153
|
+
}
|
|
154
|
+
|
|
155
|
+
const toolsJsonPath = join(testDir, ".enact", "tools.json");
|
|
156
|
+
expect(existsSync(toolsJsonPath)).toBe(true);
|
|
157
|
+
|
|
158
|
+
const content = JSON.parse(readFileSync(toolsJsonPath, "utf-8"));
|
|
159
|
+
expect(content).toEqual({ tools: {} });
|
|
160
|
+
});
|
|
161
|
+
|
|
162
|
+
test("--tool mode creates enact.md", async () => {
|
|
111
163
|
const program = new Command();
|
|
112
164
|
program.exitOverride(); // Prevent process.exit
|
|
113
165
|
configureInitCommand(program);
|
|
@@ -117,7 +169,7 @@ describe("init command", () => {
|
|
|
117
169
|
process.chdir(testDir);
|
|
118
170
|
|
|
119
171
|
try {
|
|
120
|
-
await program.parseAsync(["node", "test", "init", "--name", "test/my-tool"]);
|
|
172
|
+
await program.parseAsync(["node", "test", "init", "--tool", "--name", "test/my-tool"]);
|
|
121
173
|
} catch {
|
|
122
174
|
// Command may throw due to exitOverride
|
|
123
175
|
} finally {
|
|
@@ -133,7 +185,7 @@ describe("init command", () => {
|
|
|
133
185
|
expect(content).toContain("command:");
|
|
134
186
|
});
|
|
135
187
|
|
|
136
|
-
test("
|
|
188
|
+
test("--tool mode creates AGENTS.md for tool development", async () => {
|
|
137
189
|
const program = new Command();
|
|
138
190
|
program.exitOverride();
|
|
139
191
|
configureInitCommand(program);
|
|
@@ -142,7 +194,7 @@ describe("init command", () => {
|
|
|
142
194
|
process.chdir(testDir);
|
|
143
195
|
|
|
144
196
|
try {
|
|
145
|
-
await program.parseAsync(["node", "test", "init", "--name", "test/my-tool"]);
|
|
197
|
+
await program.parseAsync(["node", "test", "init", "--tool", "--name", "test/my-tool"]);
|
|
146
198
|
} catch {
|
|
147
199
|
// Command may throw due to exitOverride
|
|
148
200
|
} finally {
|
|
@@ -215,6 +267,134 @@ describe("init command", () => {
|
|
|
215
267
|
expect(existsSync(join(testDir, "AGENTS.md"))).toBe(false);
|
|
216
268
|
});
|
|
217
269
|
|
|
270
|
+
test("--agent mode creates .enact/tools.json", async () => {
|
|
271
|
+
const program = new Command();
|
|
272
|
+
program.exitOverride();
|
|
273
|
+
configureInitCommand(program);
|
|
274
|
+
|
|
275
|
+
const originalCwd = process.cwd();
|
|
276
|
+
process.chdir(testDir);
|
|
277
|
+
|
|
278
|
+
try {
|
|
279
|
+
await program.parseAsync(["node", "test", "init", "--agent"]);
|
|
280
|
+
} catch {
|
|
281
|
+
// Command may throw due to exitOverride
|
|
282
|
+
} finally {
|
|
283
|
+
process.chdir(originalCwd);
|
|
284
|
+
}
|
|
285
|
+
|
|
286
|
+
const toolsJsonPath = join(testDir, ".enact", "tools.json");
|
|
287
|
+
expect(existsSync(toolsJsonPath)).toBe(true);
|
|
288
|
+
|
|
289
|
+
const content = JSON.parse(readFileSync(toolsJsonPath, "utf-8"));
|
|
290
|
+
expect(content).toEqual({ tools: {} });
|
|
291
|
+
});
|
|
292
|
+
|
|
293
|
+
test("--claude mode creates .enact/tools.json", async () => {
|
|
294
|
+
const program = new Command();
|
|
295
|
+
program.exitOverride();
|
|
296
|
+
configureInitCommand(program);
|
|
297
|
+
|
|
298
|
+
const originalCwd = process.cwd();
|
|
299
|
+
process.chdir(testDir);
|
|
300
|
+
|
|
301
|
+
try {
|
|
302
|
+
await program.parseAsync(["node", "test", "init", "--claude"]);
|
|
303
|
+
} catch {
|
|
304
|
+
// Command may throw due to exitOverride
|
|
305
|
+
} finally {
|
|
306
|
+
process.chdir(originalCwd);
|
|
307
|
+
}
|
|
308
|
+
|
|
309
|
+
const toolsJsonPath = join(testDir, ".enact", "tools.json");
|
|
310
|
+
expect(existsSync(toolsJsonPath)).toBe(true);
|
|
311
|
+
|
|
312
|
+
const content = JSON.parse(readFileSync(toolsJsonPath, "utf-8"));
|
|
313
|
+
expect(content).toEqual({ tools: {} });
|
|
314
|
+
});
|
|
315
|
+
|
|
316
|
+
test("--agent mode with --force overwrites existing .enact/tools.json", async () => {
|
|
317
|
+
// Create existing .enact/tools.json with some content
|
|
318
|
+
const enactDir = join(testDir, ".enact");
|
|
319
|
+
mkdirSync(enactDir, { recursive: true });
|
|
320
|
+
const toolsJsonPath = join(enactDir, "tools.json");
|
|
321
|
+
const existingContent = { tools: { "some/tool": "1.0.0" } };
|
|
322
|
+
writeFileSync(toolsJsonPath, JSON.stringify(existingContent));
|
|
323
|
+
|
|
324
|
+
const program = new Command();
|
|
325
|
+
program.exitOverride();
|
|
326
|
+
configureInitCommand(program);
|
|
327
|
+
|
|
328
|
+
const originalCwd = process.cwd();
|
|
329
|
+
process.chdir(testDir);
|
|
330
|
+
|
|
331
|
+
try {
|
|
332
|
+
await program.parseAsync(["node", "test", "init", "--agent", "--force"]);
|
|
333
|
+
} catch {
|
|
334
|
+
// Command may throw due to exitOverride
|
|
335
|
+
} finally {
|
|
336
|
+
process.chdir(originalCwd);
|
|
337
|
+
}
|
|
338
|
+
|
|
339
|
+
const content = JSON.parse(readFileSync(toolsJsonPath, "utf-8"));
|
|
340
|
+
expect(content).toEqual({ tools: {} });
|
|
341
|
+
});
|
|
342
|
+
|
|
343
|
+
test("--agent mode preserves existing .enact/tools.json without --force", async () => {
|
|
344
|
+
// Create existing .enact/tools.json with some content
|
|
345
|
+
const enactDir = join(testDir, ".enact");
|
|
346
|
+
mkdirSync(enactDir, { recursive: true });
|
|
347
|
+
const toolsJsonPath = join(enactDir, "tools.json");
|
|
348
|
+
const existingContent = { tools: { "some/tool": "1.0.0" } };
|
|
349
|
+
writeFileSync(toolsJsonPath, JSON.stringify(existingContent));
|
|
350
|
+
|
|
351
|
+
// Also create AGENTS.md so the command doesn't fail early
|
|
352
|
+
writeFileSync(join(testDir, "AGENTS.md"), "existing");
|
|
353
|
+
|
|
354
|
+
const program = new Command();
|
|
355
|
+
program.exitOverride();
|
|
356
|
+
configureInitCommand(program);
|
|
357
|
+
|
|
358
|
+
const originalCwd = process.cwd();
|
|
359
|
+
process.chdir(testDir);
|
|
360
|
+
|
|
361
|
+
try {
|
|
362
|
+
// Without --force, AGENTS.md check will fail and return early
|
|
363
|
+
// So we need to test with --force on AGENTS.md but not tools.json
|
|
364
|
+
// Actually the --force flag applies to both, so let's just verify
|
|
365
|
+
// tools.json is preserved when it exists and no --force
|
|
366
|
+
await program.parseAsync(["node", "test", "init", "--agent"]);
|
|
367
|
+
} catch {
|
|
368
|
+
// Command may throw due to exitOverride or warning about existing file
|
|
369
|
+
} finally {
|
|
370
|
+
process.chdir(originalCwd);
|
|
371
|
+
}
|
|
372
|
+
|
|
373
|
+
// tools.json should be preserved since AGENTS.md existed and no --force was used
|
|
374
|
+
const content = JSON.parse(readFileSync(toolsJsonPath, "utf-8"));
|
|
375
|
+
expect(content).toEqual(existingContent);
|
|
376
|
+
});
|
|
377
|
+
|
|
378
|
+
test("--tool mode does NOT create .enact/tools.json", async () => {
|
|
379
|
+
const program = new Command();
|
|
380
|
+
program.exitOverride();
|
|
381
|
+
configureInitCommand(program);
|
|
382
|
+
|
|
383
|
+
const originalCwd = process.cwd();
|
|
384
|
+
process.chdir(testDir);
|
|
385
|
+
|
|
386
|
+
try {
|
|
387
|
+
await program.parseAsync(["node", "test", "init", "--tool", "--name", "test/my-tool"]);
|
|
388
|
+
} catch {
|
|
389
|
+
// Command may throw due to exitOverride
|
|
390
|
+
} finally {
|
|
391
|
+
process.chdir(originalCwd);
|
|
392
|
+
}
|
|
393
|
+
|
|
394
|
+
const toolsJsonPath = join(testDir, ".enact", "tools.json");
|
|
395
|
+
expect(existsSync(toolsJsonPath)).toBe(false);
|
|
396
|
+
});
|
|
397
|
+
|
|
218
398
|
test("enact.md contains valid YAML frontmatter", async () => {
|
|
219
399
|
const program = new Command();
|
|
220
400
|
program.exitOverride();
|
|
@@ -224,7 +404,14 @@ describe("init command", () => {
|
|
|
224
404
|
process.chdir(testDir);
|
|
225
405
|
|
|
226
406
|
try {
|
|
227
|
-
await program.parseAsync([
|
|
407
|
+
await program.parseAsync([
|
|
408
|
+
"node",
|
|
409
|
+
"test",
|
|
410
|
+
"init",
|
|
411
|
+
"--tool",
|
|
412
|
+
"--name",
|
|
413
|
+
"myorg/utils/greeter",
|
|
414
|
+
]);
|
|
228
415
|
} catch {
|
|
229
416
|
// Command may throw due to exitOverride
|
|
230
417
|
} finally {
|
|
@@ -255,7 +442,7 @@ describe("init command", () => {
|
|
|
255
442
|
process.chdir(testDir);
|
|
256
443
|
|
|
257
444
|
try {
|
|
258
|
-
await program.parseAsync(["node", "test", "init", "--name", "test/tool"]);
|
|
445
|
+
await program.parseAsync(["node", "test", "init", "--tool", "--name", "test/tool"]);
|
|
259
446
|
} catch {
|
|
260
447
|
// Command may throw due to exitOverride
|
|
261
448
|
} finally {
|
|
@@ -299,16 +486,16 @@ describe("init command", () => {
|
|
|
299
486
|
});
|
|
300
487
|
|
|
301
488
|
describe("option conflicts", () => {
|
|
302
|
-
test("--
|
|
489
|
+
test("--agent is the default when no mode specified", () => {
|
|
303
490
|
const program = new Command();
|
|
304
491
|
configureInitCommand(program);
|
|
305
492
|
|
|
306
493
|
const initCmd = program.commands.find((cmd) => cmd.name() === "init");
|
|
307
494
|
const opts = initCmd?.options ?? [];
|
|
308
|
-
const
|
|
495
|
+
const agentOpt = opts.find((o) => o.long === "--agent");
|
|
309
496
|
|
|
310
497
|
// Description should indicate it's the default
|
|
311
|
-
expect(
|
|
498
|
+
expect(agentOpt?.description).toContain("default");
|
|
312
499
|
});
|
|
313
500
|
});
|
|
314
501
|
|
|
@@ -328,7 +515,7 @@ describe("init command", () => {
|
|
|
328
515
|
process.chdir(testDir);
|
|
329
516
|
|
|
330
517
|
try {
|
|
331
|
-
await program.parseAsync(["node", "test", "init", "--name", "test/tool"]);
|
|
518
|
+
await program.parseAsync(["node", "test", "init", "--tool", "--name", "test/tool"]);
|
|
332
519
|
} catch {
|
|
333
520
|
// Command may throw due to exitOverride
|
|
334
521
|
} finally {
|
|
@@ -367,7 +554,7 @@ describe("init command", () => {
|
|
|
367
554
|
process.chdir(testDir);
|
|
368
555
|
|
|
369
556
|
try {
|
|
370
|
-
await program.parseAsync(["node", "test", "init", "--name", "test/tool"]);
|
|
557
|
+
await program.parseAsync(["node", "test", "init", "--tool", "--name", "test/tool"]);
|
|
371
558
|
} catch {
|
|
372
559
|
// Command may throw due to exitOverride
|
|
373
560
|
} finally {
|
|
@@ -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
|
});
|