@enactprotocol/cli 2.2.4 → 2.3.4
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 +4 -37
- package/dist/commands/cache/index.js +5 -5
- package/dist/commands/cache/index.js.map +1 -1
- package/dist/commands/index.d.ts +1 -0
- package/dist/commands/index.d.ts.map +1 -1
- package/dist/commands/index.js +2 -0
- package/dist/commands/index.js.map +1 -1
- package/dist/commands/inspect/index.d.ts.map +1 -1
- package/dist/commands/inspect/index.js +3 -2
- package/dist/commands/inspect/index.js.map +1 -1
- package/dist/commands/install/index.d.ts +1 -1
- package/dist/commands/install/index.d.ts.map +1 -1
- package/dist/commands/install/index.js +69 -21
- package/dist/commands/install/index.js.map +1 -1
- package/dist/commands/learn/index.d.ts.map +1 -1
- package/dist/commands/learn/index.js +73 -14
- package/dist/commands/learn/index.js.map +1 -1
- package/dist/commands/list/index.d.ts +1 -1
- package/dist/commands/list/index.js +1 -1
- package/dist/commands/run/index.d.ts.map +1 -1
- package/dist/commands/run/index.js +166 -60
- package/dist/commands/run/index.js.map +1 -1
- package/dist/commands/serve/index.d.ts +9 -0
- package/dist/commands/serve/index.d.ts.map +1 -0
- package/dist/commands/serve/index.js +24 -0
- package/dist/commands/serve/index.js.map +1 -0
- package/dist/commands/validate/index.d.ts.map +1 -1
- package/dist/commands/validate/index.js +7 -37
- package/dist/commands/validate/index.js.map +1 -1
- package/dist/index.d.ts +1 -1
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +4 -2
- package/dist/index.js.map +1 -1
- package/dist/utils/errors.js +2 -2
- package/package.json +6 -5
- package/src/commands/cache/index.ts +5 -5
- package/src/commands/env/README.md +1 -1
- package/src/commands/index.ts +3 -0
- package/src/commands/inspect/index.ts +3 -2
- package/src/commands/install/README.md +2 -2
- package/src/commands/install/index.ts +98 -21
- package/src/commands/learn/index.ts +89 -18
- package/src/commands/list/index.ts +1 -1
- package/src/commands/run/README.md +1 -1
- package/src/commands/run/index.ts +218 -67
- package/src/commands/serve/index.ts +26 -0
- package/src/commands/validate/index.ts +7 -42
- package/src/index.ts +5 -1
- package/src/utils/errors.ts +2 -2
- package/tests/commands/cache.test.ts +2 -2
- package/tests/commands/install-integration.test.ts +11 -12
- package/tests/commands/publish.test.ts +12 -2
- package/tests/commands/run.test.ts +3 -1
- package/tests/commands/serve.test.ts +82 -0
- package/tests/commands/sign.test.ts +1 -1
- package/tests/e2e.test.ts +56 -34
- package/tests/fixtures/calculator/skill.yaml +38 -0
- package/tests/fixtures/echo-tool/SKILL.md +3 -10
- package/tests/fixtures/env-tool/{enact.yaml → skill.yaml} +0 -6
- package/tests/fixtures/greeter/skill.yaml +22 -0
- package/tests/utils/ignore.test.ts +3 -1
- package/tsconfig.json +2 -1
- package/tsconfig.tsbuildinfo +1 -1
- package/tests/fixtures/calculator/enact.yaml +0 -34
- package/tests/fixtures/greeter/enact.yaml +0 -18
- /package/tests/fixtures/invalid-tool/{enact.yaml → skill.yaml} +0 -0
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
* enact install command
|
|
3
3
|
*
|
|
4
4
|
* Install a tool to the project or globally.
|
|
5
|
-
* All tools are extracted to ~/.
|
|
5
|
+
* All tools are extracted to ~/.agent/skills/{tool}/
|
|
6
6
|
* - Project install: Adds entry to .enact/tools.json
|
|
7
7
|
* - Global install: Adds entry to ~/.enact/tools.json
|
|
8
8
|
*
|
|
@@ -25,7 +25,6 @@ import {
|
|
|
25
25
|
addAlias,
|
|
26
26
|
addMcpTool,
|
|
27
27
|
addToolToRegistry,
|
|
28
|
-
getCacheDir,
|
|
29
28
|
getInstalledVersion,
|
|
30
29
|
getMinimumAttestations,
|
|
31
30
|
getProjectEnactDir,
|
|
@@ -110,7 +109,8 @@ function isLocalPath(toolName: string): boolean {
|
|
|
110
109
|
*/
|
|
111
110
|
async function extractBundle(bundleData: ArrayBuffer, destPath: string): Promise<void> {
|
|
112
111
|
// Create a temporary file for the bundle
|
|
113
|
-
const
|
|
112
|
+
const { tmpdir } = await import("node:os");
|
|
113
|
+
const tempFile = join(tmpdir(), `enact-bundle-${Date.now()}.tar.gz`);
|
|
114
114
|
mkdirSync(dirname(tempFile), { recursive: true });
|
|
115
115
|
writeFileSync(tempFile, Buffer.from(bundleData));
|
|
116
116
|
|
|
@@ -149,6 +149,40 @@ function formatBytes(bytes: number): string {
|
|
|
149
149
|
return `${(bytes / k ** i).toFixed(1)} ${sizes[i]}`;
|
|
150
150
|
}
|
|
151
151
|
|
|
152
|
+
/**
|
|
153
|
+
* Run postinstall hook commands in the tool directory.
|
|
154
|
+
* Runs each command sequentially; throws on first failure.
|
|
155
|
+
*/
|
|
156
|
+
async function runPostinstallHook(
|
|
157
|
+
toolDir: string,
|
|
158
|
+
hookCommands: string | string[],
|
|
159
|
+
_toolName: string,
|
|
160
|
+
verbose?: boolean
|
|
161
|
+
): Promise<void> {
|
|
162
|
+
const commands = Array.isArray(hookCommands) ? hookCommands : [hookCommands];
|
|
163
|
+
|
|
164
|
+
for (const cmd of commands) {
|
|
165
|
+
if (verbose) {
|
|
166
|
+
dim(` Running: ${cmd}`);
|
|
167
|
+
}
|
|
168
|
+
|
|
169
|
+
const proc = Bun.spawn(["sh", "-c", cmd], {
|
|
170
|
+
cwd: toolDir,
|
|
171
|
+
stdout: verbose ? "inherit" : "pipe",
|
|
172
|
+
stderr: "pipe",
|
|
173
|
+
env: { ...process.env },
|
|
174
|
+
});
|
|
175
|
+
|
|
176
|
+
const exitCode = await proc.exited;
|
|
177
|
+
if (exitCode !== 0) {
|
|
178
|
+
const stderr = await new Response(proc.stderr).text();
|
|
179
|
+
throw new Error(
|
|
180
|
+
`postinstall hook failed (exit ${exitCode}): ${cmd}${stderr ? `\n${stderr.trim()}` : ""}`
|
|
181
|
+
);
|
|
182
|
+
}
|
|
183
|
+
}
|
|
184
|
+
}
|
|
185
|
+
|
|
152
186
|
/**
|
|
153
187
|
* Install from the registry
|
|
154
188
|
*/
|
|
@@ -258,25 +292,29 @@ async function installFromRegistry(
|
|
|
258
292
|
const attestations = attestationsResponse.attestations;
|
|
259
293
|
|
|
260
294
|
if (attestations.length === 0) {
|
|
261
|
-
// No attestations found
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
|
|
295
|
+
// No attestations found — but if minimum_attestations is 0, that's fine
|
|
296
|
+
if (minimumAttestations === 0) {
|
|
297
|
+
// User explicitly configured zero required attestations — allow installation
|
|
298
|
+
} else {
|
|
299
|
+
info(`${symbols.warning} Tool ${toolName}@${targetVersion} has no attestations.`);
|
|
300
|
+
|
|
301
|
+
if (trustPolicy === "require_attestation") {
|
|
302
|
+
error("Trust policy requires attestations. Installation blocked.");
|
|
303
|
+
info("Configure trust policy in ~/.enact/config.yaml");
|
|
304
|
+
process.exit(EXIT_TRUST_ERROR);
|
|
305
|
+
} else if (ctx.isInteractive && trustPolicy === "prompt") {
|
|
306
|
+
const proceed = await confirm("Install unverified tool?");
|
|
307
|
+
if (!proceed) {
|
|
308
|
+
info("Installation cancelled.");
|
|
309
|
+
process.exit(0);
|
|
310
|
+
}
|
|
311
|
+
} else if (!ctx.isInteractive && trustPolicy === "prompt") {
|
|
312
|
+
error("Cannot install unverified tools in non-interactive mode.");
|
|
313
|
+
info("Run interactively to confirm installation.");
|
|
314
|
+
process.exit(EXIT_TRUST_ERROR);
|
|
273
315
|
}
|
|
274
|
-
} else if (!ctx.isInteractive && trustPolicy === "prompt") {
|
|
275
|
-
error("Cannot install unverified tools in non-interactive mode.");
|
|
276
|
-
info("Run interactively to confirm installation.");
|
|
277
|
-
process.exit(EXIT_TRUST_ERROR);
|
|
278
316
|
}
|
|
279
|
-
// trustPolicy === "allow" - continue without prompting
|
|
317
|
+
// trustPolicy === "allow" or minimumAttestations === 0 - continue without prompting
|
|
280
318
|
} else {
|
|
281
319
|
// Verify attestations locally (never trust registry's verification status)
|
|
282
320
|
const verifiedAuditors = await verifyAllAttestations(
|
|
@@ -438,6 +476,26 @@ async function installFromRegistry(
|
|
|
438
476
|
throw new RegistryError(`Failed to extract bundle: ${formatError(err)}`);
|
|
439
477
|
}
|
|
440
478
|
|
|
479
|
+
// Run postinstall hook if the manifest defines one
|
|
480
|
+
const extractedManifest = loadManifestFromDir(cachePath);
|
|
481
|
+
if (extractedManifest?.manifest.hooks?.postinstall) {
|
|
482
|
+
try {
|
|
483
|
+
await withSpinner(
|
|
484
|
+
"Running postinstall hook...",
|
|
485
|
+
async () =>
|
|
486
|
+
runPostinstallHook(
|
|
487
|
+
cachePath,
|
|
488
|
+
extractedManifest.manifest.hooks!.postinstall!,
|
|
489
|
+
toolName,
|
|
490
|
+
options.verbose
|
|
491
|
+
),
|
|
492
|
+
`${symbols.success} postinstall complete`
|
|
493
|
+
);
|
|
494
|
+
} catch (err) {
|
|
495
|
+
info(`${symbols.warning} postinstall hook failed: ${formatError(err)}`);
|
|
496
|
+
}
|
|
497
|
+
}
|
|
498
|
+
|
|
441
499
|
// Update tools.json for the appropriate scope
|
|
442
500
|
addToolToRegistry(toolName, targetVersion!, scope, isGlobal ? undefined : ctx.cwd);
|
|
443
501
|
|
|
@@ -485,7 +543,7 @@ async function installFromRegistry(
|
|
|
485
543
|
* Install from a local path
|
|
486
544
|
*
|
|
487
545
|
* Both global and project installs:
|
|
488
|
-
* 1. Copy tool to
|
|
546
|
+
* 1. Copy tool to ~/.agent/skills/{tool}/
|
|
489
547
|
* 2. Update tools.json (global: ~/.enact/tools.json, project: .enact/tools.json)
|
|
490
548
|
*/
|
|
491
549
|
async function installFromPath(
|
|
@@ -547,6 +605,25 @@ async function installFromPath(
|
|
|
547
605
|
`${symbols.success} Installed ${manifest.name}`
|
|
548
606
|
);
|
|
549
607
|
|
|
608
|
+
// Run postinstall hook if the manifest defines one
|
|
609
|
+
if (manifest.hooks?.postinstall) {
|
|
610
|
+
try {
|
|
611
|
+
await withSpinner(
|
|
612
|
+
"Running postinstall hook...",
|
|
613
|
+
async () =>
|
|
614
|
+
runPostinstallHook(
|
|
615
|
+
cachePath,
|
|
616
|
+
manifest.hooks!.postinstall!,
|
|
617
|
+
manifest.name,
|
|
618
|
+
options.verbose
|
|
619
|
+
),
|
|
620
|
+
`${symbols.success} postinstall complete`
|
|
621
|
+
);
|
|
622
|
+
} catch (err) {
|
|
623
|
+
info(`${symbols.warning} postinstall hook failed: ${formatError(err)}`);
|
|
624
|
+
}
|
|
625
|
+
}
|
|
626
|
+
|
|
550
627
|
// Update tools.json for the appropriate scope
|
|
551
628
|
addToolToRegistry(manifest.name, version, scope, isGlobal ? undefined : ctx.cwd);
|
|
552
629
|
|
|
@@ -18,6 +18,7 @@ import {
|
|
|
18
18
|
verifyAllAttestations,
|
|
19
19
|
} from "@enactprotocol/api";
|
|
20
20
|
import {
|
|
21
|
+
type ActionsManifest,
|
|
21
22
|
getMinimumAttestations,
|
|
22
23
|
getTrustPolicy,
|
|
23
24
|
getTrustedAuditors,
|
|
@@ -45,6 +46,48 @@ interface LearnOptions extends GlobalOptions {
|
|
|
45
46
|
local?: boolean;
|
|
46
47
|
}
|
|
47
48
|
|
|
49
|
+
/**
|
|
50
|
+
* Display available actions from an ActionsManifest
|
|
51
|
+
*/
|
|
52
|
+
function displayActions(actionsManifest: ActionsManifest): void {
|
|
53
|
+
newline();
|
|
54
|
+
header("Available Actions");
|
|
55
|
+
newline();
|
|
56
|
+
|
|
57
|
+
// Iterate over action map (name is the key)
|
|
58
|
+
for (const [actionName, action] of Object.entries(actionsManifest.actions)) {
|
|
59
|
+
info(` ${actionName}`);
|
|
60
|
+
if (action.description) {
|
|
61
|
+
dim(` ${action.description}`);
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
// Show input parameters
|
|
65
|
+
if (action.inputSchema?.properties) {
|
|
66
|
+
const required = new Set(
|
|
67
|
+
Array.isArray(action.inputSchema.required) ? action.inputSchema.required : []
|
|
68
|
+
);
|
|
69
|
+
const props = action.inputSchema.properties as Record<
|
|
70
|
+
string,
|
|
71
|
+
{ type?: string; description?: string }
|
|
72
|
+
>;
|
|
73
|
+
const paramNames = Object.keys(props);
|
|
74
|
+
|
|
75
|
+
if (paramNames.length > 0) {
|
|
76
|
+
dim(" Parameters:");
|
|
77
|
+
for (const paramName of paramNames) {
|
|
78
|
+
const prop = props[paramName];
|
|
79
|
+
const isRequired = required.has(paramName);
|
|
80
|
+
const reqLabel = isRequired ? " (required)" : "";
|
|
81
|
+
dim(` - ${paramName}: ${prop?.type ?? "any"}${reqLabel}`);
|
|
82
|
+
}
|
|
83
|
+
}
|
|
84
|
+
}
|
|
85
|
+
newline();
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
dim("Run an action with: enact run <skill>:<action> --args '{...}'");
|
|
89
|
+
}
|
|
90
|
+
|
|
48
91
|
/**
|
|
49
92
|
* Learn command handler
|
|
50
93
|
*/
|
|
@@ -69,6 +112,13 @@ async function learnHandler(
|
|
|
69
112
|
version: resolution.manifest.version,
|
|
70
113
|
documentation: content,
|
|
71
114
|
source: "local",
|
|
115
|
+
actions: resolution.actionsManifest
|
|
116
|
+
? Object.entries(resolution.actionsManifest.actions).map(([name, a]) => ({
|
|
117
|
+
name,
|
|
118
|
+
description: a.description,
|
|
119
|
+
inputSchema: a.inputSchema,
|
|
120
|
+
}))
|
|
121
|
+
: null,
|
|
72
122
|
});
|
|
73
123
|
return;
|
|
74
124
|
}
|
|
@@ -77,6 +127,11 @@ async function learnHandler(
|
|
|
77
127
|
dim("(installed locally)");
|
|
78
128
|
newline();
|
|
79
129
|
console.log(content);
|
|
130
|
+
|
|
131
|
+
// Display actions if available
|
|
132
|
+
if (resolution.actionsManifest) {
|
|
133
|
+
displayActions(resolution.actionsManifest);
|
|
134
|
+
}
|
|
80
135
|
return;
|
|
81
136
|
}
|
|
82
137
|
|
|
@@ -87,6 +142,13 @@ async function learnHandler(
|
|
|
87
142
|
version: resolution.manifest.version,
|
|
88
143
|
documentation: resolution.manifest.doc ?? resolution.manifest.description ?? null,
|
|
89
144
|
source: "local",
|
|
145
|
+
actions: resolution.actionsManifest
|
|
146
|
+
? Object.entries(resolution.actionsManifest.actions).map(([name, a]) => ({
|
|
147
|
+
name,
|
|
148
|
+
description: a.description,
|
|
149
|
+
inputSchema: a.inputSchema,
|
|
150
|
+
}))
|
|
151
|
+
: null,
|
|
90
152
|
});
|
|
91
153
|
return;
|
|
92
154
|
}
|
|
@@ -97,6 +159,11 @@ async function learnHandler(
|
|
|
97
159
|
console.log(
|
|
98
160
|
resolution.manifest.doc ?? resolution.manifest.description ?? "No documentation available."
|
|
99
161
|
);
|
|
162
|
+
|
|
163
|
+
// Display actions if available
|
|
164
|
+
if (resolution.actionsManifest) {
|
|
165
|
+
displayActions(resolution.actionsManifest);
|
|
166
|
+
}
|
|
100
167
|
return;
|
|
101
168
|
}
|
|
102
169
|
|
|
@@ -158,27 +225,31 @@ async function learnHandler(
|
|
|
158
225
|
const attestations = attestationsResponse.attestations;
|
|
159
226
|
|
|
160
227
|
if (attestations.length === 0) {
|
|
161
|
-
// No attestations found
|
|
162
|
-
|
|
228
|
+
// No attestations found — but if minimum_attestations is 0, that's fine
|
|
229
|
+
if (minimumAttestations === 0) {
|
|
230
|
+
// User explicitly configured zero required attestations — allow access
|
|
231
|
+
} else {
|
|
232
|
+
info(`${symbols.warning} Tool ${toolName}@${version} has no attestations.`);
|
|
163
233
|
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
234
|
+
if (trustPolicy === "require_attestation") {
|
|
235
|
+
throw new TrustError(
|
|
236
|
+
"Trust policy requires attestations. Cannot display documentation from unverified tools."
|
|
237
|
+
);
|
|
238
|
+
}
|
|
239
|
+
if (ctx.isInteractive && trustPolicy === "prompt") {
|
|
240
|
+
dim("Documentation from unverified tools may contain malicious content.");
|
|
241
|
+
const proceed = await confirm("View documentation from unverified tool?");
|
|
242
|
+
if (!proceed) {
|
|
243
|
+
info("Cancelled.");
|
|
244
|
+
process.exit(0);
|
|
245
|
+
}
|
|
246
|
+
} else if (!ctx.isInteractive && trustPolicy === "prompt") {
|
|
247
|
+
throw new TrustError(
|
|
248
|
+
"Cannot display documentation from unverified tools in non-interactive mode."
|
|
249
|
+
);
|
|
175
250
|
}
|
|
176
|
-
} else if (!ctx.isInteractive && trustPolicy === "prompt") {
|
|
177
|
-
throw new TrustError(
|
|
178
|
-
"Cannot display documentation from unverified tools in non-interactive mode."
|
|
179
|
-
);
|
|
180
251
|
}
|
|
181
|
-
// trustPolicy === "allow" - continue without prompting
|
|
252
|
+
// trustPolicy === "allow" or minimumAttestations === 0 - continue without prompting
|
|
182
253
|
} else {
|
|
183
254
|
// Verify attestations locally (never trust registry's verification status)
|
|
184
255
|
const verifiedAuditors = await verifyAllAttestations(
|
|
@@ -10,7 +10,7 @@ enact run <tool> [options]
|
|
|
10
10
|
|
|
11
11
|
## Description
|
|
12
12
|
|
|
13
|
-
The `run` command executes a tool using the command defined in its manifest (`
|
|
13
|
+
The `run` command executes a tool using the command defined in its manifest (`skill.yaml` or `SKILL.md`). The tool runs in an isolated container environment with:
|
|
14
14
|
|
|
15
15
|
- Input validation against the tool's JSON Schema
|
|
16
16
|
- Automatic secret resolution from the OS keyring
|