@enactprotocol/cli 2.0.10 → 2.0.13
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.js +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,47 @@
|
|
|
1
|
+
# AGENTS.md
|
|
2
|
+
|
|
3
|
+
This project uses Enact tools — containerized, cryptographically-signed executables.
|
|
4
|
+
|
|
5
|
+
## Running Tools
|
|
6
|
+
```bash
|
|
7
|
+
enact run <tool-name> --args '{"key": "value"}' # Run installed tool
|
|
8
|
+
enact run ./path/to/tool --args '{}' # Run local tool
|
|
9
|
+
```
|
|
10
|
+
|
|
11
|
+
## Finding & Installing Tools
|
|
12
|
+
```bash
|
|
13
|
+
enact search "pdf extraction" # Search registry
|
|
14
|
+
enact get author/category/tool # View tool info
|
|
15
|
+
enact install author/category/tool # Add to project (.enact/tools.json)
|
|
16
|
+
enact install author/category/tool --global # Add globally
|
|
17
|
+
enact list # List project tools
|
|
18
|
+
```
|
|
19
|
+
|
|
20
|
+
## Tool Output
|
|
21
|
+
Tools output JSON to stdout. Parse with jq or your language's JSON parser:
|
|
22
|
+
```bash
|
|
23
|
+
enact run tool --args '{}' | jq '.result'
|
|
24
|
+
```
|
|
25
|
+
|
|
26
|
+
## Creating Local Tools
|
|
27
|
+
Create `tools/<name>/enact.md` with:
|
|
28
|
+
```yaml
|
|
29
|
+
---
|
|
30
|
+
name: my-tool
|
|
31
|
+
description: What it does
|
|
32
|
+
command: echo "Hello ${name}"
|
|
33
|
+
inputSchema:
|
|
34
|
+
type: object
|
|
35
|
+
properties:
|
|
36
|
+
name: { type: string }
|
|
37
|
+
---
|
|
38
|
+
# My Tool
|
|
39
|
+
Documentation here.
|
|
40
|
+
```
|
|
41
|
+
Run with: `enact run ./tools/<name> --args '{"name": "World"}'`
|
|
42
|
+
|
|
43
|
+
## Environment & Secrets
|
|
44
|
+
```bash
|
|
45
|
+
enact env set API_KEY --secret --namespace author/tool # Set secret
|
|
46
|
+
enact env list # List env vars
|
|
47
|
+
```
|
|
@@ -0,0 +1,65 @@
|
|
|
1
|
+
# CLAUDE.md
|
|
2
|
+
|
|
3
|
+
This project uses Enact tools — containerized, signed executables you can run via CLI.
|
|
4
|
+
|
|
5
|
+
## Quick Reference
|
|
6
|
+
```bash
|
|
7
|
+
enact run <tool> --args '{"key": "value"}' # Run a tool
|
|
8
|
+
enact search "keyword" # Find tools
|
|
9
|
+
enact install author/tool # Install tool
|
|
10
|
+
enact list # List installed tools
|
|
11
|
+
```
|
|
12
|
+
|
|
13
|
+
## Running Tools
|
|
14
|
+
Tools take JSON input and return JSON output:
|
|
15
|
+
```bash
|
|
16
|
+
# Run and capture output
|
|
17
|
+
result=$(enact run author/utils/formatter --args '{"code": "const x=1"}')
|
|
18
|
+
|
|
19
|
+
# Parse with jq
|
|
20
|
+
enact run tool --args '{}' | jq '.data'
|
|
21
|
+
```
|
|
22
|
+
|
|
23
|
+
## Creating Tools
|
|
24
|
+
Create `enact.md` in a directory:
|
|
25
|
+
```yaml
|
|
26
|
+
---
|
|
27
|
+
name: namespace/category/tool
|
|
28
|
+
description: Clear description for search
|
|
29
|
+
version: 1.0.0
|
|
30
|
+
from: python:3.12-slim # Docker image
|
|
31
|
+
build: pip install requests # Install dependencies (cached)
|
|
32
|
+
command: python /work/main.py ${input}
|
|
33
|
+
inputSchema:
|
|
34
|
+
type: object
|
|
35
|
+
properties:
|
|
36
|
+
input: { type: string, description: "The input to process" }
|
|
37
|
+
required: [input]
|
|
38
|
+
---
|
|
39
|
+
# Tool Name
|
|
40
|
+
Usage documentation here.
|
|
41
|
+
```
|
|
42
|
+
|
|
43
|
+
Key points:
|
|
44
|
+
- `${param}` is auto-quoted — never add manual quotes
|
|
45
|
+
- `from:` pin image versions (not `:latest`)
|
|
46
|
+
- `build:` steps are cached by Dagger
|
|
47
|
+
- Output JSON to stdout, errors to stderr
|
|
48
|
+
- Non-zero exit = failure
|
|
49
|
+
|
|
50
|
+
## Tool Development Workflow
|
|
51
|
+
```bash
|
|
52
|
+
enact run ./ --args '{"input": "test"}' # Test locally
|
|
53
|
+
enact run ./ --args '{}' --dry-run # Preview command
|
|
54
|
+
enact sign ./ && enact publish ./ # Publish
|
|
55
|
+
```
|
|
56
|
+
|
|
57
|
+
## Secrets
|
|
58
|
+
Declare in enact.md, set via CLI:
|
|
59
|
+
```yaml
|
|
60
|
+
env:
|
|
61
|
+
API_KEY: # Declared but not set
|
|
62
|
+
```
|
|
63
|
+
```bash
|
|
64
|
+
enact env set API_KEY --secret --namespace author/tool
|
|
65
|
+
```
|
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
# AGENTS.md
|
|
2
|
+
|
|
3
|
+
Enact tool: containerized, signed executable. Manifest: `enact.md` (YAML frontmatter + Markdown docs).
|
|
4
|
+
|
|
5
|
+
## Commands
|
|
6
|
+
```bash
|
|
7
|
+
enact run ./ --args '{"name": "Test"}' # Run locally
|
|
8
|
+
enact run ./ --args '{}' --dry-run # Preview execution
|
|
9
|
+
enact sign ./ && enact publish ./ # Sign and publish
|
|
10
|
+
```
|
|
11
|
+
|
|
12
|
+
## enact.md Structure
|
|
13
|
+
```yaml
|
|
14
|
+
---
|
|
15
|
+
name: {{TOOL_NAME}} # org/category/tool format
|
|
16
|
+
description: What it does
|
|
17
|
+
version: 1.0.0 # semver
|
|
18
|
+
from: python:3.12-slim # pin versions, not :latest
|
|
19
|
+
build: pip install requests # cached by Dagger
|
|
20
|
+
command: python /work/main.py ${input}
|
|
21
|
+
timeout: 30s
|
|
22
|
+
inputSchema:
|
|
23
|
+
type: object
|
|
24
|
+
properties:
|
|
25
|
+
input: { type: string }
|
|
26
|
+
required: [input]
|
|
27
|
+
env:
|
|
28
|
+
API_KEY: # declare secrets (set via: enact env set API_KEY --secret)
|
|
29
|
+
---
|
|
30
|
+
# Tool Name
|
|
31
|
+
Documentation here (usage examples, etc.)
|
|
32
|
+
```
|
|
33
|
+
|
|
34
|
+
## Parameter Substitution
|
|
35
|
+
- `${param}` — auto-quoted (handles spaces, JSON, special chars)
|
|
36
|
+
- `${param:raw}` — unquoted (use carefully)
|
|
37
|
+
- **Never manually quote**: `"${param}"` causes double-quoting
|
|
38
|
+
|
|
39
|
+
## Output
|
|
40
|
+
Always output valid JSON when `outputSchema` is defined:
|
|
41
|
+
```python
|
|
42
|
+
import json, sys
|
|
43
|
+
print(json.dumps({"result": data})) # stdout = tool output
|
|
44
|
+
sys.exit(1) # non-zero = error
|
|
45
|
+
```
|
|
46
|
+
|
|
47
|
+
## File Access
|
|
48
|
+
Tool runs in container with `/work` as working directory. Source files copied there.
|
|
49
|
+
|
|
50
|
+
## Adding Dependencies
|
|
51
|
+
- Python: `build: pip install package1 package2`
|
|
52
|
+
- Node: `build: ["npm install", "npm run build"]`
|
|
53
|
+
- System: `build: apt-get update && apt-get install -y libfoo`
|
|
54
|
+
- Compiled: `build: rustc /work/main.rs -o /work/tool`
|
|
55
|
+
|
|
56
|
+
Build steps are cached — first run slow, subsequent runs instant.
|
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: {{TOOL_NAME}}
|
|
3
|
+
description: A simple tool that echoes a greeting
|
|
4
|
+
version: 0.1.0
|
|
5
|
+
enact: "2.0"
|
|
6
|
+
|
|
7
|
+
from: alpine:latest
|
|
8
|
+
|
|
9
|
+
inputSchema:
|
|
10
|
+
type: object
|
|
11
|
+
properties:
|
|
12
|
+
name:
|
|
13
|
+
type: string
|
|
14
|
+
description: Name to greet
|
|
15
|
+
default: World
|
|
16
|
+
required: []
|
|
17
|
+
|
|
18
|
+
command: |
|
|
19
|
+
echo "Hello, ${name}!"
|
|
20
|
+
---
|
|
21
|
+
|
|
22
|
+
# {{TOOL_NAME}}
|
|
23
|
+
|
|
24
|
+
A simple greeting tool created with `enact init`.
|
|
25
|
+
|
|
26
|
+
## Usage
|
|
27
|
+
|
|
28
|
+
```bash
|
|
29
|
+
enact run ./ --args '{"name": "Alice"}'
|
|
30
|
+
```
|
|
31
|
+
|
|
32
|
+
## Customization
|
|
33
|
+
|
|
34
|
+
Edit this file to create your own tool:
|
|
35
|
+
|
|
36
|
+
1. Update the `name` and `description` in the frontmatter
|
|
37
|
+
2. Modify the `inputSchema` to define your tool's inputs
|
|
38
|
+
3. Change the `command` to run your desired shell commands
|
|
39
|
+
4. Update this documentation section
|
|
40
|
+
|
|
41
|
+
## Learn More
|
|
42
|
+
|
|
43
|
+
- [Enact Documentation](https://enact.dev/docs)
|
|
44
|
+
- [Tool Manifest Reference](https://enact.dev/docs/manifest)
|
package/src/index.ts
CHANGED
|
@@ -32,7 +32,7 @@ import {
|
|
|
32
32
|
} from "./commands";
|
|
33
33
|
import { error, formatError } from "./utils";
|
|
34
34
|
|
|
35
|
-
export const version = "2.0.
|
|
35
|
+
export const version = "2.0.13";
|
|
36
36
|
|
|
37
37
|
// Export types for external use
|
|
38
38
|
export type { GlobalOptions, CommandContext } from "./types";
|
|
@@ -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
|
+
});
|