@enactprotocol/cli 2.1.14 → 2.1.17

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.
Files changed (46) hide show
  1. package/dist/commands/init/index.d.ts.map +1 -1
  2. package/dist/commands/init/index.js +6 -377
  3. package/dist/commands/init/index.js.map +1 -1
  4. package/dist/commands/init/templates/agent-agents.d.ts +5 -0
  5. package/dist/commands/init/templates/agent-agents.d.ts.map +1 -0
  6. package/dist/commands/init/templates/agent-agents.js +53 -0
  7. package/dist/commands/init/templates/agent-agents.js.map +1 -0
  8. package/dist/commands/init/templates/claude.d.ts +5 -0
  9. package/dist/commands/init/templates/claude.d.ts.map +1 -0
  10. package/dist/commands/init/templates/claude.js +71 -0
  11. package/dist/commands/init/templates/claude.js.map +1 -0
  12. package/dist/commands/init/templates/index.d.ts +8 -0
  13. package/dist/commands/init/templates/index.d.ts.map +1 -0
  14. package/dist/commands/init/templates/index.js +8 -0
  15. package/dist/commands/init/templates/index.js.map +1 -0
  16. package/dist/commands/init/templates/tool-agents.d.ts +5 -0
  17. package/dist/commands/init/templates/tool-agents.d.ts.map +1 -0
  18. package/dist/commands/init/templates/tool-agents.js +219 -0
  19. package/dist/commands/init/templates/tool-agents.js.map +1 -0
  20. package/dist/commands/init/templates/tool-skill.d.ts +5 -0
  21. package/dist/commands/init/templates/tool-skill.d.ts.map +1 -0
  22. package/dist/commands/init/templates/tool-skill.js +76 -0
  23. package/dist/commands/init/templates/tool-skill.js.map +1 -0
  24. package/dist/commands/publish/index.d.ts +1 -0
  25. package/dist/commands/publish/index.d.ts.map +1 -1
  26. package/dist/commands/publish/index.js +95 -2
  27. package/dist/commands/publish/index.js.map +1 -1
  28. package/dist/commands/sign/index.d.ts +7 -2
  29. package/dist/commands/sign/index.d.ts.map +1 -1
  30. package/dist/commands/sign/index.js +102 -49
  31. package/dist/commands/sign/index.js.map +1 -1
  32. package/dist/index.d.ts +1 -1
  33. package/dist/index.js +1 -1
  34. package/package.json +5 -5
  35. package/src/commands/init/index.ts +11 -380
  36. package/src/commands/init/templates/{agent-agents.md → agent-agents.ts} +20 -15
  37. package/src/commands/init/templates/{claude.md → claude.ts} +24 -19
  38. package/src/commands/init/templates/index.ts +7 -0
  39. package/src/commands/init/templates/tool-agents.ts +218 -0
  40. package/src/commands/init/templates/tool-skill.ts +75 -0
  41. package/src/commands/publish/index.ts +111 -1
  42. package/src/commands/sign/index.ts +127 -52
  43. package/src/index.ts +1 -1
  44. package/tsconfig.tsbuildinfo +1 -1
  45. package/src/commands/init/templates/tool-agents.md +0 -56
  46. package/src/commands/init/templates/tool-enact.md +0 -44
@@ -0,0 +1,218 @@
1
+ /**
2
+ * AGENTS.md template for tool development
3
+ */
4
+ export const toolAgentsTemplate = `# Enact Tool Development Guide
5
+
6
+ Enact tools are containerized, cryptographically-signed executables. Each tool is defined by a \`SKILL.md\` file (YAML frontmatter + Markdown docs).
7
+
8
+ ## Quick Reference
9
+
10
+ | Task | Command |
11
+ |------|---------|
12
+ | Run with JSON | \`enact run ./ --args '{"key": "value"}'\` |
13
+ | Run from file | \`enact run ./ --input-file inputs.json\` |
14
+ | Dry run | \`enact run ./ --args '{}' --dry-run\` |
15
+ | Sign & publish | \`enact sign ./ && enact publish ./\` |
16
+
17
+ ## SKILL.md Structure
18
+
19
+ \`\`\`yaml
20
+ ---
21
+ name: {{TOOL_NAME}}
22
+ description: What the tool does
23
+ version: 1.0.0
24
+ enact: "2.0.0"
25
+
26
+ from: python:3.12-slim # Docker image (pin versions, not :latest)
27
+ build: pip install requests # Build steps (cached by Dagger)
28
+ command: python /work/main.py \${input}
29
+ timeout: 30s
30
+
31
+ inputSchema:
32
+ type: object
33
+ properties:
34
+ input:
35
+ type: string
36
+ description: "Input to process"
37
+ required: [input]
38
+
39
+ outputSchema:
40
+ type: object
41
+ properties:
42
+ result:
43
+ type: string
44
+
45
+ env:
46
+ API_KEY:
47
+ description: "External API key"
48
+ secret: true # Set via: enact env set API_KEY --secret
49
+ ---
50
+ # Tool Name
51
+ Documentation here.
52
+ \`\`\`
53
+
54
+ ## Field Reference
55
+
56
+ | Field | Description |
57
+ |-------|-------------|
58
+ | \`name\` | Hierarchical ID: \`org/category/tool\` |
59
+ | \`description\` | What the tool does |
60
+ | \`version\` | Semver version |
61
+ | \`from\` | Docker image |
62
+ | \`build\` | Build commands (string or array, cached) |
63
+ | \`command\` | Shell command with \`\${param}\` substitution |
64
+ | \`timeout\` | Max execution time (e.g., "30s", "5m") |
65
+ | \`inputSchema\` | JSON Schema for inputs |
66
+ | \`outputSchema\` | JSON Schema for outputs |
67
+ | \`env\` | Environment variables and secrets |
68
+
69
+ ## Parameter Substitution
70
+
71
+ Enact auto-quotes parameters. **Never manually quote:**
72
+
73
+ \`\`\`yaml
74
+ # WRONG - causes double-quoting
75
+ command: python /work/main.py "\${input}"
76
+
77
+ # RIGHT - Enact handles quoting
78
+ command: python /work/main.py \${input}
79
+ \`\`\`
80
+
81
+ **Optional params:** Missing optional params become empty strings. Always provide defaults:
82
+ \`\`\`yaml
83
+ inputSchema:
84
+ properties:
85
+ greeting:
86
+ type: string
87
+ default: "Hello" # Recommended for optional params
88
+ \`\`\`
89
+
90
+ Or handle empty in shell:
91
+ \`\`\`yaml
92
+ command: "echo \${greeting:-Hello} \${name}"
93
+ \`\`\`
94
+
95
+ Modifiers:
96
+ - \`\${param}\` — auto-quoted (handles spaces, JSON, special chars)
97
+ - \`\${param:raw}\` — raw, no quoting (use carefully)
98
+
99
+ ## Output
100
+
101
+ Output valid JSON to stdout when \`outputSchema\` is defined:
102
+
103
+ \`\`\`python
104
+ import json, sys
105
+
106
+ try:
107
+ result = do_work()
108
+ print(json.dumps({"status": "success", "result": result}))
109
+ except Exception as e:
110
+ print(json.dumps({"status": "error", "message": str(e)}))
111
+ sys.exit(1) # non-zero = error
112
+ \`\`\`
113
+
114
+ ## Build Steps by Language
115
+
116
+ **Python:**
117
+ \`\`\`yaml
118
+ from: python:3.12-slim
119
+ build: pip install requests pandas
120
+ \`\`\`
121
+
122
+ **Node.js:**
123
+ \`\`\`yaml
124
+ from: node:20-alpine
125
+ build:
126
+ - npm install
127
+ - npm run build
128
+ \`\`\`
129
+
130
+ **Rust:**
131
+ \`\`\`yaml
132
+ from: rust:1.83-slim
133
+ build: rustc /work/main.rs -o /work/tool
134
+ command: /work/tool \${input}
135
+ \`\`\`
136
+
137
+ **Go:**
138
+ \`\`\`yaml
139
+ from: golang:1.22-alpine
140
+ build: cd /work && go build -o tool main.go
141
+ command: /work/tool \${input}
142
+ \`\`\`
143
+
144
+ **System packages:**
145
+ \`\`\`yaml
146
+ build: apt-get update && apt-get install -y libfoo-dev
147
+ \`\`\`
148
+
149
+ Build steps are cached — first run slow, subsequent runs instant.
150
+
151
+ ## File Access
152
+
153
+ Tools run in a container with \`/work\` as the working directory. All source files are copied there.
154
+
155
+ ## Secrets
156
+
157
+ Declare in \`SKILL.md\`:
158
+ \`\`\`yaml
159
+ env:
160
+ API_KEY:
161
+ description: "API key for service"
162
+ secret: true
163
+ \`\`\`
164
+
165
+ Set before running:
166
+ \`\`\`bash
167
+ enact env set API_KEY --secret --namespace {{TOOL_NAME}}
168
+ \`\`\`
169
+
170
+ Access in code:
171
+ \`\`\`python
172
+ import os
173
+ api_key = os.environ.get('API_KEY')
174
+ \`\`\`
175
+
176
+ ## LLM Instruction Tools
177
+
178
+ Tools without a \`command\` field are interpreted by LLMs:
179
+
180
+ \`\`\`yaml
181
+ ---
182
+ name: myorg/ai/reviewer
183
+ description: AI-powered code review
184
+ inputSchema:
185
+ type: object
186
+ properties:
187
+ code: { type: string }
188
+ required: [code]
189
+ outputSchema:
190
+ type: object
191
+ properties:
192
+ issues: { type: array }
193
+ score: { type: number }
194
+ ---
195
+ # Code Reviewer
196
+
197
+ You are a senior engineer. Review the code for bugs, style, and security.
198
+ Return JSON: {"issues": [...], "score": 75}
199
+ \`\`\`
200
+
201
+ ## Publishing Checklist
202
+
203
+ - [ ] \`name\` follows \`namespace/category/tool\` pattern
204
+ - [ ] \`version\` set (semver)
205
+ - [ ] \`description\` is clear and searchable
206
+ - [ ] \`inputSchema\` / \`outputSchema\` defined
207
+ - [ ] \`from\` uses pinned image version
208
+ - [ ] \`timeout\` set appropriately
209
+ - [ ] Tool tested locally with \`enact run ./\`
210
+
211
+ ## Troubleshooting
212
+
213
+ \`\`\`bash
214
+ enact run ./ --args '{"x": "y"}' --verbose # Verbose output
215
+ enact run ./ --args '{}' --dry-run # Preview command
216
+ enact list # List installed tools
217
+ \`\`\`
218
+ `;
@@ -0,0 +1,75 @@
1
+ /**
2
+ * SKILL.md template for new tool creation
3
+ */
4
+ export const toolSkillTemplate = `---
5
+ name: {{TOOL_NAME}}
6
+ description: A simple tool that echoes a greeting
7
+ version: 0.1.0
8
+ enact: "2.0"
9
+
10
+ from: python:3.12-slim
11
+
12
+ # Install dependencies (cached by Dagger)
13
+ build: |
14
+ pip install requests
15
+
16
+ # Environment variables (optional)
17
+ # env:
18
+ # API_KEY:
19
+ # secret: true
20
+ # description: "Your API key"
21
+
22
+ inputSchema:
23
+ type: object
24
+ properties:
25
+ name:
26
+ type: string
27
+ description: Name to greet
28
+ default: World
29
+ required: []
30
+
31
+ command: |
32
+ echo "Hello, \${name}!"
33
+ ---
34
+
35
+ # {{TOOL_NAME}}
36
+
37
+ A simple greeting tool created with \`enact init\`.
38
+
39
+ ## Usage
40
+
41
+ \`\`\`bash
42
+ enact run ./ --args '{"name": "Alice"}'
43
+ \`\`\`
44
+
45
+ ## Customization
46
+
47
+ Edit this file to create your own tool:
48
+
49
+ 1. Update the \`name\` and \`description\` in the frontmatter
50
+ 2. Change the \`from\` image to match your runtime (python, node, rust, etc.)
51
+ 3. Add dependencies in the \`build\` section (pip install, npm install, etc.)
52
+ 4. Uncomment and configure \`env\` for secrets/API keys
53
+ 5. Modify the \`inputSchema\` to define your tool's inputs
54
+ 6. Change the \`command\` to run your script or shell commands
55
+ 7. Update this documentation section
56
+
57
+ ## Environment Variables
58
+
59
+ To use secrets, uncomment the \`env\` section above, then set the value:
60
+
61
+ \`\`\`bash
62
+ enact env set API_KEY --secret --namespace {{TOOL_NAME}}
63
+ \`\`\`
64
+
65
+ Access in your code:
66
+ \`\`\`python
67
+ import os
68
+ api_key = os.environ.get('API_KEY')
69
+ \`\`\`
70
+
71
+ ## Learn More
72
+
73
+ - [Enact Documentation](https://enact.dev/docs)
74
+ - [Tool Manifest Reference](https://enact.dev/docs/manifest)
75
+ `;
@@ -2,6 +2,7 @@
2
2
  * enact publish command
3
3
  *
4
4
  * Publish a tool to the Enact registry using v2 multipart upload.
5
+ * Supports pre-signed attestations via manifest-based signing.
5
6
  */
6
7
 
7
8
  import { existsSync, readFileSync, readdirSync, statSync } from "node:fs";
@@ -16,9 +17,16 @@ import {
16
17
  loadManifestFromDir,
17
18
  validateManifest,
18
19
  } from "@enactprotocol/shared";
20
+ import {
21
+ type ChecksumManifest,
22
+ type SigstoreBundle,
23
+ parseChecksumManifest,
24
+ verifyChecksumManifest,
25
+ } from "@enactprotocol/trust";
19
26
  import type { Command } from "commander";
20
27
  import type { CommandContext, GlobalOptions } from "../../types";
21
28
  import {
29
+ confirm,
22
30
  dim,
23
31
  error,
24
32
  extractNamespace,
@@ -208,6 +216,88 @@ async function publishHandler(
208
216
  header(`Publishing ${toolName}@${version}`);
209
217
  newline();
210
218
 
219
+ // Check for pre-signed attestation (manifest-based signing)
220
+ const checksumManifestPath = join(toolDir, ".enact-manifest.json");
221
+ const sigstoreBundlePath = join(toolDir, ".sigstore-bundle.json");
222
+
223
+ let checksumManifest: ChecksumManifest | undefined;
224
+ let sigstoreBundle: SigstoreBundle | undefined;
225
+ let hasPreSignedAttestation = false;
226
+
227
+ if (existsSync(checksumManifestPath) && existsSync(sigstoreBundlePath)) {
228
+ info("Found pre-signed attestation files");
229
+
230
+ try {
231
+ // Load and parse the checksum manifest
232
+ const manifestContent = readFileSync(checksumManifestPath, "utf-8");
233
+ checksumManifest = parseChecksumManifest(manifestContent);
234
+
235
+ // Load the sigstore bundle
236
+ const bundleContent = readFileSync(sigstoreBundlePath, "utf-8");
237
+ sigstoreBundle = JSON.parse(bundleContent) as SigstoreBundle;
238
+
239
+ // Verify the checksum manifest matches current files
240
+ const ignorePatterns = loadGitignore(toolDir);
241
+ const verification = await verifyChecksumManifest(toolDir, checksumManifest, {
242
+ ignorePatterns,
243
+ });
244
+
245
+ if (!verification.valid) {
246
+ newline();
247
+ warning("Pre-signed attestation is outdated - files have changed since signing:");
248
+ if (verification.modifiedFiles?.length) {
249
+ for (const file of verification.modifiedFiles) {
250
+ dim(` • Modified: ${file}`);
251
+ }
252
+ }
253
+ if (verification.missingFiles?.length) {
254
+ for (const file of verification.missingFiles) {
255
+ dim(` • Missing: ${file}`);
256
+ }
257
+ }
258
+ if (verification.extraFiles?.length) {
259
+ for (const file of verification.extraFiles) {
260
+ dim(` • New file: ${file}`);
261
+ }
262
+ }
263
+ newline();
264
+
265
+ if (ctx.isInteractive) {
266
+ const continueWithoutAttestation = await confirm(
267
+ "Continue publishing without the pre-signed attestation?",
268
+ false
269
+ );
270
+ if (!continueWithoutAttestation) {
271
+ info("Publishing cancelled. Please re-sign with 'enact sign .' after making changes.");
272
+ return;
273
+ }
274
+ // Clear the attestation since it's outdated
275
+ checksumManifest = undefined;
276
+ sigstoreBundle = undefined;
277
+ } else {
278
+ error("Pre-signed attestation does not match current files.");
279
+ dim(
280
+ "Please re-sign with 'enact sign .' or remove .enact-manifest.json and .sigstore-bundle.json"
281
+ );
282
+ process.exit(1);
283
+ }
284
+ } else {
285
+ hasPreSignedAttestation = true;
286
+ keyValue("Attestation", "Pre-signed (valid)");
287
+ keyValue("Manifest hash", `${checksumManifest.manifestHash.digest.slice(0, 16)}...`);
288
+ keyValue("Files in attestation", String(checksumManifest.files.length));
289
+ }
290
+ } catch (err) {
291
+ warning("Failed to load pre-signed attestation:");
292
+ if (err instanceof Error) {
293
+ dim(` ${err.message}`);
294
+ }
295
+ dim("Continuing without attestation...");
296
+ checksumManifest = undefined;
297
+ sigstoreBundle = undefined;
298
+ }
299
+ }
300
+
211
301
  // Determine visibility (private by default for security)
212
302
  const visibility: ToolVisibility = options.public
213
303
  ? "public"
@@ -341,12 +431,22 @@ async function publishHandler(
341
431
  bundle,
342
432
  rawManifest: rawManifestContent,
343
433
  visibility,
434
+ // Include pre-signed attestation if available (cast to Record for API compatibility)
435
+ checksumManifest: hasPreSignedAttestation
436
+ ? (checksumManifest as unknown as Record<string, unknown>)
437
+ : undefined,
438
+ sigstoreBundle: hasPreSignedAttestation
439
+ ? (sigstoreBundle as unknown as Record<string, unknown>)
440
+ : undefined,
344
441
  });
345
442
  });
346
443
 
347
444
  // JSON output
348
445
  if (options.json) {
349
- json(result);
446
+ json({
447
+ ...result,
448
+ hasAttestation: hasPreSignedAttestation,
449
+ });
350
450
  return;
351
451
  }
352
452
 
@@ -355,6 +455,9 @@ async function publishHandler(
355
455
  success(`Published ${result.name}@${result.version} (${visibility})`);
356
456
  keyValue("Bundle Hash", result.bundleHash);
357
457
  keyValue("Published At", result.publishedAt.toISOString());
458
+ if (hasPreSignedAttestation) {
459
+ keyValue("Attestation", "Included (pre-signed)");
460
+ }
358
461
  newline();
359
462
  if (visibility === "private") {
360
463
  dim("This tool is private - only you can access it.");
@@ -362,6 +465,13 @@ async function publishHandler(
362
465
  dim("This tool is unlisted - accessible via direct link, not searchable.");
363
466
  }
364
467
  dim(`Install with: enact install ${toolName}`);
468
+
469
+ if (!hasPreSignedAttestation) {
470
+ newline();
471
+ info("Tip: Sign your tool before publishing for verified attestations:");
472
+ dim(` 1. enact sign ${pathArg} # Create pre-signed attestation`);
473
+ dim(` 2. enact publish ${pathArg} # Publish with attestation`);
474
+ }
365
475
  }
366
476
 
367
477
  /**