@enactprotocol/cli 1.2.8 → 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 -231410
- package/dist/index.js.bak +0 -231409
- 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,198 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* enact get command
|
|
3
|
+
*
|
|
4
|
+
* Show detailed information about a tool from the registry.
|
|
5
|
+
*/
|
|
6
|
+
|
|
7
|
+
import {
|
|
8
|
+
type ToolInfo,
|
|
9
|
+
type ToolVersionInfo,
|
|
10
|
+
createApiClient,
|
|
11
|
+
getToolInfo,
|
|
12
|
+
getToolVersion,
|
|
13
|
+
} from "@enactprotocol/api";
|
|
14
|
+
import { loadConfig } from "@enactprotocol/shared";
|
|
15
|
+
import type { Command } from "commander";
|
|
16
|
+
import type { CommandContext, GlobalOptions } from "../../types";
|
|
17
|
+
import {
|
|
18
|
+
dim,
|
|
19
|
+
error,
|
|
20
|
+
formatError,
|
|
21
|
+
header,
|
|
22
|
+
info,
|
|
23
|
+
json,
|
|
24
|
+
keyValue,
|
|
25
|
+
newline,
|
|
26
|
+
success,
|
|
27
|
+
} from "../../utils";
|
|
28
|
+
|
|
29
|
+
interface GetOptions extends GlobalOptions {
|
|
30
|
+
version?: string;
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
/**
|
|
34
|
+
* Format a date for display
|
|
35
|
+
*/
|
|
36
|
+
function formatDate(date: Date): string {
|
|
37
|
+
return date.toLocaleDateString("en-US", {
|
|
38
|
+
year: "numeric",
|
|
39
|
+
month: "short",
|
|
40
|
+
day: "numeric",
|
|
41
|
+
});
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
/**
|
|
45
|
+
* Display tool info
|
|
46
|
+
*/
|
|
47
|
+
function displayToolInfo(tool: ToolInfo, options: GetOptions): void {
|
|
48
|
+
header(tool.name);
|
|
49
|
+
newline();
|
|
50
|
+
|
|
51
|
+
info(tool.description);
|
|
52
|
+
newline();
|
|
53
|
+
|
|
54
|
+
keyValue("Latest Version", tool.latestVersion);
|
|
55
|
+
keyValue("License", tool.license);
|
|
56
|
+
keyValue("Created", formatDate(tool.createdAt));
|
|
57
|
+
keyValue("Updated", formatDate(tool.updatedAt));
|
|
58
|
+
|
|
59
|
+
if (tool.tags.length > 0) {
|
|
60
|
+
keyValue("Tags", tool.tags.join(", "));
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
if (tool.author) {
|
|
64
|
+
keyValue("Author", tool.author.username);
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
newline();
|
|
68
|
+
keyValue("Available Versions", tool.versions.join(", "));
|
|
69
|
+
|
|
70
|
+
if (options.verbose) {
|
|
71
|
+
newline();
|
|
72
|
+
dim("Use --version <ver> to see version-specific details");
|
|
73
|
+
}
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
/**
|
|
77
|
+
* Display version-specific info
|
|
78
|
+
*/
|
|
79
|
+
function displayVersionInfo(version: ToolVersionInfo): void {
|
|
80
|
+
header(`${version.name}@${version.version}`);
|
|
81
|
+
newline();
|
|
82
|
+
|
|
83
|
+
info(version.description);
|
|
84
|
+
newline();
|
|
85
|
+
|
|
86
|
+
keyValue("Version", version.version);
|
|
87
|
+
keyValue("License", version.license);
|
|
88
|
+
if (version.bundle) {
|
|
89
|
+
keyValue("Bundle Hash", version.bundle.hash);
|
|
90
|
+
keyValue("Bundle Size", `${(version.bundle.size / 1024).toFixed(1)} KB`);
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
if (version.yanked) {
|
|
94
|
+
newline();
|
|
95
|
+
dim(`⚠ This version is yanked${version.yankReason ? `: ${version.yankReason}` : ""}`);
|
|
96
|
+
if (version.yankReplacement) {
|
|
97
|
+
dim(` Recommended: ${version.yankReplacement}`);
|
|
98
|
+
}
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
if (version.manifest) {
|
|
102
|
+
newline();
|
|
103
|
+
dim("Manifest:");
|
|
104
|
+
console.log(JSON.stringify(version.manifest, null, 2));
|
|
105
|
+
}
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
/**
|
|
109
|
+
* Get command handler
|
|
110
|
+
*/
|
|
111
|
+
async function getHandler(
|
|
112
|
+
toolName: string,
|
|
113
|
+
options: GetOptions,
|
|
114
|
+
ctx: CommandContext
|
|
115
|
+
): Promise<void> {
|
|
116
|
+
const config = loadConfig();
|
|
117
|
+
const registryUrl =
|
|
118
|
+
process.env.ENACT_REGISTRY_URL ??
|
|
119
|
+
config.registry?.url ??
|
|
120
|
+
"https://siikwkfgsmouioodghho.supabase.co/functions/v1";
|
|
121
|
+
const authToken = config.registry?.authToken;
|
|
122
|
+
const client = createApiClient({
|
|
123
|
+
baseUrl: registryUrl,
|
|
124
|
+
authToken: authToken,
|
|
125
|
+
});
|
|
126
|
+
|
|
127
|
+
if (ctx.options.verbose) {
|
|
128
|
+
info(`Fetching info for: ${toolName}`);
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
try {
|
|
132
|
+
if (options.version) {
|
|
133
|
+
// Get specific version info
|
|
134
|
+
const versionInfo = await getToolVersion(client, toolName, options.version);
|
|
135
|
+
|
|
136
|
+
if (options.json) {
|
|
137
|
+
json(versionInfo);
|
|
138
|
+
return;
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
displayVersionInfo(versionInfo);
|
|
142
|
+
} else {
|
|
143
|
+
// Get general tool info
|
|
144
|
+
const toolInfo = await getToolInfo(client, toolName);
|
|
145
|
+
|
|
146
|
+
if (options.json) {
|
|
147
|
+
json(toolInfo);
|
|
148
|
+
return;
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
displayToolInfo(toolInfo, options);
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
newline();
|
|
155
|
+
success(`Install with: enact install ${toolName}`);
|
|
156
|
+
} catch (err) {
|
|
157
|
+
if (err instanceof Error) {
|
|
158
|
+
if (err.message.includes("not_found") || err.message.includes("404")) {
|
|
159
|
+
error(`Tool not found: ${toolName}`);
|
|
160
|
+
dim("Check the tool name or search with: enact search <query>");
|
|
161
|
+
process.exit(1);
|
|
162
|
+
}
|
|
163
|
+
if (err.message.includes("fetch")) {
|
|
164
|
+
error("Unable to connect to registry. Check your internet connection.");
|
|
165
|
+
process.exit(1);
|
|
166
|
+
}
|
|
167
|
+
}
|
|
168
|
+
throw err;
|
|
169
|
+
}
|
|
170
|
+
}
|
|
171
|
+
|
|
172
|
+
/**
|
|
173
|
+
* Configure the get command
|
|
174
|
+
*/
|
|
175
|
+
export function configureGetCommand(program: Command): void {
|
|
176
|
+
program
|
|
177
|
+
.command("get <tool>")
|
|
178
|
+
.alias("info")
|
|
179
|
+
.description("Show detailed information about a tool")
|
|
180
|
+
.option("-V, --version <version>", "Show info for a specific version")
|
|
181
|
+
.option("-v, --verbose", "Show detailed output")
|
|
182
|
+
.option("--json", "Output as JSON")
|
|
183
|
+
.action(async (toolName: string, options: GetOptions) => {
|
|
184
|
+
const ctx: CommandContext = {
|
|
185
|
+
cwd: process.cwd(),
|
|
186
|
+
options,
|
|
187
|
+
isCI: Boolean(process.env.CI),
|
|
188
|
+
isInteractive: process.stdout.isTTY ?? false,
|
|
189
|
+
};
|
|
190
|
+
|
|
191
|
+
try {
|
|
192
|
+
await getHandler(toolName, options, ctx);
|
|
193
|
+
} catch (err) {
|
|
194
|
+
error(formatError(err));
|
|
195
|
+
process.exit(1);
|
|
196
|
+
}
|
|
197
|
+
});
|
|
198
|
+
}
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* CLI Commands Index
|
|
3
|
+
*
|
|
4
|
+
* Exports all command configuration functions.
|
|
5
|
+
*/
|
|
6
|
+
|
|
7
|
+
export { configureSetupCommand } from "./setup";
|
|
8
|
+
export { configureRunCommand } from "./run";
|
|
9
|
+
export { configureExecCommand } from "./exec";
|
|
10
|
+
export { configureInstallCommand } from "./install";
|
|
11
|
+
export { configureListCommand } from "./list";
|
|
12
|
+
export { configureEnvCommand } from "./env";
|
|
13
|
+
export { configureTrustCommand } from "./trust";
|
|
14
|
+
export { configureConfigCommand } from "./config";
|
|
15
|
+
|
|
16
|
+
// Registry commands (Phase 8)
|
|
17
|
+
export { configureSearchCommand } from "./search";
|
|
18
|
+
export { configureGetCommand } from "./get";
|
|
19
|
+
export { configurePublishCommand } from "./publish";
|
|
20
|
+
export { configureAuthCommand } from "./auth";
|
|
21
|
+
export { configureCacheCommand } from "./cache";
|
|
22
|
+
|
|
23
|
+
// CLI solidification commands (Phase 9)
|
|
24
|
+
export { configureSignCommand } from "./sign";
|
|
25
|
+
export { configureReportCommand } from "./report";
|
|
26
|
+
export { configureInspectCommand } from "./inspect";
|
|
27
|
+
|
|
28
|
+
// API v2 migration commands
|
|
29
|
+
export { configureYankCommand } from "./yank";
|
|
30
|
+
export { configureUnyankCommand } from "./unyank";
|
|
@@ -0,0 +1,264 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* enact inspect command
|
|
3
|
+
*
|
|
4
|
+
* Open a tool's page in the browser for inspection/review.
|
|
5
|
+
* Use --download to download locally for deeper code review.
|
|
6
|
+
* This allows reviewers to examine tool code before trusting it.
|
|
7
|
+
*/
|
|
8
|
+
|
|
9
|
+
import { mkdirSync, rmSync, unlinkSync, writeFileSync } from "node:fs";
|
|
10
|
+
import { dirname, join, resolve } from "node:path";
|
|
11
|
+
import { createApiClient, downloadBundle, getToolInfo } from "@enactprotocol/api";
|
|
12
|
+
import { getCacheDir, loadConfig, pathExists } from "@enactprotocol/shared";
|
|
13
|
+
import type { Command } from "commander";
|
|
14
|
+
import type { CommandContext, GlobalOptions } from "../../types";
|
|
15
|
+
import {
|
|
16
|
+
EXIT_FAILURE,
|
|
17
|
+
RegistryError,
|
|
18
|
+
colors,
|
|
19
|
+
confirm,
|
|
20
|
+
dim,
|
|
21
|
+
error,
|
|
22
|
+
formatError,
|
|
23
|
+
handleError,
|
|
24
|
+
info,
|
|
25
|
+
json,
|
|
26
|
+
keyValue,
|
|
27
|
+
newline,
|
|
28
|
+
success,
|
|
29
|
+
symbols,
|
|
30
|
+
withSpinner,
|
|
31
|
+
} from "../../utils";
|
|
32
|
+
|
|
33
|
+
interface InspectOptions extends GlobalOptions {
|
|
34
|
+
output?: string;
|
|
35
|
+
force?: boolean;
|
|
36
|
+
download?: boolean;
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
/**
|
|
40
|
+
* Parse tool@version syntax
|
|
41
|
+
*/
|
|
42
|
+
function parseToolSpec(spec: string): { name: string; version: string | undefined } {
|
|
43
|
+
const match = spec.match(/^(@[^@/]+\/[^@]+|[^@]+)(?:@(.+))?$/);
|
|
44
|
+
if (match?.[1]) {
|
|
45
|
+
return {
|
|
46
|
+
name: match[1],
|
|
47
|
+
version: match[2],
|
|
48
|
+
};
|
|
49
|
+
}
|
|
50
|
+
return { name: spec, version: undefined };
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
/**
|
|
54
|
+
* Format bytes to human-readable string
|
|
55
|
+
*/
|
|
56
|
+
function formatBytes(bytes: number): string {
|
|
57
|
+
if (bytes === 0) return "0 B";
|
|
58
|
+
const k = 1024;
|
|
59
|
+
const sizes = ["B", "KB", "MB", "GB"];
|
|
60
|
+
const i = Math.floor(Math.log(bytes) / Math.log(k));
|
|
61
|
+
return `${(bytes / k ** i).toFixed(1)} ${sizes[i]}`;
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
/**
|
|
65
|
+
* Extract a tar.gz bundle to a directory
|
|
66
|
+
*/
|
|
67
|
+
async function extractBundle(bundleData: ArrayBuffer, destPath: string): Promise<void> {
|
|
68
|
+
const tempFile = join(getCacheDir(), `inspect-${Date.now()}.tar.gz`);
|
|
69
|
+
mkdirSync(dirname(tempFile), { recursive: true });
|
|
70
|
+
writeFileSync(tempFile, Buffer.from(bundleData));
|
|
71
|
+
|
|
72
|
+
mkdirSync(destPath, { recursive: true });
|
|
73
|
+
|
|
74
|
+
const proc = Bun.spawn(["tar", "-xzf", tempFile, "-C", destPath], {
|
|
75
|
+
stdout: "pipe",
|
|
76
|
+
stderr: "pipe",
|
|
77
|
+
});
|
|
78
|
+
|
|
79
|
+
const exitCode = await proc.exited;
|
|
80
|
+
|
|
81
|
+
try {
|
|
82
|
+
unlinkSync(tempFile);
|
|
83
|
+
} catch {
|
|
84
|
+
// Ignore cleanup errors
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
if (exitCode !== 0) {
|
|
88
|
+
const stderr = await new Response(proc.stderr).text();
|
|
89
|
+
throw new Error(`Failed to extract bundle: ${stderr}`);
|
|
90
|
+
}
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
/**
|
|
94
|
+
* Inspect command handler
|
|
95
|
+
*/
|
|
96
|
+
async function inspectHandler(
|
|
97
|
+
toolSpec: string,
|
|
98
|
+
options: InspectOptions,
|
|
99
|
+
ctx: CommandContext
|
|
100
|
+
): Promise<void> {
|
|
101
|
+
const { name: toolName, version } = parseToolSpec(toolSpec);
|
|
102
|
+
|
|
103
|
+
const config = loadConfig();
|
|
104
|
+
const registryUrl = config.registry?.url ?? "https://registry.enact.tools";
|
|
105
|
+
|
|
106
|
+
// Default behavior: open in browser
|
|
107
|
+
// Use --download to download locally instead
|
|
108
|
+
if (!options.download) {
|
|
109
|
+
const toolUrl = version
|
|
110
|
+
? `${registryUrl}/tools/${toolName}/v/${version}`
|
|
111
|
+
: `${registryUrl}/tools/${toolName}`;
|
|
112
|
+
|
|
113
|
+
info(`Opening ${toolName} in browser...`);
|
|
114
|
+
|
|
115
|
+
// Open URL in default browser
|
|
116
|
+
const openCmd =
|
|
117
|
+
process.platform === "darwin" ? "open" : process.platform === "win32" ? "start" : "xdg-open";
|
|
118
|
+
|
|
119
|
+
const proc = Bun.spawn([openCmd, toolUrl], {
|
|
120
|
+
stdout: "ignore",
|
|
121
|
+
stderr: "ignore",
|
|
122
|
+
});
|
|
123
|
+
await proc.exited;
|
|
124
|
+
|
|
125
|
+
if (options.json) {
|
|
126
|
+
json({ opened: true, url: toolUrl });
|
|
127
|
+
} else {
|
|
128
|
+
success(`Opened: ${toolUrl}`);
|
|
129
|
+
}
|
|
130
|
+
return;
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
const client = createApiClient({
|
|
134
|
+
baseUrl: registryUrl,
|
|
135
|
+
authToken: config.registry?.authToken,
|
|
136
|
+
});
|
|
137
|
+
|
|
138
|
+
// Determine target version
|
|
139
|
+
let targetVersion = version;
|
|
140
|
+
|
|
141
|
+
try {
|
|
142
|
+
if (!targetVersion) {
|
|
143
|
+
const metadata = await withSpinner(
|
|
144
|
+
`Fetching ${toolName} info...`,
|
|
145
|
+
async () => getToolInfo(client, toolName),
|
|
146
|
+
`${symbols.success} Found ${toolName}`
|
|
147
|
+
);
|
|
148
|
+
targetVersion = metadata.latestVersion;
|
|
149
|
+
}
|
|
150
|
+
} catch (err) {
|
|
151
|
+
throw new RegistryError(`Failed to fetch tool info: ${formatError(err)}`);
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
// Determine output directory
|
|
155
|
+
const outputDir = options.output
|
|
156
|
+
? resolve(ctx.cwd, options.output)
|
|
157
|
+
: resolve(ctx.cwd, toolName.split("/").pop() ?? toolName);
|
|
158
|
+
|
|
159
|
+
// Check if output already exists
|
|
160
|
+
if (pathExists(outputDir) && !options.force) {
|
|
161
|
+
if (ctx.isInteractive) {
|
|
162
|
+
const shouldOverwrite = await confirm(`Directory ${outputDir} already exists. Overwrite?`);
|
|
163
|
+
if (!shouldOverwrite) {
|
|
164
|
+
info("Inspection cancelled.");
|
|
165
|
+
return;
|
|
166
|
+
}
|
|
167
|
+
rmSync(outputDir, { recursive: true, force: true });
|
|
168
|
+
} else {
|
|
169
|
+
error(`Directory ${outputDir} already exists. Use --force to overwrite.`);
|
|
170
|
+
process.exit(EXIT_FAILURE);
|
|
171
|
+
}
|
|
172
|
+
}
|
|
173
|
+
|
|
174
|
+
// Download bundle (no trust verification - this is for inspection)
|
|
175
|
+
let bundleResult: { data: ArrayBuffer; hash: string; size: number };
|
|
176
|
+
try {
|
|
177
|
+
bundleResult = await withSpinner(
|
|
178
|
+
`Downloading ${toolName}@${targetVersion}...`,
|
|
179
|
+
async () =>
|
|
180
|
+
downloadBundle(client, {
|
|
181
|
+
name: toolName,
|
|
182
|
+
version: targetVersion!,
|
|
183
|
+
verify: true,
|
|
184
|
+
}),
|
|
185
|
+
`${symbols.success} Downloaded`
|
|
186
|
+
);
|
|
187
|
+
} catch (err) {
|
|
188
|
+
throw new RegistryError(`Failed to download bundle: ${formatError(err)}`);
|
|
189
|
+
}
|
|
190
|
+
|
|
191
|
+
// Extract to output directory
|
|
192
|
+
try {
|
|
193
|
+
await withSpinner(
|
|
194
|
+
"Extracting...",
|
|
195
|
+
async () => extractBundle(bundleResult.data, outputDir),
|
|
196
|
+
`${symbols.success} Extracted`
|
|
197
|
+
);
|
|
198
|
+
} catch (err) {
|
|
199
|
+
throw new RegistryError(`Failed to extract bundle: ${formatError(err)}`);
|
|
200
|
+
}
|
|
201
|
+
|
|
202
|
+
// Output result
|
|
203
|
+
if (options.json) {
|
|
204
|
+
json({
|
|
205
|
+
inspected: true,
|
|
206
|
+
tool: toolName,
|
|
207
|
+
version: targetVersion,
|
|
208
|
+
location: outputDir,
|
|
209
|
+
hash: bundleResult.hash,
|
|
210
|
+
size: bundleResult.size,
|
|
211
|
+
});
|
|
212
|
+
return;
|
|
213
|
+
}
|
|
214
|
+
|
|
215
|
+
newline();
|
|
216
|
+
keyValue("Tool", toolName);
|
|
217
|
+
keyValue("Version", targetVersion ?? "unknown");
|
|
218
|
+
keyValue("Location", outputDir);
|
|
219
|
+
keyValue("Size", formatBytes(bundleResult.size));
|
|
220
|
+
keyValue("Hash", `${bundleResult.hash.substring(0, 20)}...`);
|
|
221
|
+
newline();
|
|
222
|
+
|
|
223
|
+
success(`Downloaded ${colors.bold(toolName)}@${targetVersion} for inspection`);
|
|
224
|
+
newline();
|
|
225
|
+
|
|
226
|
+
info("Next steps:");
|
|
227
|
+
dim(` cd ${outputDir}`);
|
|
228
|
+
dim(" # Review the code, run security scans, test it");
|
|
229
|
+
dim(" # If it passes review, sign it:");
|
|
230
|
+
dim(" enact sign .");
|
|
231
|
+
dim(" # Or report issues:");
|
|
232
|
+
dim(` enact report ${toolName}@${targetVersion} --reason "description"`);
|
|
233
|
+
}
|
|
234
|
+
|
|
235
|
+
/**
|
|
236
|
+
* Configure the inspect command
|
|
237
|
+
*/
|
|
238
|
+
export function configureInspectCommand(program: Command): void {
|
|
239
|
+
program
|
|
240
|
+
.command("inspect <tool[@version]>")
|
|
241
|
+
.description("Open a tool's page in browser for inspection (use --download to save locally)")
|
|
242
|
+
.option("-d, --download", "Download tool locally instead of opening in browser")
|
|
243
|
+
.option(
|
|
244
|
+
"-o, --output <path>",
|
|
245
|
+
"Output directory for download (default: tool name in current directory)"
|
|
246
|
+
)
|
|
247
|
+
.option("-f, --force", "Overwrite existing directory when downloading")
|
|
248
|
+
.option("-v, --verbose", "Show detailed output")
|
|
249
|
+
.option("--json", "Output result as JSON")
|
|
250
|
+
.action(async (toolSpec: string, options: InspectOptions) => {
|
|
251
|
+
const ctx: CommandContext = {
|
|
252
|
+
cwd: process.cwd(),
|
|
253
|
+
options,
|
|
254
|
+
isCI: Boolean(process.env.CI),
|
|
255
|
+
isInteractive: process.stdout.isTTY ?? false,
|
|
256
|
+
};
|
|
257
|
+
|
|
258
|
+
try {
|
|
259
|
+
await inspectHandler(toolSpec, options, ctx);
|
|
260
|
+
} catch (err) {
|
|
261
|
+
handleError(err, options.verbose ? { verbose: true } : undefined);
|
|
262
|
+
}
|
|
263
|
+
});
|
|
264
|
+
}
|
|
@@ -0,0 +1,146 @@
|
|
|
1
|
+
# enact install
|
|
2
|
+
|
|
3
|
+
Install a tool to the project or user directory.
|
|
4
|
+
|
|
5
|
+
## Synopsis
|
|
6
|
+
|
|
7
|
+
```bash
|
|
8
|
+
enact install [tool] [options]
|
|
9
|
+
```
|
|
10
|
+
|
|
11
|
+
## Description
|
|
12
|
+
|
|
13
|
+
The `install` command copies a tool to either the project's `.enact/tools/` directory (default) or the user's `~/.enact/tools/` directory (with `--global`). This makes the tool available for execution without specifying the full path.
|
|
14
|
+
|
|
15
|
+
## Arguments
|
|
16
|
+
|
|
17
|
+
| Argument | Description |
|
|
18
|
+
|----------|-------------|
|
|
19
|
+
| `[tool]` | Tool to install. Can be a tool name, path, `.` for current directory, or omitted to install from `tools.json` |
|
|
20
|
+
|
|
21
|
+
## Options
|
|
22
|
+
|
|
23
|
+
| Option | Description |
|
|
24
|
+
|--------|-------------|
|
|
25
|
+
| `-g, --global` | Install to user directory (`~/.enact/tools/`) instead of project |
|
|
26
|
+
| `-f, --force` | Overwrite existing installation without prompting |
|
|
27
|
+
| `-v, --verbose` | Show detailed output |
|
|
28
|
+
| `--json` | Output result as JSON |
|
|
29
|
+
|
|
30
|
+
## Installation Scopes
|
|
31
|
+
|
|
32
|
+
### Project Scope (Default)
|
|
33
|
+
|
|
34
|
+
```bash
|
|
35
|
+
enact install alice/utils/greeter
|
|
36
|
+
```
|
|
37
|
+
|
|
38
|
+
Installs to `.enact/tools/alice/utils/greeter/` in your project. This is ideal for:
|
|
39
|
+
- Tools specific to a project
|
|
40
|
+
- Team collaboration (commit `.enact/tools.json`)
|
|
41
|
+
- Reproducible builds
|
|
42
|
+
|
|
43
|
+
### Global Scope
|
|
44
|
+
|
|
45
|
+
```bash
|
|
46
|
+
enact install alice/utils/greeter --global
|
|
47
|
+
```
|
|
48
|
+
|
|
49
|
+
Installs to `~/.enact/tools/alice/utils/greeter/`. This is ideal for:
|
|
50
|
+
- Tools you use across multiple projects
|
|
51
|
+
- Personal utility tools
|
|
52
|
+
- System-wide availability
|
|
53
|
+
|
|
54
|
+
## Examples
|
|
55
|
+
|
|
56
|
+
### Install from registry (future)
|
|
57
|
+
|
|
58
|
+
```bash
|
|
59
|
+
# Install to project
|
|
60
|
+
enact install alice/utils/greeter
|
|
61
|
+
|
|
62
|
+
# Install globally
|
|
63
|
+
enact install alice/utils/greeter --global
|
|
64
|
+
```
|
|
65
|
+
|
|
66
|
+
### Install from local path
|
|
67
|
+
|
|
68
|
+
```bash
|
|
69
|
+
# Install current directory as a tool
|
|
70
|
+
enact install .
|
|
71
|
+
|
|
72
|
+
# Install from a specific path
|
|
73
|
+
enact install ./my-tools/greeter
|
|
74
|
+
|
|
75
|
+
# Install with absolute path
|
|
76
|
+
enact install /path/to/tool
|
|
77
|
+
```
|
|
78
|
+
|
|
79
|
+
### Install all project tools
|
|
80
|
+
|
|
81
|
+
```bash
|
|
82
|
+
# Install all tools defined in .enact/tools.json
|
|
83
|
+
enact install
|
|
84
|
+
```
|
|
85
|
+
|
|
86
|
+
### Force reinstall
|
|
87
|
+
|
|
88
|
+
```bash
|
|
89
|
+
# Overwrite existing installation
|
|
90
|
+
enact install alice/utils/greeter --force
|
|
91
|
+
```
|
|
92
|
+
|
|
93
|
+
## Tool Resolution
|
|
94
|
+
|
|
95
|
+
When installing by name, the command searches for the tool in this order:
|
|
96
|
+
|
|
97
|
+
1. Project tools (`.enact/tools/`)
|
|
98
|
+
2. User tools (`~/.enact/tools/`)
|
|
99
|
+
3. Cache (`~/.enact/cache/`)
|
|
100
|
+
4. Registry (future)
|
|
101
|
+
|
|
102
|
+
## Directory Structure
|
|
103
|
+
|
|
104
|
+
After installation, tools are organized by their namespaced name:
|
|
105
|
+
|
|
106
|
+
```
|
|
107
|
+
.enact/tools/
|
|
108
|
+
├── alice/
|
|
109
|
+
│ └── utils/
|
|
110
|
+
│ └── greeter/
|
|
111
|
+
│ ├── enact.md
|
|
112
|
+
│ └── ...
|
|
113
|
+
└── EnactProtocol/
|
|
114
|
+
└── pdf-extract/
|
|
115
|
+
├── enact.yaml
|
|
116
|
+
└── ...
|
|
117
|
+
```
|
|
118
|
+
|
|
119
|
+
## tools.json
|
|
120
|
+
|
|
121
|
+
The `.enact/tools.json` file tracks project dependencies:
|
|
122
|
+
|
|
123
|
+
```json
|
|
124
|
+
{
|
|
125
|
+
"tools": {
|
|
126
|
+
"alice/utils/greeter": "^1.0.0",
|
|
127
|
+
"EnactProtocol/pdf-extract": "2.1.0"
|
|
128
|
+
}
|
|
129
|
+
}
|
|
130
|
+
```
|
|
131
|
+
|
|
132
|
+
Running `enact install` without arguments installs all tools from this file.
|
|
133
|
+
|
|
134
|
+
## Exit Codes
|
|
135
|
+
|
|
136
|
+
| Code | Description |
|
|
137
|
+
|------|-------------|
|
|
138
|
+
| `0` | Successful installation |
|
|
139
|
+
| `1` | Installation failed |
|
|
140
|
+
| `3` | Tool not found |
|
|
141
|
+
|
|
142
|
+
## See Also
|
|
143
|
+
|
|
144
|
+
- [enact list](../list/README.md) - List installed tools
|
|
145
|
+
- [enact run](../run/README.md) - Execute tools
|
|
146
|
+
- [enact search](../search/README.md) - Search for tools (future)
|