@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.
- package/dist/commands/init/index.d.ts.map +1 -1
- package/dist/commands/init/index.js +6 -377
- package/dist/commands/init/index.js.map +1 -1
- package/dist/commands/init/templates/agent-agents.d.ts +5 -0
- package/dist/commands/init/templates/agent-agents.d.ts.map +1 -0
- package/dist/commands/init/templates/agent-agents.js +53 -0
- package/dist/commands/init/templates/agent-agents.js.map +1 -0
- package/dist/commands/init/templates/claude.d.ts +5 -0
- package/dist/commands/init/templates/claude.d.ts.map +1 -0
- package/dist/commands/init/templates/claude.js +71 -0
- package/dist/commands/init/templates/claude.js.map +1 -0
- package/dist/commands/init/templates/index.d.ts +8 -0
- package/dist/commands/init/templates/index.d.ts.map +1 -0
- package/dist/commands/init/templates/index.js +8 -0
- package/dist/commands/init/templates/index.js.map +1 -0
- package/dist/commands/init/templates/tool-agents.d.ts +5 -0
- package/dist/commands/init/templates/tool-agents.d.ts.map +1 -0
- package/dist/commands/init/templates/tool-agents.js +219 -0
- package/dist/commands/init/templates/tool-agents.js.map +1 -0
- package/dist/commands/init/templates/tool-skill.d.ts +5 -0
- package/dist/commands/init/templates/tool-skill.d.ts.map +1 -0
- package/dist/commands/init/templates/tool-skill.js +76 -0
- package/dist/commands/init/templates/tool-skill.js.map +1 -0
- package/dist/commands/publish/index.d.ts +1 -0
- package/dist/commands/publish/index.d.ts.map +1 -1
- package/dist/commands/publish/index.js +95 -2
- package/dist/commands/publish/index.js.map +1 -1
- package/dist/commands/sign/index.d.ts +7 -2
- package/dist/commands/sign/index.d.ts.map +1 -1
- package/dist/commands/sign/index.js +102 -49
- package/dist/commands/sign/index.js.map +1 -1
- package/dist/index.d.ts +1 -1
- package/dist/index.js +1 -1
- package/package.json +5 -5
- package/src/commands/init/index.ts +11 -380
- package/src/commands/init/templates/{agent-agents.md → agent-agents.ts} +20 -15
- package/src/commands/init/templates/{claude.md → claude.ts} +24 -19
- package/src/commands/init/templates/index.ts +7 -0
- package/src/commands/init/templates/tool-agents.ts +218 -0
- package/src/commands/init/templates/tool-skill.ts +75 -0
- package/src/commands/publish/index.ts +111 -1
- package/src/commands/sign/index.ts +127 -52
- package/src/index.ts +1 -1
- package/tsconfig.tsbuildinfo +1 -1
- package/src/commands/init/templates/tool-agents.md +0 -56
- 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(
|
|
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
|
/**
|