@enactprotocol/cli 1.2.13 → 2.0.0
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/README.md +88 -0
- package/package.json +34 -38
- package/src/commands/auth/index.ts +940 -0
- package/src/commands/cache/index.ts +361 -0
- package/src/commands/config/README.md +239 -0
- package/src/commands/config/index.ts +164 -0
- package/src/commands/env/README.md +197 -0
- package/src/commands/env/index.ts +392 -0
- package/src/commands/exec/README.md +110 -0
- package/src/commands/exec/index.ts +195 -0
- package/src/commands/get/index.ts +198 -0
- package/src/commands/index.ts +30 -0
- package/src/commands/inspect/index.ts +264 -0
- package/src/commands/install/README.md +146 -0
- package/src/commands/install/index.ts +682 -0
- package/src/commands/list/README.md +115 -0
- package/src/commands/list/index.ts +138 -0
- package/src/commands/publish/index.ts +350 -0
- package/src/commands/report/index.ts +366 -0
- package/src/commands/run/README.md +124 -0
- package/src/commands/run/index.ts +686 -0
- package/src/commands/search/index.ts +368 -0
- package/src/commands/setup/index.ts +274 -0
- package/src/commands/sign/index.ts +652 -0
- package/src/commands/trust/README.md +214 -0
- package/src/commands/trust/index.ts +453 -0
- package/src/commands/unyank/index.ts +107 -0
- package/src/commands/yank/index.ts +143 -0
- package/src/index.ts +96 -0
- package/src/types.ts +81 -0
- package/src/utils/errors.ts +409 -0
- package/src/utils/exit-codes.ts +159 -0
- package/src/utils/ignore.ts +147 -0
- package/src/utils/index.ts +107 -0
- package/src/utils/output.ts +242 -0
- package/src/utils/spinner.ts +214 -0
- package/tests/commands/auth.test.ts +217 -0
- package/tests/commands/cache.test.ts +286 -0
- package/tests/commands/config.test.ts +277 -0
- package/tests/commands/env.test.ts +293 -0
- package/tests/commands/exec.test.ts +112 -0
- package/tests/commands/get.test.ts +179 -0
- package/tests/commands/inspect.test.ts +201 -0
- package/tests/commands/install-integration.test.ts +343 -0
- package/tests/commands/install.test.ts +288 -0
- package/tests/commands/list.test.ts +160 -0
- package/tests/commands/publish.test.ts +186 -0
- package/tests/commands/report.test.ts +194 -0
- package/tests/commands/run.test.ts +231 -0
- package/tests/commands/search.test.ts +131 -0
- package/tests/commands/sign.test.ts +164 -0
- package/tests/commands/trust.test.ts +236 -0
- package/tests/commands/unyank.test.ts +114 -0
- package/tests/commands/yank.test.ts +154 -0
- package/tests/e2e.test.ts +554 -0
- package/tests/fixtures/calculator/enact.yaml +34 -0
- package/tests/fixtures/echo-tool/enact.md +31 -0
- package/tests/fixtures/env-tool/enact.yaml +19 -0
- package/tests/fixtures/greeter/enact.yaml +18 -0
- package/tests/fixtures/invalid-tool/enact.yaml +4 -0
- package/tests/index.test.ts +8 -0
- package/tests/types.test.ts +84 -0
- package/tests/utils/errors.test.ts +303 -0
- package/tests/utils/exit-codes.test.ts +189 -0
- package/tests/utils/ignore.test.ts +461 -0
- package/tests/utils/output.test.ts +126 -0
- package/tsconfig.json +17 -0
- package/tsconfig.tsbuildinfo +1 -0
- package/dist/index.js +0 -231612
- package/dist/index.js.bak +0 -231611
- package/dist/web/static/app.js +0 -663
- package/dist/web/static/index.html +0 -117
- package/dist/web/static/style.css +0 -291
|
@@ -0,0 +1,366 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* enact report command
|
|
3
|
+
*
|
|
4
|
+
* Report security vulnerabilities or issues with a tool.
|
|
5
|
+
* Creates a signed attestation with result "failed" and submits to the registry.
|
|
6
|
+
*/
|
|
7
|
+
|
|
8
|
+
import { createApiClient, getToolVersion, submitAttestation } from "@enactprotocol/api";
|
|
9
|
+
import { getSecret } from "@enactprotocol/secrets";
|
|
10
|
+
import { loadConfig } from "@enactprotocol/shared";
|
|
11
|
+
import {
|
|
12
|
+
type EnactAuditAttestationOptions,
|
|
13
|
+
createEnactAuditStatement,
|
|
14
|
+
signAttestation,
|
|
15
|
+
} from "@enactprotocol/trust";
|
|
16
|
+
import type { Command } from "commander";
|
|
17
|
+
import type { CommandContext, GlobalOptions } from "../../types";
|
|
18
|
+
import {
|
|
19
|
+
colors,
|
|
20
|
+
dim,
|
|
21
|
+
error,
|
|
22
|
+
formatError,
|
|
23
|
+
info,
|
|
24
|
+
json,
|
|
25
|
+
keyValue,
|
|
26
|
+
newline,
|
|
27
|
+
success,
|
|
28
|
+
warning,
|
|
29
|
+
withSpinner,
|
|
30
|
+
} from "../../utils";
|
|
31
|
+
|
|
32
|
+
/** Auth namespace for token storage */
|
|
33
|
+
const AUTH_NAMESPACE = "enact:auth";
|
|
34
|
+
const ACCESS_TOKEN_KEY = "access_token";
|
|
35
|
+
|
|
36
|
+
interface ReportOptions extends GlobalOptions {
|
|
37
|
+
reason: string;
|
|
38
|
+
severity?: "critical" | "high" | "medium" | "low";
|
|
39
|
+
category?: "security" | "malware" | "quality" | "license" | "other";
|
|
40
|
+
dryRun?: boolean;
|
|
41
|
+
local?: boolean;
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
/** Valid severity levels */
|
|
45
|
+
const SEVERITY_LEVELS = ["critical", "high", "medium", "low"] as const;
|
|
46
|
+
|
|
47
|
+
/** Valid categories */
|
|
48
|
+
const CATEGORIES = ["security", "malware", "quality", "license", "other"] as const;
|
|
49
|
+
|
|
50
|
+
/**
|
|
51
|
+
* Parse tool@version format
|
|
52
|
+
*/
|
|
53
|
+
function parseToolVersion(toolArg: string): { name: string; version: string | undefined } {
|
|
54
|
+
const atIndex = toolArg.lastIndexOf("@");
|
|
55
|
+
|
|
56
|
+
// Check if @ is part of the tool name (like @scope/package) or version separator
|
|
57
|
+
if (atIndex <= 0 || toolArg.startsWith("@")) {
|
|
58
|
+
// Could be @scope/package or @scope/package@version
|
|
59
|
+
const scopedMatch = toolArg.match(/^(@[^/]+\/[^@]+)(?:@(.+))?$/);
|
|
60
|
+
if (scopedMatch) {
|
|
61
|
+
return {
|
|
62
|
+
name: scopedMatch[1] ?? toolArg,
|
|
63
|
+
version: scopedMatch[2],
|
|
64
|
+
};
|
|
65
|
+
}
|
|
66
|
+
return { name: toolArg, version: undefined };
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
return {
|
|
70
|
+
name: toolArg.slice(0, atIndex),
|
|
71
|
+
version: toolArg.slice(atIndex + 1),
|
|
72
|
+
};
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
/**
|
|
76
|
+
* Display report preview (dry run)
|
|
77
|
+
*/
|
|
78
|
+
function displayDryRun(tool: string, version: string | undefined, options: ReportOptions): void {
|
|
79
|
+
newline();
|
|
80
|
+
info(colors.bold("Dry Run Preview - Report Submission"));
|
|
81
|
+
newline();
|
|
82
|
+
|
|
83
|
+
keyValue("Tool", tool);
|
|
84
|
+
if (version) {
|
|
85
|
+
keyValue("Version", version);
|
|
86
|
+
}
|
|
87
|
+
keyValue("Reason", options.reason);
|
|
88
|
+
keyValue("Severity", options.severity ?? "medium");
|
|
89
|
+
keyValue("Category", options.category ?? "other");
|
|
90
|
+
keyValue("Submit to registry", options.local ? "No (local only)" : "Yes");
|
|
91
|
+
newline();
|
|
92
|
+
|
|
93
|
+
info("Actions that would be performed:");
|
|
94
|
+
dim(" 1. Authenticate via OIDC (browser-based OAuth flow)");
|
|
95
|
+
dim(" 2. Create audit attestation with result 'failed'");
|
|
96
|
+
dim(" 3. Request signing certificate from Fulcio");
|
|
97
|
+
dim(" 4. Sign attestation with ephemeral keypair");
|
|
98
|
+
dim(" 5. Log signature to Rekor transparency log");
|
|
99
|
+
if (!options.local) {
|
|
100
|
+
dim(" 6. Submit signed report to Enact registry");
|
|
101
|
+
}
|
|
102
|
+
newline();
|
|
103
|
+
|
|
104
|
+
warning("Note: False reports may result in account suspension.");
|
|
105
|
+
dim("Your identity will be cryptographically bound to this report.");
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
/**
|
|
109
|
+
* Display report result
|
|
110
|
+
*/
|
|
111
|
+
function displayResult(
|
|
112
|
+
tool: string,
|
|
113
|
+
version: string | undefined,
|
|
114
|
+
options: ReportOptions,
|
|
115
|
+
registryResult?: { auditor: string; rekorLogIndex: number | undefined }
|
|
116
|
+
): void {
|
|
117
|
+
if (options.json) {
|
|
118
|
+
json({
|
|
119
|
+
success: true,
|
|
120
|
+
tool,
|
|
121
|
+
version: version ?? "latest",
|
|
122
|
+
reason: options.reason,
|
|
123
|
+
severity: options.severity ?? "medium",
|
|
124
|
+
category: options.category ?? "other",
|
|
125
|
+
registry: registryResult
|
|
126
|
+
? {
|
|
127
|
+
submitted: true,
|
|
128
|
+
auditor: registryResult.auditor,
|
|
129
|
+
rekorLogIndex: registryResult.rekorLogIndex,
|
|
130
|
+
}
|
|
131
|
+
: { submitted: false },
|
|
132
|
+
});
|
|
133
|
+
return;
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
newline();
|
|
137
|
+
success(`Report submitted for ${tool}${version ? `@${version}` : ""}`);
|
|
138
|
+
newline();
|
|
139
|
+
|
|
140
|
+
keyValue("Severity", options.severity ?? "medium");
|
|
141
|
+
keyValue("Category", options.category ?? "other");
|
|
142
|
+
|
|
143
|
+
if (registryResult) {
|
|
144
|
+
keyValue("Auditor identity", registryResult.auditor);
|
|
145
|
+
if (registryResult.rekorLogIndex !== undefined) {
|
|
146
|
+
keyValue("Rekor log index", String(registryResult.rekorLogIndex));
|
|
147
|
+
}
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
newline();
|
|
151
|
+
|
|
152
|
+
info("What happens next:");
|
|
153
|
+
dim(" • Your signed report is recorded on the Rekor transparency log");
|
|
154
|
+
dim(" • Registry moderators will review your report");
|
|
155
|
+
dim(" • The tool publisher will be notified");
|
|
156
|
+
dim(" • You may be contacted for additional information");
|
|
157
|
+
newline();
|
|
158
|
+
|
|
159
|
+
warning("Note: Your identity is cryptographically bound to this report.");
|
|
160
|
+
warning("False reports may result in account suspension.");
|
|
161
|
+
}
|
|
162
|
+
|
|
163
|
+
/**
|
|
164
|
+
* Report command handler
|
|
165
|
+
*/
|
|
166
|
+
async function reportHandler(
|
|
167
|
+
toolArg: string,
|
|
168
|
+
options: ReportOptions,
|
|
169
|
+
_ctx: CommandContext
|
|
170
|
+
): Promise<void> {
|
|
171
|
+
// Parse tool@version
|
|
172
|
+
const { name: toolName, version: parsedVersion } = parseToolVersion(toolArg);
|
|
173
|
+
|
|
174
|
+
// Validate required options
|
|
175
|
+
if (!options.reason || options.reason.trim().length === 0) {
|
|
176
|
+
error("--reason is required. Please provide a description of the issue.");
|
|
177
|
+
process.exit(1);
|
|
178
|
+
}
|
|
179
|
+
|
|
180
|
+
// Validate severity if provided
|
|
181
|
+
if (options.severity && !SEVERITY_LEVELS.includes(options.severity)) {
|
|
182
|
+
error(`Invalid severity. Must be one of: ${SEVERITY_LEVELS.join(", ")}`);
|
|
183
|
+
process.exit(1);
|
|
184
|
+
}
|
|
185
|
+
|
|
186
|
+
// Validate category if provided
|
|
187
|
+
if (options.category && !CATEGORIES.includes(options.category)) {
|
|
188
|
+
error(`Invalid category. Must be one of: ${CATEGORIES.join(", ")}`);
|
|
189
|
+
process.exit(1);
|
|
190
|
+
}
|
|
191
|
+
|
|
192
|
+
// Dry run mode
|
|
193
|
+
if (options.dryRun) {
|
|
194
|
+
displayDryRun(toolName, parsedVersion, options);
|
|
195
|
+
return;
|
|
196
|
+
}
|
|
197
|
+
|
|
198
|
+
// Create API client to fetch tool info
|
|
199
|
+
const config = loadConfig();
|
|
200
|
+
const registryUrl = config.registry?.url ?? "https://registry.enact.tools";
|
|
201
|
+
const client = createApiClient({ baseUrl: registryUrl });
|
|
202
|
+
|
|
203
|
+
// Get version to report - either specified or latest
|
|
204
|
+
let version = parsedVersion;
|
|
205
|
+
if (!version) {
|
|
206
|
+
try {
|
|
207
|
+
const toolInfo = await withSpinner(
|
|
208
|
+
`Fetching ${toolName} info...`,
|
|
209
|
+
async () => getToolVersion(client, toolName, "latest"),
|
|
210
|
+
`Found ${toolName}`
|
|
211
|
+
);
|
|
212
|
+
version = toolInfo.version;
|
|
213
|
+
} catch (err) {
|
|
214
|
+
error(`Failed to find tool ${toolName}: ${formatError(err)}`);
|
|
215
|
+
process.exit(1);
|
|
216
|
+
}
|
|
217
|
+
}
|
|
218
|
+
|
|
219
|
+
// Create the audit notes with severity and category
|
|
220
|
+
const notes = `[${(options.category ?? "other").toUpperCase()}] [${(options.severity ?? "medium").toUpperCase()}] ${options.reason}`;
|
|
221
|
+
|
|
222
|
+
// Create audit statement with result "failed"
|
|
223
|
+
const auditOptions: EnactAuditAttestationOptions = {
|
|
224
|
+
toolName,
|
|
225
|
+
toolVersion: version,
|
|
226
|
+
auditor: "unknown", // Will be filled by OIDC
|
|
227
|
+
result: "failed",
|
|
228
|
+
timestamp: new Date(),
|
|
229
|
+
notes,
|
|
230
|
+
};
|
|
231
|
+
|
|
232
|
+
// Create a simple manifest placeholder for the subject hash
|
|
233
|
+
// In a full implementation, we would fetch the actual manifest from registry
|
|
234
|
+
const manifestPlaceholder = JSON.stringify({
|
|
235
|
+
name: toolName,
|
|
236
|
+
version,
|
|
237
|
+
reported: true,
|
|
238
|
+
});
|
|
239
|
+
|
|
240
|
+
const statement = createEnactAuditStatement(manifestPlaceholder, auditOptions);
|
|
241
|
+
|
|
242
|
+
if (options.verbose) {
|
|
243
|
+
info("Created report attestation statement:");
|
|
244
|
+
dim(JSON.stringify(statement, null, 2));
|
|
245
|
+
newline();
|
|
246
|
+
}
|
|
247
|
+
|
|
248
|
+
// Sign the attestation using Sigstore
|
|
249
|
+
info("Starting OIDC signing flow...");
|
|
250
|
+
dim("A browser window will open for authentication.");
|
|
251
|
+
newline();
|
|
252
|
+
|
|
253
|
+
const result = await withSpinner("Signing report...", async () => {
|
|
254
|
+
try {
|
|
255
|
+
return await signAttestation(statement as unknown as Record<string, unknown>, {
|
|
256
|
+
timeout: 120000, // 2 minutes for OIDC flow
|
|
257
|
+
});
|
|
258
|
+
} catch (err) {
|
|
259
|
+
if (err instanceof Error) {
|
|
260
|
+
if (err.message.includes("OIDC") || err.message.includes("token")) {
|
|
261
|
+
throw new Error(
|
|
262
|
+
`OIDC authentication failed: ${err.message}\nMake sure you complete the browser authentication flow.`
|
|
263
|
+
);
|
|
264
|
+
}
|
|
265
|
+
if (err.message.includes("Fulcio") || err.message.includes("certificate")) {
|
|
266
|
+
throw new Error(
|
|
267
|
+
`Certificate issuance failed: ${err.message}\nThis may be a temporary issue with the Sigstore infrastructure.`
|
|
268
|
+
);
|
|
269
|
+
}
|
|
270
|
+
if (err.message.includes("Rekor") || err.message.includes("transparency")) {
|
|
271
|
+
throw new Error(
|
|
272
|
+
`Transparency log failed: ${err.message}\nThis may be a temporary issue with the Sigstore infrastructure.`
|
|
273
|
+
);
|
|
274
|
+
}
|
|
275
|
+
}
|
|
276
|
+
throw err;
|
|
277
|
+
}
|
|
278
|
+
});
|
|
279
|
+
|
|
280
|
+
// Submit attestation to registry (unless --local)
|
|
281
|
+
let registryResult: { auditor: string; rekorLogIndex: number | undefined } | undefined;
|
|
282
|
+
|
|
283
|
+
if (!options.local) {
|
|
284
|
+
// Check for auth token from keyring
|
|
285
|
+
const authToken = await getSecret(AUTH_NAMESPACE, ACCESS_TOKEN_KEY);
|
|
286
|
+
|
|
287
|
+
if (!authToken) {
|
|
288
|
+
warning("Not authenticated with registry - report not submitted");
|
|
289
|
+
dim("Run 'enact auth login' to authenticate, then report again");
|
|
290
|
+
dim("Your report is still recorded on the Rekor transparency log.");
|
|
291
|
+
} else {
|
|
292
|
+
client.setAuthToken(authToken);
|
|
293
|
+
|
|
294
|
+
try {
|
|
295
|
+
const attestationResult = await withSpinner(
|
|
296
|
+
"Submitting report to registry...",
|
|
297
|
+
async () => {
|
|
298
|
+
return await submitAttestation(client, {
|
|
299
|
+
name: toolName,
|
|
300
|
+
version: version!,
|
|
301
|
+
sigstoreBundle: result.bundle as unknown as Record<string, unknown>,
|
|
302
|
+
});
|
|
303
|
+
}
|
|
304
|
+
);
|
|
305
|
+
|
|
306
|
+
registryResult = {
|
|
307
|
+
auditor: attestationResult.auditor,
|
|
308
|
+
rekorLogIndex: attestationResult.rekorLogIndex,
|
|
309
|
+
};
|
|
310
|
+
} catch (err) {
|
|
311
|
+
warning("Failed to submit report to registry");
|
|
312
|
+
if (err instanceof Error) {
|
|
313
|
+
dim(` ${err.message}`);
|
|
314
|
+
}
|
|
315
|
+
dim("Your report is still recorded on the Rekor transparency log.");
|
|
316
|
+
}
|
|
317
|
+
}
|
|
318
|
+
} else {
|
|
319
|
+
dim("Report signed locally only (--local flag)");
|
|
320
|
+
dim("The signature is recorded on the Rekor transparency log.");
|
|
321
|
+
}
|
|
322
|
+
|
|
323
|
+
// Display result
|
|
324
|
+
displayResult(toolName, version, options, registryResult);
|
|
325
|
+
}
|
|
326
|
+
|
|
327
|
+
/**
|
|
328
|
+
* Configure the report command
|
|
329
|
+
*/
|
|
330
|
+
export function configureReportCommand(program: Command): void {
|
|
331
|
+
program
|
|
332
|
+
.command("report")
|
|
333
|
+
.description(
|
|
334
|
+
"Report security vulnerabilities or issues with a tool (creates signed attestation)"
|
|
335
|
+
)
|
|
336
|
+
.argument("<tool>", "Tool to report (name or name@version)")
|
|
337
|
+
.requiredOption("-r, --reason <description>", "Issue description (required)")
|
|
338
|
+
.option("-s, --severity <level>", "Severity level: critical, high, medium, low", "medium")
|
|
339
|
+
.option(
|
|
340
|
+
"-c, --category <type>",
|
|
341
|
+
"Issue type: security, malware, quality, license, other",
|
|
342
|
+
"other"
|
|
343
|
+
)
|
|
344
|
+
.option("--dry-run", "Show what would be submitted without submitting")
|
|
345
|
+
.option("--local", "Sign locally only, do not submit to registry")
|
|
346
|
+
.option("-v, --verbose", "Show detailed output")
|
|
347
|
+
.option("--json", "Output result as JSON")
|
|
348
|
+
.action(async (toolArg: string, options: ReportOptions) => {
|
|
349
|
+
const ctx: CommandContext = {
|
|
350
|
+
cwd: process.cwd(),
|
|
351
|
+
options,
|
|
352
|
+
isCI: Boolean(process.env.CI),
|
|
353
|
+
isInteractive: process.stdout.isTTY ?? false,
|
|
354
|
+
};
|
|
355
|
+
|
|
356
|
+
try {
|
|
357
|
+
await reportHandler(toolArg, options, ctx);
|
|
358
|
+
} catch (err) {
|
|
359
|
+
error(formatError(err));
|
|
360
|
+
if (options.verbose && err instanceof Error && err.stack) {
|
|
361
|
+
dim(err.stack);
|
|
362
|
+
}
|
|
363
|
+
process.exit(1);
|
|
364
|
+
}
|
|
365
|
+
});
|
|
366
|
+
}
|
|
@@ -0,0 +1,124 @@
|
|
|
1
|
+
# enact run
|
|
2
|
+
|
|
3
|
+
Execute a tool with its manifest-defined command in a containerized environment.
|
|
4
|
+
|
|
5
|
+
## Synopsis
|
|
6
|
+
|
|
7
|
+
```bash
|
|
8
|
+
enact run <tool> [options]
|
|
9
|
+
```
|
|
10
|
+
|
|
11
|
+
## Description
|
|
12
|
+
|
|
13
|
+
The `run` command executes a tool using the command defined in its manifest (`enact.yaml` or `enact.md`). The tool runs in an isolated container environment with:
|
|
14
|
+
|
|
15
|
+
- Input validation against the tool's JSON Schema
|
|
16
|
+
- Automatic secret resolution from the OS keyring
|
|
17
|
+
- Environment variable injection from `.env` files
|
|
18
|
+
- Shell-safe parameter interpolation
|
|
19
|
+
|
|
20
|
+
## Arguments
|
|
21
|
+
|
|
22
|
+
| Argument | Description |
|
|
23
|
+
|----------|-------------|
|
|
24
|
+
| `<tool>` | Tool to run. Can be a tool name (`alice/utils/greeter`), a path (`./my-tool`), or `.` for the current directory |
|
|
25
|
+
|
|
26
|
+
## Options
|
|
27
|
+
|
|
28
|
+
| Option | Description |
|
|
29
|
+
|--------|-------------|
|
|
30
|
+
| `-a, --args <json>` | Input arguments as a JSON object |
|
|
31
|
+
| `-i, --input <key=value>` | Input arguments as key=value pairs (can be repeated) |
|
|
32
|
+
| `-t, --timeout <duration>` | Execution timeout (e.g., `30s`, `5m`, `1h`) |
|
|
33
|
+
| `--no-cache` | Disable container caching |
|
|
34
|
+
| `--local` | Only resolve from local sources |
|
|
35
|
+
| `--dry-run` | Show what would be executed without running |
|
|
36
|
+
| `-v, --verbose` | Show detailed output including stderr and timing |
|
|
37
|
+
| `--json` | Output result as JSON |
|
|
38
|
+
|
|
39
|
+
## Examples
|
|
40
|
+
|
|
41
|
+
### Basic execution
|
|
42
|
+
|
|
43
|
+
```bash
|
|
44
|
+
# Run a tool with JSON arguments
|
|
45
|
+
enact run alice/utils/greeter --args '{"name":"World"}'
|
|
46
|
+
|
|
47
|
+
# Run a tool with key=value arguments
|
|
48
|
+
enact run alice/utils/greeter --input name=World
|
|
49
|
+
|
|
50
|
+
# Run a tool from current directory
|
|
51
|
+
enact run . --args '{"input":"test.txt"}'
|
|
52
|
+
```
|
|
53
|
+
|
|
54
|
+
### Advanced options
|
|
55
|
+
|
|
56
|
+
```bash
|
|
57
|
+
# Run with timeout
|
|
58
|
+
enact run slow-tool --args '{}' --timeout 5m
|
|
59
|
+
|
|
60
|
+
# Dry run to preview execution
|
|
61
|
+
enact run alice/utils/greeter --args '{"name":"World"}' --dry-run
|
|
62
|
+
|
|
63
|
+
# Get JSON output for scripting
|
|
64
|
+
enact run alice/utils/greeter --args '{"name":"World"}' --json
|
|
65
|
+
|
|
66
|
+
# Verbose mode for debugging
|
|
67
|
+
enact run alice/utils/greeter --args '{"name":"World"}' --verbose
|
|
68
|
+
```
|
|
69
|
+
|
|
70
|
+
### Multiple inputs
|
|
71
|
+
|
|
72
|
+
```bash
|
|
73
|
+
# Mix JSON and key=value
|
|
74
|
+
enact run my-tool --args '{"config":{"debug":true}}' --input file=input.txt
|
|
75
|
+
|
|
76
|
+
# Multiple key=value pairs
|
|
77
|
+
enact run my-tool --input name=test --input count=5 --input enabled=true
|
|
78
|
+
```
|
|
79
|
+
|
|
80
|
+
## Input Resolution
|
|
81
|
+
|
|
82
|
+
Inputs are resolved in the following priority order:
|
|
83
|
+
|
|
84
|
+
1. `--input` key=value pairs (highest priority)
|
|
85
|
+
2. `--args` JSON object
|
|
86
|
+
3. Default values from the manifest's `inputSchema`
|
|
87
|
+
|
|
88
|
+
## Environment Resolution
|
|
89
|
+
|
|
90
|
+
The command automatically resolves environment variables:
|
|
91
|
+
|
|
92
|
+
1. **Secrets** (env vars with `secret: true` in manifest):
|
|
93
|
+
- Resolved from OS keyring using namespace inheritance
|
|
94
|
+
- Never written to disk or shown in output
|
|
95
|
+
|
|
96
|
+
2. **Environment variables** (env vars with `secret: false`):
|
|
97
|
+
- Resolved from local `.enact/.env` (project)
|
|
98
|
+
- Then from `~/.enact/.env` (global)
|
|
99
|
+
- Then from manifest defaults
|
|
100
|
+
|
|
101
|
+
## Dry Run Output
|
|
102
|
+
|
|
103
|
+
When using `--dry-run`, the command shows:
|
|
104
|
+
|
|
105
|
+
- Tool name and version
|
|
106
|
+
- Container image that would be used
|
|
107
|
+
- All input parameters
|
|
108
|
+
- Environment variables (secrets masked as `***`)
|
|
109
|
+
- The interpolated command
|
|
110
|
+
|
|
111
|
+
## Exit Codes
|
|
112
|
+
|
|
113
|
+
| Code | Description |
|
|
114
|
+
|------|-------------|
|
|
115
|
+
| `0` | Successful execution |
|
|
116
|
+
| `1` | Execution failed or error |
|
|
117
|
+
| `2` | Invalid arguments |
|
|
118
|
+
| `3` | Tool not found |
|
|
119
|
+
|
|
120
|
+
## See Also
|
|
121
|
+
|
|
122
|
+
- [enact exec](../exec/README.md) - Execute arbitrary commands in a tool's container
|
|
123
|
+
- [enact install](../install/README.md) - Install tools
|
|
124
|
+
- [enact env](../env/README.md) - Manage environment variables and secrets
|