@enactprotocol/cli 2.0.9 → 2.0.11
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/init/index.d.ts.map +1 -1
- package/dist/commands/init/index.js +75 -60
- package/dist/commands/init/index.js.map +1 -1
- package/dist/commands/init/templates/agent-agents.md +47 -0
- package/dist/commands/init/templates/claude.md +65 -0
- package/dist/commands/init/templates/tool-agents.md +56 -0
- package/dist/commands/init/templates/tool-enact.md +44 -0
- package/dist/index.d.ts +1 -1
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +1 -1
- package/dist/index.js.map +1 -1
- package/package.json +6 -6
- package/src/commands/init/index.ts +86 -61
- package/src/commands/init/templates/agent-agents.md +47 -0
- package/src/commands/init/templates/claude.md +65 -0
- package/src/commands/init/templates/tool-agents.md +56 -0
- package/src/commands/init/templates/tool-enact.md +44 -0
- package/src/index.ts +1 -1
- package/tests/commands/init.test.ts +387 -0
- package/tsconfig.tsbuildinfo +1 -1
|
@@ -0,0 +1,387 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Tests for the init command
|
|
3
|
+
*/
|
|
4
|
+
|
|
5
|
+
import { afterEach, beforeEach, describe, expect, test } from "bun:test";
|
|
6
|
+
import { existsSync, mkdirSync, readFileSync, rmSync } from "node:fs";
|
|
7
|
+
import { join } from "node:path";
|
|
8
|
+
import { Command } from "commander";
|
|
9
|
+
import { configureInitCommand } from "../../src/commands/init";
|
|
10
|
+
|
|
11
|
+
describe("init command", () => {
|
|
12
|
+
describe("command configuration", () => {
|
|
13
|
+
test("configures init command on program", () => {
|
|
14
|
+
const program = new Command();
|
|
15
|
+
configureInitCommand(program);
|
|
16
|
+
|
|
17
|
+
const initCmd = program.commands.find((cmd) => cmd.name() === "init");
|
|
18
|
+
expect(initCmd).toBeDefined();
|
|
19
|
+
});
|
|
20
|
+
|
|
21
|
+
test("has correct description", () => {
|
|
22
|
+
const program = new Command();
|
|
23
|
+
configureInitCommand(program);
|
|
24
|
+
|
|
25
|
+
const initCmd = program.commands.find((cmd) => cmd.name() === "init");
|
|
26
|
+
expect(initCmd?.description()).toBe("Initialize Enact in the current directory");
|
|
27
|
+
});
|
|
28
|
+
|
|
29
|
+
test("has --name option", () => {
|
|
30
|
+
const program = new Command();
|
|
31
|
+
configureInitCommand(program);
|
|
32
|
+
|
|
33
|
+
const initCmd = program.commands.find((cmd) => cmd.name() === "init");
|
|
34
|
+
const opts = initCmd?.options ?? [];
|
|
35
|
+
const nameOpt = opts.find((o) => o.long === "--name");
|
|
36
|
+
expect(nameOpt).toBeDefined();
|
|
37
|
+
expect(nameOpt?.short).toBe("-n");
|
|
38
|
+
});
|
|
39
|
+
|
|
40
|
+
test("has --force option", () => {
|
|
41
|
+
const program = new Command();
|
|
42
|
+
configureInitCommand(program);
|
|
43
|
+
|
|
44
|
+
const initCmd = program.commands.find((cmd) => cmd.name() === "init");
|
|
45
|
+
const opts = initCmd?.options ?? [];
|
|
46
|
+
const forceOpt = opts.find((o) => o.long === "--force");
|
|
47
|
+
expect(forceOpt).toBeDefined();
|
|
48
|
+
expect(forceOpt?.short).toBe("-f");
|
|
49
|
+
});
|
|
50
|
+
|
|
51
|
+
test("has --tool option", () => {
|
|
52
|
+
const program = new Command();
|
|
53
|
+
configureInitCommand(program);
|
|
54
|
+
|
|
55
|
+
const initCmd = program.commands.find((cmd) => cmd.name() === "init");
|
|
56
|
+
const opts = initCmd?.options ?? [];
|
|
57
|
+
const toolOpt = opts.find((o) => o.long === "--tool");
|
|
58
|
+
expect(toolOpt).toBeDefined();
|
|
59
|
+
});
|
|
60
|
+
|
|
61
|
+
test("has --agent option", () => {
|
|
62
|
+
const program = new Command();
|
|
63
|
+
configureInitCommand(program);
|
|
64
|
+
|
|
65
|
+
const initCmd = program.commands.find((cmd) => cmd.name() === "init");
|
|
66
|
+
const opts = initCmd?.options ?? [];
|
|
67
|
+
const agentOpt = opts.find((o) => o.long === "--agent");
|
|
68
|
+
expect(agentOpt).toBeDefined();
|
|
69
|
+
});
|
|
70
|
+
|
|
71
|
+
test("has --claude option", () => {
|
|
72
|
+
const program = new Command();
|
|
73
|
+
configureInitCommand(program);
|
|
74
|
+
|
|
75
|
+
const initCmd = program.commands.find((cmd) => cmd.name() === "init");
|
|
76
|
+
const opts = initCmd?.options ?? [];
|
|
77
|
+
const claudeOpt = opts.find((o) => o.long === "--claude");
|
|
78
|
+
expect(claudeOpt).toBeDefined();
|
|
79
|
+
});
|
|
80
|
+
|
|
81
|
+
test("has --verbose option", () => {
|
|
82
|
+
const program = new Command();
|
|
83
|
+
configureInitCommand(program);
|
|
84
|
+
|
|
85
|
+
const initCmd = program.commands.find((cmd) => cmd.name() === "init");
|
|
86
|
+
const opts = initCmd?.options ?? [];
|
|
87
|
+
const verboseOpt = opts.find((o) => o.long === "--verbose");
|
|
88
|
+
expect(verboseOpt).toBeDefined();
|
|
89
|
+
});
|
|
90
|
+
});
|
|
91
|
+
|
|
92
|
+
describe("file generation", () => {
|
|
93
|
+
const testDir = join(import.meta.dir, ".test-init-temp");
|
|
94
|
+
|
|
95
|
+
beforeEach(() => {
|
|
96
|
+
// Clean up before each test
|
|
97
|
+
if (existsSync(testDir)) {
|
|
98
|
+
rmSync(testDir, { recursive: true });
|
|
99
|
+
}
|
|
100
|
+
mkdirSync(testDir, { recursive: true });
|
|
101
|
+
});
|
|
102
|
+
|
|
103
|
+
afterEach(() => {
|
|
104
|
+
// Clean up after each test
|
|
105
|
+
if (existsSync(testDir)) {
|
|
106
|
+
rmSync(testDir, { recursive: true });
|
|
107
|
+
}
|
|
108
|
+
});
|
|
109
|
+
|
|
110
|
+
test("default mode creates enact.md", async () => {
|
|
111
|
+
const program = new Command();
|
|
112
|
+
program.exitOverride(); // Prevent process.exit
|
|
113
|
+
configureInitCommand(program);
|
|
114
|
+
|
|
115
|
+
// Change to test directory and run init
|
|
116
|
+
const originalCwd = process.cwd();
|
|
117
|
+
process.chdir(testDir);
|
|
118
|
+
|
|
119
|
+
try {
|
|
120
|
+
await program.parseAsync(["node", "test", "init", "--name", "test/my-tool"]);
|
|
121
|
+
} catch {
|
|
122
|
+
// Command may throw due to exitOverride
|
|
123
|
+
} finally {
|
|
124
|
+
process.chdir(originalCwd);
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
const manifestPath = join(testDir, "enact.md");
|
|
128
|
+
expect(existsSync(manifestPath)).toBe(true);
|
|
129
|
+
|
|
130
|
+
const content = readFileSync(manifestPath, "utf-8");
|
|
131
|
+
expect(content).toContain("name: test/my-tool");
|
|
132
|
+
expect(content).toContain("description:");
|
|
133
|
+
expect(content).toContain("command:");
|
|
134
|
+
});
|
|
135
|
+
|
|
136
|
+
test("default mode creates AGENTS.md for tool development", async () => {
|
|
137
|
+
const program = new Command();
|
|
138
|
+
program.exitOverride();
|
|
139
|
+
configureInitCommand(program);
|
|
140
|
+
|
|
141
|
+
const originalCwd = process.cwd();
|
|
142
|
+
process.chdir(testDir);
|
|
143
|
+
|
|
144
|
+
try {
|
|
145
|
+
await program.parseAsync(["node", "test", "init", "--name", "test/my-tool"]);
|
|
146
|
+
} catch {
|
|
147
|
+
// Command may throw due to exitOverride
|
|
148
|
+
} finally {
|
|
149
|
+
process.chdir(originalCwd);
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
const agentsPath = join(testDir, "AGENTS.md");
|
|
153
|
+
expect(existsSync(agentsPath)).toBe(true);
|
|
154
|
+
|
|
155
|
+
const content = readFileSync(agentsPath, "utf-8");
|
|
156
|
+
expect(content).toContain("enact run");
|
|
157
|
+
expect(content).toContain("enact.md");
|
|
158
|
+
expect(content).toContain("Parameter Substitution");
|
|
159
|
+
});
|
|
160
|
+
|
|
161
|
+
test("--agent mode creates AGENTS.md for tool consumers", async () => {
|
|
162
|
+
const program = new Command();
|
|
163
|
+
program.exitOverride();
|
|
164
|
+
configureInitCommand(program);
|
|
165
|
+
|
|
166
|
+
const originalCwd = process.cwd();
|
|
167
|
+
process.chdir(testDir);
|
|
168
|
+
|
|
169
|
+
try {
|
|
170
|
+
await program.parseAsync(["node", "test", "init", "--agent"]);
|
|
171
|
+
} catch {
|
|
172
|
+
// Command may throw due to exitOverride
|
|
173
|
+
} finally {
|
|
174
|
+
process.chdir(originalCwd);
|
|
175
|
+
}
|
|
176
|
+
|
|
177
|
+
const agentsPath = join(testDir, "AGENTS.md");
|
|
178
|
+
expect(existsSync(agentsPath)).toBe(true);
|
|
179
|
+
|
|
180
|
+
const content = readFileSync(agentsPath, "utf-8");
|
|
181
|
+
expect(content).toContain("enact search");
|
|
182
|
+
expect(content).toContain("enact install");
|
|
183
|
+
expect(content).toContain("Finding & Installing Tools");
|
|
184
|
+
|
|
185
|
+
// Should NOT create enact.md in agent mode
|
|
186
|
+
const manifestPath = join(testDir, "enact.md");
|
|
187
|
+
expect(existsSync(manifestPath)).toBe(false);
|
|
188
|
+
});
|
|
189
|
+
|
|
190
|
+
test("--claude mode creates CLAUDE.md", async () => {
|
|
191
|
+
const program = new Command();
|
|
192
|
+
program.exitOverride();
|
|
193
|
+
configureInitCommand(program);
|
|
194
|
+
|
|
195
|
+
const originalCwd = process.cwd();
|
|
196
|
+
process.chdir(testDir);
|
|
197
|
+
|
|
198
|
+
try {
|
|
199
|
+
await program.parseAsync(["node", "test", "init", "--claude"]);
|
|
200
|
+
} catch {
|
|
201
|
+
// Command may throw due to exitOverride
|
|
202
|
+
} finally {
|
|
203
|
+
process.chdir(originalCwd);
|
|
204
|
+
}
|
|
205
|
+
|
|
206
|
+
const claudePath = join(testDir, "CLAUDE.md");
|
|
207
|
+
expect(existsSync(claudePath)).toBe(true);
|
|
208
|
+
|
|
209
|
+
const content = readFileSync(claudePath, "utf-8");
|
|
210
|
+
expect(content).toContain("enact run");
|
|
211
|
+
expect(content).toContain("enact search");
|
|
212
|
+
|
|
213
|
+
// Should NOT create enact.md or AGENTS.md in claude mode
|
|
214
|
+
expect(existsSync(join(testDir, "enact.md"))).toBe(false);
|
|
215
|
+
expect(existsSync(join(testDir, "AGENTS.md"))).toBe(false);
|
|
216
|
+
});
|
|
217
|
+
|
|
218
|
+
test("enact.md contains valid YAML frontmatter", async () => {
|
|
219
|
+
const program = new Command();
|
|
220
|
+
program.exitOverride();
|
|
221
|
+
configureInitCommand(program);
|
|
222
|
+
|
|
223
|
+
const originalCwd = process.cwd();
|
|
224
|
+
process.chdir(testDir);
|
|
225
|
+
|
|
226
|
+
try {
|
|
227
|
+
await program.parseAsync(["node", "test", "init", "--name", "myorg/utils/greeter"]);
|
|
228
|
+
} catch {
|
|
229
|
+
// Command may throw due to exitOverride
|
|
230
|
+
} finally {
|
|
231
|
+
process.chdir(originalCwd);
|
|
232
|
+
}
|
|
233
|
+
|
|
234
|
+
const content = readFileSync(join(testDir, "enact.md"), "utf-8");
|
|
235
|
+
|
|
236
|
+
// Check frontmatter structure
|
|
237
|
+
expect(content.startsWith("---")).toBe(true);
|
|
238
|
+
expect(content).toContain("name: myorg/utils/greeter");
|
|
239
|
+
expect(content).toContain("version:");
|
|
240
|
+
expect(content).toContain("from:");
|
|
241
|
+
expect(content).toContain("inputSchema:");
|
|
242
|
+
expect(content).toContain("command:");
|
|
243
|
+
|
|
244
|
+
// Check it has closing frontmatter and markdown body
|
|
245
|
+
const parts = content.split("---");
|
|
246
|
+
expect(parts.length).toBeGreaterThanOrEqual(3); // empty, frontmatter, body
|
|
247
|
+
});
|
|
248
|
+
|
|
249
|
+
test("AGENTS.md for tools contains development instructions", async () => {
|
|
250
|
+
const program = new Command();
|
|
251
|
+
program.exitOverride();
|
|
252
|
+
configureInitCommand(program);
|
|
253
|
+
|
|
254
|
+
const originalCwd = process.cwd();
|
|
255
|
+
process.chdir(testDir);
|
|
256
|
+
|
|
257
|
+
try {
|
|
258
|
+
await program.parseAsync(["node", "test", "init", "--name", "test/tool"]);
|
|
259
|
+
} catch {
|
|
260
|
+
// Command may throw due to exitOverride
|
|
261
|
+
} finally {
|
|
262
|
+
process.chdir(originalCwd);
|
|
263
|
+
}
|
|
264
|
+
|
|
265
|
+
const content = readFileSync(join(testDir, "AGENTS.md"), "utf-8");
|
|
266
|
+
|
|
267
|
+
// Should contain tool development-specific content
|
|
268
|
+
expect(content).toContain("enact sign");
|
|
269
|
+
expect(content).toContain("enact publish");
|
|
270
|
+
expect(content).toContain("${param}");
|
|
271
|
+
expect(content).toContain("build:");
|
|
272
|
+
expect(content).toContain("/work");
|
|
273
|
+
});
|
|
274
|
+
|
|
275
|
+
test("AGENTS.md for agent projects contains usage instructions", async () => {
|
|
276
|
+
const program = new Command();
|
|
277
|
+
program.exitOverride();
|
|
278
|
+
configureInitCommand(program);
|
|
279
|
+
|
|
280
|
+
const originalCwd = process.cwd();
|
|
281
|
+
process.chdir(testDir);
|
|
282
|
+
|
|
283
|
+
try {
|
|
284
|
+
await program.parseAsync(["node", "test", "init", "--agent"]);
|
|
285
|
+
} catch {
|
|
286
|
+
// Command may throw due to exitOverride
|
|
287
|
+
} finally {
|
|
288
|
+
process.chdir(originalCwd);
|
|
289
|
+
}
|
|
290
|
+
|
|
291
|
+
const content = readFileSync(join(testDir, "AGENTS.md"), "utf-8");
|
|
292
|
+
|
|
293
|
+
// Should contain consumer-focused content
|
|
294
|
+
expect(content).toContain("enact search");
|
|
295
|
+
expect(content).toContain("enact install");
|
|
296
|
+
expect(content).toContain("enact list");
|
|
297
|
+
expect(content).toContain(".enact/tools.json");
|
|
298
|
+
});
|
|
299
|
+
});
|
|
300
|
+
|
|
301
|
+
describe("option conflicts", () => {
|
|
302
|
+
test("--tool is the default when no mode specified", () => {
|
|
303
|
+
const program = new Command();
|
|
304
|
+
configureInitCommand(program);
|
|
305
|
+
|
|
306
|
+
const initCmd = program.commands.find((cmd) => cmd.name() === "init");
|
|
307
|
+
const opts = initCmd?.options ?? [];
|
|
308
|
+
const toolOpt = opts.find((o) => o.long === "--tool");
|
|
309
|
+
|
|
310
|
+
// Description should indicate it's the default
|
|
311
|
+
expect(toolOpt?.description).toContain("default");
|
|
312
|
+
});
|
|
313
|
+
});
|
|
314
|
+
|
|
315
|
+
describe("template content quality", () => {
|
|
316
|
+
test("tool template has all required manifest fields", async () => {
|
|
317
|
+
const program = new Command();
|
|
318
|
+
program.exitOverride();
|
|
319
|
+
configureInitCommand(program);
|
|
320
|
+
|
|
321
|
+
const testDir = join(import.meta.dir, ".test-init-quality");
|
|
322
|
+
if (existsSync(testDir)) {
|
|
323
|
+
rmSync(testDir, { recursive: true });
|
|
324
|
+
}
|
|
325
|
+
mkdirSync(testDir, { recursive: true });
|
|
326
|
+
|
|
327
|
+
const originalCwd = process.cwd();
|
|
328
|
+
process.chdir(testDir);
|
|
329
|
+
|
|
330
|
+
try {
|
|
331
|
+
await program.parseAsync(["node", "test", "init", "--name", "test/tool"]);
|
|
332
|
+
} catch {
|
|
333
|
+
// Command may throw due to exitOverride
|
|
334
|
+
} finally {
|
|
335
|
+
process.chdir(originalCwd);
|
|
336
|
+
}
|
|
337
|
+
|
|
338
|
+
const content = readFileSync(join(testDir, "enact.md"), "utf-8");
|
|
339
|
+
|
|
340
|
+
// Required fields per spec
|
|
341
|
+
expect(content).toContain("name:");
|
|
342
|
+
expect(content).toContain("description:");
|
|
343
|
+
|
|
344
|
+
// Recommended fields
|
|
345
|
+
expect(content).toContain("version:");
|
|
346
|
+
expect(content).toContain("enact:");
|
|
347
|
+
expect(content).toContain("from:");
|
|
348
|
+
expect(content).toContain("inputSchema:");
|
|
349
|
+
expect(content).toContain("command:");
|
|
350
|
+
|
|
351
|
+
// Clean up
|
|
352
|
+
rmSync(testDir, { recursive: true });
|
|
353
|
+
});
|
|
354
|
+
|
|
355
|
+
test("AGENTS.md does not contain unnecessary verbosity", async () => {
|
|
356
|
+
const program = new Command();
|
|
357
|
+
program.exitOverride();
|
|
358
|
+
configureInitCommand(program);
|
|
359
|
+
|
|
360
|
+
const testDir = join(import.meta.dir, ".test-init-verbosity");
|
|
361
|
+
if (existsSync(testDir)) {
|
|
362
|
+
rmSync(testDir, { recursive: true });
|
|
363
|
+
}
|
|
364
|
+
mkdirSync(testDir, { recursive: true });
|
|
365
|
+
|
|
366
|
+
const originalCwd = process.cwd();
|
|
367
|
+
process.chdir(testDir);
|
|
368
|
+
|
|
369
|
+
try {
|
|
370
|
+
await program.parseAsync(["node", "test", "init", "--name", "test/tool"]);
|
|
371
|
+
} catch {
|
|
372
|
+
// Command may throw due to exitOverride
|
|
373
|
+
} finally {
|
|
374
|
+
process.chdir(originalCwd);
|
|
375
|
+
}
|
|
376
|
+
|
|
377
|
+
const content = readFileSync(join(testDir, "AGENTS.md"), "utf-8");
|
|
378
|
+
|
|
379
|
+
// Should be concise - check line count is reasonable
|
|
380
|
+
const lines = content.split("\n").length;
|
|
381
|
+
expect(lines).toBeLessThan(100); // Should be concise
|
|
382
|
+
|
|
383
|
+
// Clean up
|
|
384
|
+
rmSync(testDir, { recursive: true });
|
|
385
|
+
});
|
|
386
|
+
});
|
|
387
|
+
});
|