@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.
@@ -4,8 +4,9 @@
4
4
  * Create a basic tool template in the current directory.
5
5
  */
6
6
 
7
- import { existsSync, mkdirSync, writeFileSync } from "node:fs";
8
- import { join } from "node:path";
7
+ import { existsSync, mkdirSync, readFileSync, writeFileSync } from "node:fs";
8
+ import { dirname, join } from "node:path";
9
+ import { fileURLToPath } from "node:url";
9
10
  import { getSecret } from "@enactprotocol/secrets";
10
11
  import type { Command } from "commander";
11
12
  import type { CommandContext, GlobalOptions } from "../../types";
@@ -22,9 +23,32 @@ const SUPABASE_ANON_KEY =
22
23
  process.env.SUPABASE_ANON_KEY ??
23
24
  "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpc3MiOiJzdXBhYmFzZSIsInJlZiI6InNpaWt3a2Znc21vdWlvb2RnaGhvIiwicm9sZSI6ImFub24iLCJpYXQiOjE3NjQ2MTkzMzksImV4cCI6MjA4MDE5NTMzOX0.kxnx6-IPFhmGx6rzNx36vbyhFMFZKP_jFqaDbKnJ_E0";
24
25
 
26
+ /** Get the templates directory path */
27
+ const __filename = fileURLToPath(import.meta.url);
28
+ const __dirname = dirname(__filename);
29
+ const TEMPLATES_DIR = join(__dirname, "templates");
30
+
25
31
  interface InitOptions extends GlobalOptions {
26
32
  name?: string;
27
33
  force?: boolean;
34
+ tool?: boolean;
35
+ agent?: boolean;
36
+ claude?: boolean;
37
+ }
38
+
39
+ /**
40
+ * Load a template file and replace placeholders
41
+ */
42
+ function loadTemplate(templateName: string, replacements: Record<string, string> = {}): string {
43
+ const templatePath = join(TEMPLATES_DIR, templateName);
44
+ let content = readFileSync(templatePath, "utf-8");
45
+
46
+ // Replace all {{PLACEHOLDER}} patterns
47
+ for (const [key, value] of Object.entries(replacements)) {
48
+ content = content.replaceAll(`{{${key}}}`, value);
49
+ }
50
+
51
+ return content;
28
52
  }
29
53
 
30
54
  /**
@@ -105,65 +129,52 @@ async function getCurrentUsername(): Promise<string | null> {
105
129
  }
106
130
  }
107
131
 
108
- /**
109
- * Generate the tool template content
110
- */
111
- function generateToolTemplate(toolName: string): string {
112
- return `---
113
- name: ${toolName}
114
- description: A simple tool that echoes a greeting
115
- version: 0.1.0
116
- enact: "2.0"
117
-
118
- from: alpine:latest
119
-
120
- inputSchema:
121
- type: object
122
- properties:
123
- name:
124
- type: string
125
- description: Name to greet
126
- default: World
127
- required: []
128
-
129
- command: |
130
- echo "Hello, \${name}!"
131
- ---
132
-
133
- # ${toolName}
134
-
135
- A simple greeting tool created with \`enact init\`.
136
-
137
- ## Usage
138
-
139
- \`\`\`bash
140
- enact run ./ --args '{"name": "Alice"}'
141
- \`\`\`
142
-
143
- ## Customization
144
-
145
- Edit this file to create your own tool:
146
-
147
- 1. Update the \`name\` and \`description\` in the frontmatter
148
- 2. Modify the \`inputSchema\` to define your tool's inputs
149
- 3. Change the \`command\` to run your desired shell commands
150
- 4. Update this documentation section
151
-
152
- ## Learn More
153
-
154
- - [Enact Documentation](https://enact.dev/docs)
155
- - [Tool Manifest Reference](https://enact.dev/docs/manifest)
156
- `;
157
- }
158
-
159
132
  /**
160
133
  * Init command handler
161
134
  */
162
135
  async function initHandler(options: InitOptions, ctx: CommandContext): Promise<void> {
163
136
  const targetDir = ctx.cwd;
137
+
138
+ // Determine mode: --agent, --claude, or --tool (default)
139
+ const isAgentMode = options.agent;
140
+ const isClaudeMode = options.claude;
141
+ // Default to tool mode if no flag specified
142
+
143
+ // Handle --agent mode: create AGENTS.md for projects using Enact tools
144
+ if (isAgentMode) {
145
+ const agentsPath = join(targetDir, "AGENTS.md");
146
+ if (existsSync(agentsPath) && !options.force) {
147
+ warning(`AGENTS.md already exists at: ${agentsPath}`);
148
+ info("Use --force to overwrite");
149
+ return;
150
+ }
151
+ writeFileSync(agentsPath, loadTemplate("agent-agents.md"), "utf-8");
152
+ success(`Created AGENTS.md: ${agentsPath}`);
153
+ info("");
154
+ info("This file helps AI agents understand how to use Enact tools in your project.");
155
+ info("Run 'enact search <query>' to find tools, 'enact install <tool>' to add them.");
156
+ return;
157
+ }
158
+
159
+ // Handle --claude mode: create CLAUDE.md
160
+ if (isClaudeMode) {
161
+ const claudePath = join(targetDir, "CLAUDE.md");
162
+ if (existsSync(claudePath) && !options.force) {
163
+ warning(`CLAUDE.md already exists at: ${claudePath}`);
164
+ info("Use --force to overwrite");
165
+ return;
166
+ }
167
+ writeFileSync(claudePath, loadTemplate("claude.md"), "utf-8");
168
+ success(`Created CLAUDE.md: ${claudePath}`);
169
+ info("");
170
+ info("This file helps Claude understand how to use Enact tools in your project.");
171
+ return;
172
+ }
173
+
174
+ // Handle --tool mode (default): create enact.md + AGENTS.md for tool development
164
175
  const manifestPath = join(targetDir, "enact.md");
176
+ const agentsPath = join(targetDir, "AGENTS.md");
165
177
 
166
- // Check if manifest already exists
167
178
  if (existsSync(manifestPath) && !options.force) {
168
179
  warning(`Tool manifest already exists at: ${manifestPath}`);
169
180
  info("Use --force to overwrite");
@@ -185,17 +196,28 @@ async function initHandler(options: InitOptions, ctx: CommandContext): Promise<v
185
196
  }
186
197
  }
187
198
 
188
- // Generate and write the template
189
- const content = generateToolTemplate(toolName);
199
+ // Load templates with placeholder replacement
200
+ const replacements = { TOOL_NAME: toolName };
201
+ const manifestContent = loadTemplate("tool-enact.md", replacements);
202
+ const agentsContent = loadTemplate("tool-agents.md", replacements);
190
203
 
191
204
  // Ensure directory exists
192
205
  if (!existsSync(targetDir)) {
193
206
  mkdirSync(targetDir, { recursive: true });
194
207
  }
195
208
 
196
- writeFileSync(manifestPath, content, "utf-8");
209
+ // Write enact.md
210
+ writeFileSync(manifestPath, manifestContent, "utf-8");
211
+ success(`Created tool manifest: ${manifestPath}`);
212
+
213
+ // Write AGENTS.md (only if it doesn't exist or --force is used)
214
+ if (!existsSync(agentsPath) || options.force) {
215
+ writeFileSync(agentsPath, agentsContent, "utf-8");
216
+ success(`Created AGENTS.md: ${agentsPath}`);
217
+ } else {
218
+ info("AGENTS.md already exists, skipping (use --force to overwrite)");
219
+ }
197
220
 
198
- success(`Created tool template: ${manifestPath}`);
199
221
  info("");
200
222
  info("Next steps:");
201
223
  info(" 1. Edit enact.md to customize your tool");
@@ -209,9 +231,12 @@ async function initHandler(options: InitOptions, ctx: CommandContext): Promise<v
209
231
  export function configureInitCommand(program: Command): void {
210
232
  program
211
233
  .command("init")
212
- .description("Create a new tool template in the current directory")
213
- .option("-n, --name <name>", "Tool name (default: username/my-tool or my-tool)")
214
- .option("-f, --force", "Overwrite existing enact.md file")
234
+ .description("Initialize Enact in the current directory")
235
+ .option("-n, --name <name>", "Tool name (default: username/my-tool)")
236
+ .option("-f, --force", "Overwrite existing files")
237
+ .option("--tool", "Create a new Enact tool (default)")
238
+ .option("--agent", "Create AGENTS.md for projects that use Enact tools")
239
+ .option("--claude", "Create CLAUDE.md with Claude-specific instructions")
215
240
  .option("-v, --verbose", "Show detailed output")
216
241
  .action(async (options: InitOptions) => {
217
242
  const ctx: CommandContext = {
@@ -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.9";
35
+ export const version = "2.0.11";
36
36
 
37
37
  // Export types for external use
38
38
  export type { GlobalOptions, CommandContext } from "./types";