@enactprotocol/cli 2.1.7 → 2.1.14
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/auth/index.js +1 -1
- package/dist/commands/auth/index.js.map +1 -1
- package/dist/commands/index.d.ts +2 -1
- package/dist/commands/index.d.ts.map +1 -1
- package/dist/commands/index.js +3 -1
- package/dist/commands/index.js.map +1 -1
- package/dist/commands/info/index.d.ts +11 -0
- package/dist/commands/info/index.d.ts.map +1 -0
- package/dist/commands/info/index.js +232 -0
- package/dist/commands/info/index.js.map +1 -0
- package/dist/commands/init/index.d.ts.map +1 -1
- package/dist/commands/init/index.js +17 -14
- package/dist/commands/init/index.js.map +1 -1
- package/dist/commands/learn/index.d.ts +4 -0
- package/dist/commands/learn/index.d.ts.map +1 -1
- package/dist/commands/learn/index.js +159 -5
- package/dist/commands/learn/index.js.map +1 -1
- package/dist/commands/mcp/index.d.ts +20 -0
- package/dist/commands/mcp/index.d.ts.map +1 -0
- package/dist/commands/mcp/index.js +460 -0
- package/dist/commands/mcp/index.js.map +1 -0
- package/dist/commands/publish/index.d.ts +2 -0
- package/dist/commands/publish/index.d.ts.map +1 -1
- package/dist/commands/publish/index.js +32 -8
- package/dist/commands/publish/index.js.map +1 -1
- package/dist/commands/sign/index.d.ts +2 -1
- package/dist/commands/sign/index.d.ts.map +1 -1
- package/dist/commands/sign/index.js +75 -17
- package/dist/commands/sign/index.js.map +1 -1
- package/dist/commands/visibility/index.d.ts +11 -0
- package/dist/commands/visibility/index.d.ts.map +1 -0
- package/dist/commands/visibility/index.js +117 -0
- package/dist/commands/visibility/index.js.map +1 -0
- package/dist/index.d.ts +1 -1
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +5 -3
- package/dist/index.js.map +1 -1
- package/package.json +5 -5
- package/src/commands/auth/index.ts +1 -1
- package/src/commands/index.ts +4 -1
- package/src/commands/{get → info}/index.ts +103 -16
- package/src/commands/init/index.ts +17 -14
- package/src/commands/learn/index.ts +228 -5
- package/src/commands/publish/index.ts +37 -8
- package/src/commands/sign/index.ts +93 -18
- package/src/commands/visibility/index.ts +154 -0
- package/src/index.ts +7 -3
- package/tests/commands/{get.test.ts → info.test.ts} +35 -33
- package/tests/commands/init.test.ts +6 -6
- package/tests/commands/learn.test.ts +2 -2
- package/tests/commands/publish.test.ts +50 -0
- package/tests/commands/visibility.test.ts +156 -0
- package/tests/e2e.test.ts +1 -1
- package/tsconfig.tsbuildinfo +1 -1
- /package/tests/fixtures/echo-tool/{enact.md → SKILL.md} +0 -0
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* enact
|
|
2
|
+
* enact info command
|
|
3
3
|
*
|
|
4
|
-
* Show detailed information about a tool from the registry.
|
|
4
|
+
* Show detailed information about a tool from the registry or local path.
|
|
5
5
|
*/
|
|
6
6
|
|
|
7
7
|
import {
|
|
@@ -11,7 +11,7 @@ import {
|
|
|
11
11
|
getToolInfo,
|
|
12
12
|
getToolVersion,
|
|
13
13
|
} from "@enactprotocol/api";
|
|
14
|
-
import { loadConfig } from "@enactprotocol/shared";
|
|
14
|
+
import { loadConfig, tryResolveTool } from "@enactprotocol/shared";
|
|
15
15
|
import type { Command } from "commander";
|
|
16
16
|
import type { CommandContext, GlobalOptions } from "../../types";
|
|
17
17
|
import {
|
|
@@ -26,8 +26,9 @@ import {
|
|
|
26
26
|
success,
|
|
27
27
|
} from "../../utils";
|
|
28
28
|
|
|
29
|
-
interface
|
|
29
|
+
interface InfoOptions extends GlobalOptions {
|
|
30
30
|
ver?: string;
|
|
31
|
+
local?: boolean;
|
|
31
32
|
}
|
|
32
33
|
|
|
33
34
|
/**
|
|
@@ -46,7 +47,7 @@ function formatDate(date: Date): string {
|
|
|
46
47
|
*/
|
|
47
48
|
function displayToolInfo(
|
|
48
49
|
tool: ToolInfo,
|
|
49
|
-
options:
|
|
50
|
+
options: InfoOptions,
|
|
50
51
|
rawManifest?: string | undefined
|
|
51
52
|
): void {
|
|
52
53
|
header(tool.name);
|
|
@@ -83,7 +84,7 @@ function displayToolInfo(
|
|
|
83
84
|
/**
|
|
84
85
|
* Display version-specific info
|
|
85
86
|
*/
|
|
86
|
-
function displayVersionInfo(version: ToolVersionInfo, options:
|
|
87
|
+
function displayVersionInfo(version: ToolVersionInfo, options: InfoOptions): void {
|
|
87
88
|
header(`${version.name}@${version.version}`);
|
|
88
89
|
newline();
|
|
89
90
|
|
|
@@ -119,13 +120,98 @@ function displayVersionInfo(version: ToolVersionInfo, options: GetOptions): void
|
|
|
119
120
|
}
|
|
120
121
|
|
|
121
122
|
/**
|
|
122
|
-
*
|
|
123
|
+
* Display local tool info
|
|
123
124
|
*/
|
|
124
|
-
|
|
125
|
+
function displayLocalToolInfo(
|
|
126
|
+
name: string,
|
|
127
|
+
manifest: Record<string, unknown>,
|
|
128
|
+
manifestPath: string,
|
|
129
|
+
options: InfoOptions
|
|
130
|
+
): void {
|
|
131
|
+
header(name);
|
|
132
|
+
dim("(local)");
|
|
133
|
+
newline();
|
|
134
|
+
|
|
135
|
+
if (manifest.description) {
|
|
136
|
+
info(String(manifest.description));
|
|
137
|
+
newline();
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
keyValue("Version", String(manifest.version ?? "unknown"));
|
|
141
|
+
if (manifest.license) {
|
|
142
|
+
keyValue("License", String(manifest.license));
|
|
143
|
+
}
|
|
144
|
+
if (manifest.command) {
|
|
145
|
+
keyValue("Command", String(manifest.command));
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
if (Array.isArray(manifest.tags) && manifest.tags.length > 0) {
|
|
149
|
+
keyValue("Tags", manifest.tags.join(", "));
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
if (Array.isArray(manifest.authors) && manifest.authors.length > 0) {
|
|
153
|
+
const authorNames = manifest.authors
|
|
154
|
+
.map((a: { name?: string }) => a.name ?? "unknown")
|
|
155
|
+
.join(", ");
|
|
156
|
+
keyValue("Authors", authorNames);
|
|
157
|
+
}
|
|
158
|
+
|
|
159
|
+
keyValue("Manifest", manifestPath);
|
|
160
|
+
|
|
161
|
+
// Show raw manifest when --verbose is used
|
|
162
|
+
if (options.verbose && manifestPath.endsWith(".md")) {
|
|
163
|
+
const { readFileSync } = require("node:fs");
|
|
164
|
+
const content = readFileSync(manifestPath, "utf-8");
|
|
165
|
+
newline();
|
|
166
|
+
header("Documentation");
|
|
167
|
+
newline();
|
|
168
|
+
console.log(content);
|
|
169
|
+
}
|
|
170
|
+
}
|
|
171
|
+
|
|
172
|
+
/**
|
|
173
|
+
* Info command handler
|
|
174
|
+
*/
|
|
175
|
+
async function infoHandler(
|
|
125
176
|
toolName: string,
|
|
126
|
-
options:
|
|
177
|
+
options: InfoOptions,
|
|
127
178
|
ctx: CommandContext
|
|
128
179
|
): Promise<void> {
|
|
180
|
+
// First, try to resolve locally if it looks like a path
|
|
181
|
+
const resolution = tryResolveTool(toolName, { startDir: ctx.cwd });
|
|
182
|
+
|
|
183
|
+
if (resolution) {
|
|
184
|
+
// Tool found locally
|
|
185
|
+
if (options.json) {
|
|
186
|
+
json({
|
|
187
|
+
name: resolution.manifest.name ?? toolName,
|
|
188
|
+
version: resolution.manifest.version,
|
|
189
|
+
description: resolution.manifest.description,
|
|
190
|
+
command: resolution.manifest.command,
|
|
191
|
+
tags: resolution.manifest.tags,
|
|
192
|
+
authors: resolution.manifest.authors,
|
|
193
|
+
source: "local",
|
|
194
|
+
manifestPath: resolution.manifestPath,
|
|
195
|
+
});
|
|
196
|
+
return;
|
|
197
|
+
}
|
|
198
|
+
|
|
199
|
+
displayLocalToolInfo(
|
|
200
|
+
resolution.manifest.name ?? toolName,
|
|
201
|
+
resolution.manifest as unknown as Record<string, unknown>,
|
|
202
|
+
resolution.manifestPath,
|
|
203
|
+
options
|
|
204
|
+
);
|
|
205
|
+
return;
|
|
206
|
+
}
|
|
207
|
+
|
|
208
|
+
// If --local flag is set, don't fetch from registry
|
|
209
|
+
if (options.local) {
|
|
210
|
+
error(`Tool not found locally: ${toolName}`);
|
|
211
|
+
dim("The tool is not installed. Remove --local to fetch from registry.");
|
|
212
|
+
process.exit(1);
|
|
213
|
+
}
|
|
214
|
+
|
|
129
215
|
const config = loadConfig();
|
|
130
216
|
const registryUrl =
|
|
131
217
|
process.env.ENACT_REGISTRY_URL ??
|
|
@@ -190,17 +276,18 @@ async function getHandler(
|
|
|
190
276
|
}
|
|
191
277
|
|
|
192
278
|
/**
|
|
193
|
-
* Configure the
|
|
279
|
+
* Configure the info command
|
|
194
280
|
*/
|
|
195
|
-
export function
|
|
281
|
+
export function configureInfoCommand(program: Command): void {
|
|
196
282
|
program
|
|
197
|
-
.command("
|
|
198
|
-
.alias("
|
|
199
|
-
.description("Show detailed information about a tool")
|
|
283
|
+
.command("info <tool>")
|
|
284
|
+
.alias("get")
|
|
285
|
+
.description("Show detailed information about a tool (local path or registry)")
|
|
200
286
|
.option("--ver <version>", "Show info for a specific version")
|
|
201
287
|
.option("-v, --verbose", "Show detailed output")
|
|
288
|
+
.option("--local", "Only check locally installed tools")
|
|
202
289
|
.option("--json", "Output as JSON")
|
|
203
|
-
.action(async (toolName: string, options:
|
|
290
|
+
.action(async (toolName: string, options: InfoOptions) => {
|
|
204
291
|
const ctx: CommandContext = {
|
|
205
292
|
cwd: process.cwd(),
|
|
206
293
|
options,
|
|
@@ -209,7 +296,7 @@ export function configureGetCommand(program: Command): void {
|
|
|
209
296
|
};
|
|
210
297
|
|
|
211
298
|
try {
|
|
212
|
-
await
|
|
299
|
+
await infoHandler(toolName, options, ctx);
|
|
213
300
|
} catch (err) {
|
|
214
301
|
error(formatError(err));
|
|
215
302
|
process.exit(1);
|
|
@@ -26,7 +26,7 @@ const SUPABASE_ANON_KEY =
|
|
|
26
26
|
* Embedded templates (for single-binary compatibility)
|
|
27
27
|
*/
|
|
28
28
|
const TEMPLATES: Record<string, string> = {
|
|
29
|
-
"tool-
|
|
29
|
+
"tool-skill.md": `---
|
|
30
30
|
name: {{TOOL_NAME}}
|
|
31
31
|
description: A simple tool that echoes a greeting
|
|
32
32
|
version: 0.1.0
|
|
@@ -74,7 +74,7 @@ Edit this file to create your own tool:
|
|
|
74
74
|
|
|
75
75
|
"tool-agents.md": `# Enact Tool Development Guide
|
|
76
76
|
|
|
77
|
-
Enact tools are containerized, cryptographically-signed executables. Each tool is defined by
|
|
77
|
+
Enact tools are containerized, cryptographically-signed executables. Each tool is defined by a \`SKILL.md\` file (YAML frontmatter + Markdown docs).
|
|
78
78
|
|
|
79
79
|
## Quick Reference
|
|
80
80
|
|
|
@@ -85,7 +85,7 @@ Enact tools are containerized, cryptographically-signed executables. Each tool i
|
|
|
85
85
|
| Dry run | \`enact run ./ --args '{}' --dry-run\` |
|
|
86
86
|
| Sign & publish | \`enact sign ./ && enact publish ./\` |
|
|
87
87
|
|
|
88
|
-
##
|
|
88
|
+
## SKILL.md Structure
|
|
89
89
|
|
|
90
90
|
\`\`\`yaml
|
|
91
91
|
---
|
|
@@ -225,7 +225,7 @@ Tools run in a container with \`/work\` as the working directory. All source fil
|
|
|
225
225
|
|
|
226
226
|
## Secrets
|
|
227
227
|
|
|
228
|
-
Declare in \`
|
|
228
|
+
Declare in \`SKILL.md\`:
|
|
229
229
|
\`\`\`yaml
|
|
230
230
|
env:
|
|
231
231
|
API_KEY:
|
|
@@ -302,6 +302,7 @@ enact run ./path/to/tool --args '{}' # Run local tool
|
|
|
302
302
|
\`\`\`bash
|
|
303
303
|
enact search "pdf extraction" # Search registry
|
|
304
304
|
enact get author/category/tool # View tool info
|
|
305
|
+
enact learn author/category/tool # View tool documentation
|
|
305
306
|
enact install author/category/tool # Add to project (.enact/tools.json)
|
|
306
307
|
enact install author/category/tool --global # Add globally
|
|
307
308
|
enact list # List project tools
|
|
@@ -314,7 +315,7 @@ enact run tool --args '{}' | jq '.result'
|
|
|
314
315
|
\`\`\`
|
|
315
316
|
|
|
316
317
|
## Creating Local Tools
|
|
317
|
-
Create \`tools/<name>/
|
|
318
|
+
Create \`tools/<name>/SKILL.md\` with:
|
|
318
319
|
\`\`\`yaml
|
|
319
320
|
---
|
|
320
321
|
name: my-tool
|
|
@@ -345,6 +346,7 @@ This project uses Enact tools — containerized, signed executables you can run
|
|
|
345
346
|
\`\`\`bash
|
|
346
347
|
enact run <tool> --args '{"key": "value"}' # Run a tool
|
|
347
348
|
enact search "keyword" # Find tools
|
|
349
|
+
enact learn author/tool # View tool documentation
|
|
348
350
|
enact install author/tool # Install tool
|
|
349
351
|
enact list # List installed tools
|
|
350
352
|
\`\`\`
|
|
@@ -360,7 +362,7 @@ enact run tool --args '{}' | jq '.data'
|
|
|
360
362
|
\`\`\`
|
|
361
363
|
|
|
362
364
|
## Creating Tools
|
|
363
|
-
Create \`
|
|
365
|
+
Create \`SKILL.md\` in a directory:
|
|
364
366
|
\`\`\`yaml
|
|
365
367
|
---
|
|
366
368
|
name: namespace/category/tool
|
|
@@ -394,7 +396,7 @@ enact sign ./ && enact publish ./ # Publish
|
|
|
394
396
|
\`\`\`
|
|
395
397
|
|
|
396
398
|
## Secrets
|
|
397
|
-
Declare in
|
|
399
|
+
Declare in SKILL.md, set via CLI:
|
|
398
400
|
\`\`\`yaml
|
|
399
401
|
env:
|
|
400
402
|
API_KEY: # Declared but not set
|
|
@@ -543,9 +545,9 @@ async function initHandler(options: InitOptions, ctx: CommandContext): Promise<v
|
|
|
543
545
|
const isClaudeMode = options.claude;
|
|
544
546
|
// Default to agent mode if no flag specified
|
|
545
547
|
|
|
546
|
-
// Handle --tool mode: create
|
|
548
|
+
// Handle --tool mode: create SKILL.md + AGENTS.md for tool development
|
|
547
549
|
if (isToolMode) {
|
|
548
|
-
const manifestPath = join(targetDir, "
|
|
550
|
+
const manifestPath = join(targetDir, "SKILL.md");
|
|
549
551
|
const agentsPath = join(targetDir, "AGENTS.md");
|
|
550
552
|
|
|
551
553
|
if (existsSync(manifestPath) && !options.force) {
|
|
@@ -571,7 +573,7 @@ async function initHandler(options: InitOptions, ctx: CommandContext): Promise<v
|
|
|
571
573
|
|
|
572
574
|
// Load templates with placeholder replacement
|
|
573
575
|
const replacements = { TOOL_NAME: toolName };
|
|
574
|
-
const manifestContent = loadTemplate("tool-
|
|
576
|
+
const manifestContent = loadTemplate("tool-skill.md", replacements);
|
|
575
577
|
const agentsContent = loadTemplate("tool-agents.md", replacements);
|
|
576
578
|
|
|
577
579
|
// Ensure directory exists
|
|
@@ -579,7 +581,7 @@ async function initHandler(options: InitOptions, ctx: CommandContext): Promise<v
|
|
|
579
581
|
mkdirSync(targetDir, { recursive: true });
|
|
580
582
|
}
|
|
581
583
|
|
|
582
|
-
// Write
|
|
584
|
+
// Write SKILL.md
|
|
583
585
|
writeFileSync(manifestPath, manifestContent, "utf-8");
|
|
584
586
|
success(`Created tool manifest: ${manifestPath}`);
|
|
585
587
|
|
|
@@ -593,7 +595,7 @@ async function initHandler(options: InitOptions, ctx: CommandContext): Promise<v
|
|
|
593
595
|
|
|
594
596
|
info("");
|
|
595
597
|
info("Next steps:");
|
|
596
|
-
info(" 1. Edit
|
|
598
|
+
info(" 1. Edit SKILL.md to customize your tool");
|
|
597
599
|
info(" 2. Run 'enact run ./' to test your tool");
|
|
598
600
|
info(" 3. Run 'enact publish' to share your tool");
|
|
599
601
|
return;
|
|
@@ -637,7 +639,8 @@ async function initHandler(options: InitOptions, ctx: CommandContext): Promise<v
|
|
|
637
639
|
|
|
638
640
|
info("");
|
|
639
641
|
info("This file helps AI agents understand how to use Enact tools in your project.");
|
|
640
|
-
info("Run 'enact search <query>' to find tools, 'enact
|
|
642
|
+
info("Run 'enact search <query>' to find tools, 'enact learn <tool>' to view docs,");
|
|
643
|
+
info("and 'enact install <tool>' to add them.");
|
|
641
644
|
}
|
|
642
645
|
|
|
643
646
|
/**
|
|
@@ -649,7 +652,7 @@ export function configureInitCommand(program: Command): void {
|
|
|
649
652
|
.description("Initialize Enact in the current directory")
|
|
650
653
|
.option("-n, --name <name>", "Tool name (default: username/my-tool)")
|
|
651
654
|
.option("-f, --force", "Overwrite existing files")
|
|
652
|
-
.option("--tool", "Create a new Enact tool (
|
|
655
|
+
.option("--tool", "Create a new Enact tool (SKILL.md + AGENTS.md)")
|
|
653
656
|
.option("--agent", "Create AGENTS.md + .enact/tools.json (default)")
|
|
654
657
|
.option("--claude", "Create CLAUDE.md + .enact/tools.json")
|
|
655
658
|
.option("-v, --verbose", "Show detailed output")
|
|
@@ -3,16 +3,46 @@
|
|
|
3
3
|
*
|
|
4
4
|
* Display the documentation (enact.md) for a tool.
|
|
5
5
|
* Fetches and displays the raw manifest content for easy reading.
|
|
6
|
+
*
|
|
7
|
+
* Security: For tools fetched from the registry, attestation checks are
|
|
8
|
+
* performed according to the trust policy. This prevents potentially
|
|
9
|
+
* malicious documentation from being displayed to LLMs or users.
|
|
6
10
|
*/
|
|
7
11
|
|
|
8
|
-
import {
|
|
9
|
-
|
|
12
|
+
import {
|
|
13
|
+
type AttestationListResponse,
|
|
14
|
+
createApiClient,
|
|
15
|
+
getAttestationList,
|
|
16
|
+
getToolInfo,
|
|
17
|
+
getToolVersion,
|
|
18
|
+
verifyAllAttestations,
|
|
19
|
+
} from "@enactprotocol/api";
|
|
20
|
+
import {
|
|
21
|
+
getMinimumAttestations,
|
|
22
|
+
getTrustPolicy,
|
|
23
|
+
getTrustedAuditors,
|
|
24
|
+
loadConfig,
|
|
25
|
+
tryResolveTool,
|
|
26
|
+
} from "@enactprotocol/shared";
|
|
10
27
|
import type { Command } from "commander";
|
|
11
28
|
import type { CommandContext, GlobalOptions } from "../../types";
|
|
12
|
-
import {
|
|
29
|
+
import {
|
|
30
|
+
TrustError,
|
|
31
|
+
confirm,
|
|
32
|
+
dim,
|
|
33
|
+
error,
|
|
34
|
+
formatError,
|
|
35
|
+
header,
|
|
36
|
+
info,
|
|
37
|
+
json,
|
|
38
|
+
newline,
|
|
39
|
+
success,
|
|
40
|
+
symbols,
|
|
41
|
+
} from "../../utils";
|
|
13
42
|
|
|
14
43
|
interface LearnOptions extends GlobalOptions {
|
|
15
44
|
ver?: string;
|
|
45
|
+
local?: boolean;
|
|
16
46
|
}
|
|
17
47
|
|
|
18
48
|
/**
|
|
@@ -21,14 +51,76 @@ interface LearnOptions extends GlobalOptions {
|
|
|
21
51
|
async function learnHandler(
|
|
22
52
|
toolName: string,
|
|
23
53
|
options: LearnOptions,
|
|
24
|
-
|
|
54
|
+
ctx: CommandContext
|
|
25
55
|
): Promise<void> {
|
|
56
|
+
// First, try to resolve locally (project → user → cache)
|
|
57
|
+
// If the tool is already installed/cached, we trust it
|
|
58
|
+
const resolution = tryResolveTool(toolName, { startDir: ctx.cwd });
|
|
59
|
+
|
|
60
|
+
if (resolution) {
|
|
61
|
+
// Tool is installed locally - read documentation from the manifest file
|
|
62
|
+
if (resolution.manifestPath.endsWith(".md")) {
|
|
63
|
+
const { readFileSync } = await import("node:fs");
|
|
64
|
+
const content = readFileSync(resolution.manifestPath, "utf-8");
|
|
65
|
+
|
|
66
|
+
if (options.json) {
|
|
67
|
+
json({
|
|
68
|
+
name: toolName,
|
|
69
|
+
version: resolution.manifest.version,
|
|
70
|
+
documentation: content,
|
|
71
|
+
source: "local",
|
|
72
|
+
});
|
|
73
|
+
return;
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
header(`${toolName}@${resolution.manifest.version ?? "local"}`);
|
|
77
|
+
dim("(installed locally)");
|
|
78
|
+
newline();
|
|
79
|
+
console.log(content);
|
|
80
|
+
return;
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
// Fallback for non-.md manifests
|
|
84
|
+
if (options.json) {
|
|
85
|
+
json({
|
|
86
|
+
name: toolName,
|
|
87
|
+
version: resolution.manifest.version,
|
|
88
|
+
documentation: resolution.manifest.doc ?? resolution.manifest.description ?? null,
|
|
89
|
+
source: "local",
|
|
90
|
+
});
|
|
91
|
+
return;
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
header(`${toolName}@${resolution.manifest.version ?? "local"}`);
|
|
95
|
+
dim("(installed locally)");
|
|
96
|
+
newline();
|
|
97
|
+
console.log(
|
|
98
|
+
resolution.manifest.doc ?? resolution.manifest.description ?? "No documentation available."
|
|
99
|
+
);
|
|
100
|
+
return;
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
// If --local flag is set, don't fetch from registry
|
|
104
|
+
if (options.local) {
|
|
105
|
+
error(`Tool not found locally: ${toolName}`);
|
|
106
|
+
dim("The tool is not installed. Remove --local to fetch from registry.");
|
|
107
|
+
process.exit(1);
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
// Tool not installed - fetch from registry with attestation checks
|
|
26
111
|
const config = loadConfig();
|
|
27
112
|
const registryUrl =
|
|
28
113
|
process.env.ENACT_REGISTRY_URL ??
|
|
29
114
|
config.registry?.url ??
|
|
30
115
|
"https://siikwkfgsmouioodghho.supabase.co/functions/v1";
|
|
31
|
-
|
|
116
|
+
|
|
117
|
+
// Get auth token - use user token if available, otherwise use anon key for public access
|
|
118
|
+
let authToken = config.registry?.authToken ?? process.env.ENACT_AUTH_TOKEN;
|
|
119
|
+
if (!authToken && registryUrl.includes("siikwkfgsmouioodghho.supabase.co")) {
|
|
120
|
+
authToken =
|
|
121
|
+
"eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpc3MiOiJzdXBhYmFzZSIsInJlZiI6InNpaWt3a2Znc21vdWlvb2RnaGhvIiwicm9sZSI6ImFub24iLCJpYXQiOjE3NjQ2MTkzMzksImV4cCI6MjA4MDE5NTMzOX0.kxnx6-IPFhmGx6rzNx36vbyhFMFZKP_jFqaDbKnJ_E0";
|
|
122
|
+
}
|
|
123
|
+
|
|
32
124
|
const client = createApiClient({
|
|
33
125
|
baseUrl: registryUrl,
|
|
34
126
|
authToken: authToken,
|
|
@@ -42,14 +134,139 @@ async function learnHandler(
|
|
|
42
134
|
version = toolInfo.latestVersion;
|
|
43
135
|
}
|
|
44
136
|
|
|
137
|
+
if (!version) {
|
|
138
|
+
error(`No published versions for ${toolName}`);
|
|
139
|
+
process.exit(1);
|
|
140
|
+
}
|
|
141
|
+
|
|
45
142
|
// Get the version info which includes rawManifest
|
|
46
143
|
const versionInfo = await getToolVersion(client, toolName, version);
|
|
47
144
|
|
|
145
|
+
// ========================================
|
|
146
|
+
// TRUST VERIFICATION - same as run command
|
|
147
|
+
// ========================================
|
|
148
|
+
const trustPolicy = getTrustPolicy();
|
|
149
|
+
const minimumAttestations = getMinimumAttestations();
|
|
150
|
+
const trustedAuditors = getTrustedAuditors();
|
|
151
|
+
|
|
152
|
+
// Fetch attestations from registry
|
|
153
|
+
const attestationsResponse: AttestationListResponse = await getAttestationList(
|
|
154
|
+
client,
|
|
155
|
+
toolName,
|
|
156
|
+
version
|
|
157
|
+
);
|
|
158
|
+
const attestations = attestationsResponse.attestations;
|
|
159
|
+
|
|
160
|
+
if (attestations.length === 0) {
|
|
161
|
+
// No attestations found
|
|
162
|
+
info(`${symbols.warning} Tool ${toolName}@${version} has no attestations.`);
|
|
163
|
+
|
|
164
|
+
if (trustPolicy === "require_attestation") {
|
|
165
|
+
throw new TrustError(
|
|
166
|
+
"Trust policy requires attestations. Cannot display documentation from unverified tools."
|
|
167
|
+
);
|
|
168
|
+
}
|
|
169
|
+
if (ctx.isInteractive && trustPolicy === "prompt") {
|
|
170
|
+
dim("Documentation from unverified tools may contain malicious content.");
|
|
171
|
+
const proceed = await confirm("View documentation from unverified tool?");
|
|
172
|
+
if (!proceed) {
|
|
173
|
+
info("Cancelled.");
|
|
174
|
+
process.exit(0);
|
|
175
|
+
}
|
|
176
|
+
} else if (!ctx.isInteractive && trustPolicy === "prompt") {
|
|
177
|
+
throw new TrustError(
|
|
178
|
+
"Cannot display documentation from unverified tools in non-interactive mode."
|
|
179
|
+
);
|
|
180
|
+
}
|
|
181
|
+
// trustPolicy === "allow" - continue without prompting
|
|
182
|
+
} else {
|
|
183
|
+
// Verify attestations locally (never trust registry's verification status)
|
|
184
|
+
const verifiedAuditors = await verifyAllAttestations(
|
|
185
|
+
client,
|
|
186
|
+
toolName,
|
|
187
|
+
version,
|
|
188
|
+
versionInfo.bundle.hash ?? ""
|
|
189
|
+
);
|
|
190
|
+
|
|
191
|
+
// Check verified auditors against trust config using provider:identity format
|
|
192
|
+
const trustedVerifiedAuditors = verifiedAuditors
|
|
193
|
+
.filter((auditor) => trustedAuditors.includes(auditor.providerIdentity))
|
|
194
|
+
.map((auditor) => auditor.providerIdentity);
|
|
195
|
+
|
|
196
|
+
if (trustedVerifiedAuditors.length > 0) {
|
|
197
|
+
// Check if we meet minimum attestations threshold
|
|
198
|
+
if (trustedVerifiedAuditors.length < minimumAttestations) {
|
|
199
|
+
info(
|
|
200
|
+
`${symbols.warning} Tool ${toolName}@${version} has ${trustedVerifiedAuditors.length} trusted attestation(s), but ${minimumAttestations} required.`
|
|
201
|
+
);
|
|
202
|
+
dim(`Trusted attestations: ${trustedVerifiedAuditors.join(", ")}`);
|
|
203
|
+
|
|
204
|
+
if (trustPolicy === "require_attestation") {
|
|
205
|
+
throw new TrustError(
|
|
206
|
+
`Trust policy requires at least ${minimumAttestations} attestation(s) from trusted identities.`
|
|
207
|
+
);
|
|
208
|
+
}
|
|
209
|
+
if (ctx.isInteractive && trustPolicy === "prompt") {
|
|
210
|
+
const proceed = await confirm(
|
|
211
|
+
"View documentation with fewer attestations than required?"
|
|
212
|
+
);
|
|
213
|
+
if (!proceed) {
|
|
214
|
+
info("Cancelled.");
|
|
215
|
+
process.exit(0);
|
|
216
|
+
}
|
|
217
|
+
} else if (!ctx.isInteractive && trustPolicy === "prompt") {
|
|
218
|
+
throw new TrustError(
|
|
219
|
+
"Cannot display documentation without meeting minimum attestation requirement in non-interactive mode."
|
|
220
|
+
);
|
|
221
|
+
}
|
|
222
|
+
// trustPolicy === "allow" - continue without prompting
|
|
223
|
+
} else {
|
|
224
|
+
// Tool meets or exceeds minimum attestations
|
|
225
|
+
if (options.verbose) {
|
|
226
|
+
success(
|
|
227
|
+
`Tool verified by ${trustedVerifiedAuditors.length} trusted identity(ies): ${trustedVerifiedAuditors.join(", ")}`
|
|
228
|
+
);
|
|
229
|
+
}
|
|
230
|
+
}
|
|
231
|
+
} else {
|
|
232
|
+
// Has attestations but none from trusted auditors
|
|
233
|
+
info(
|
|
234
|
+
`${symbols.warning} Tool ${toolName}@${version} has ${verifiedAuditors.length} attestation(s), but none from trusted auditors.`
|
|
235
|
+
);
|
|
236
|
+
|
|
237
|
+
if (trustPolicy === "require_attestation") {
|
|
238
|
+
dim(`Your trusted auditors: ${trustedAuditors.join(", ")}`);
|
|
239
|
+
dim(`Tool attested by: ${verifiedAuditors.map((a) => a.providerIdentity).join(", ")}`);
|
|
240
|
+
throw new TrustError(
|
|
241
|
+
"Trust policy requires attestations from trusted identities. Cannot display documentation."
|
|
242
|
+
);
|
|
243
|
+
}
|
|
244
|
+
if (ctx.isInteractive && trustPolicy === "prompt") {
|
|
245
|
+
dim(`Attested by: ${verifiedAuditors.map((a) => a.providerIdentity).join(", ")}`);
|
|
246
|
+
dim(`Your trusted auditors: ${trustedAuditors.join(", ")}`);
|
|
247
|
+
const proceed = await confirm("View documentation anyway?");
|
|
248
|
+
if (!proceed) {
|
|
249
|
+
info("Cancelled.");
|
|
250
|
+
process.exit(0);
|
|
251
|
+
}
|
|
252
|
+
} else if (!ctx.isInteractive && trustPolicy === "prompt") {
|
|
253
|
+
throw new TrustError(
|
|
254
|
+
"Cannot display documentation without trusted attestations in non-interactive mode."
|
|
255
|
+
);
|
|
256
|
+
}
|
|
257
|
+
// trustPolicy === "allow" - continue without prompting
|
|
258
|
+
}
|
|
259
|
+
}
|
|
260
|
+
|
|
261
|
+
// ========================================
|
|
262
|
+
// Display documentation (trust verified)
|
|
263
|
+
// ========================================
|
|
48
264
|
if (options.json) {
|
|
49
265
|
json({
|
|
50
266
|
name: toolName,
|
|
51
267
|
version: versionInfo.version,
|
|
52
268
|
documentation: versionInfo.rawManifest ?? null,
|
|
269
|
+
source: "registry",
|
|
53
270
|
});
|
|
54
271
|
return;
|
|
55
272
|
}
|
|
@@ -65,6 +282,10 @@ async function learnHandler(
|
|
|
65
282
|
newline();
|
|
66
283
|
console.log(versionInfo.rawManifest);
|
|
67
284
|
} catch (err) {
|
|
285
|
+
if (err instanceof TrustError) {
|
|
286
|
+
error(err.message);
|
|
287
|
+
process.exit(1);
|
|
288
|
+
}
|
|
68
289
|
if (err instanceof Error) {
|
|
69
290
|
if (err.message.includes("not_found") || err.message.includes("404")) {
|
|
70
291
|
error(`Tool not found: ${toolName}`);
|
|
@@ -88,7 +309,9 @@ export function configureLearnCommand(program: Command): void {
|
|
|
88
309
|
.command("learn <tool>")
|
|
89
310
|
.description("Display documentation (enact.md) for a tool")
|
|
90
311
|
.option("--ver <version>", "Show documentation for a specific version")
|
|
312
|
+
.option("--local", "Only show documentation for locally installed tools")
|
|
91
313
|
.option("--json", "Output as JSON")
|
|
314
|
+
.option("-v, --verbose", "Show detailed output")
|
|
92
315
|
.action(async (toolName: string, options: LearnOptions) => {
|
|
93
316
|
const ctx: CommandContext = {
|
|
94
317
|
cwd: process.cwd(),
|