enact-cli 1.0.5 → 1.0.7

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.
Files changed (2) hide show
  1. package/dist/index.js +1171 -10
  2. package/package.json +1 -1
package/dist/index.js CHANGED
@@ -7050,7 +7050,7 @@ var require_public_api = __commonJS((exports) => {
7050
7050
  });
7051
7051
 
7052
7052
  // src/index.ts
7053
- var import_picocolors10 = __toESM(require_picocolors(), 1);
7053
+ var import_picocolors12 = __toESM(require_picocolors(), 1);
7054
7054
  import { parseArgs } from "util";
7055
7055
 
7056
7056
  // node_modules/@clack/prompts/dist/index.mjs
@@ -7782,9 +7782,11 @@ ${import_picocolors3.default.bold("Usage:")}
7782
7782
 
7783
7783
  ${import_picocolors3.default.bold("Commands:")}
7784
7784
  ${import_picocolors3.default.green("auth")} Manage authentication (login, logout, status, token)
7785
+ ${import_picocolors3.default.green("exec")} Execute a tool by fetching and running it
7785
7786
  ${import_picocolors3.default.green("init")} Create a new tool definition
7786
7787
  ${import_picocolors3.default.green("publish")} Publish a tool to the registry
7787
7788
  ${import_picocolors3.default.green("search")} Search for tools in the registry
7789
+ ${import_picocolors3.default.green("sign")} Sign and verify tools with cryptographic signatures
7788
7790
  ${import_picocolors3.default.green("remote")} Manage remote servers (add, list, remove)
7789
7791
  ${import_picocolors3.default.green("user")} User operations (get public key)
7790
7792
 
@@ -7793,11 +7795,13 @@ ${import_picocolors3.default.bold("Global Options:")}
7793
7795
  ${import_picocolors3.default.yellow("--version, -v")} Show version information
7794
7796
 
7795
7797
  ${import_picocolors3.default.bold("Examples:")}
7796
- ${import_picocolors3.default.cyan("enact")} ${import_picocolors3.default.dim("# Interactive mode")}
7797
- ${import_picocolors3.default.cyan("enact")} ${import_picocolors3.default.green("search")} ${import_picocolors3.default.yellow("--tags")} web,api ${import_picocolors3.default.dim("# Search tools by tags")}
7798
- ${import_picocolors3.default.cyan("enact")} ${import_picocolors3.default.green("publish")} my-tool.yaml ${import_picocolors3.default.dim("# Publish a tool")}
7799
- ${import_picocolors3.default.cyan("enact")} ${import_picocolors3.default.green("auth")} login ${import_picocolors3.default.dim("# Login with OAuth")}
7800
- ${import_picocolors3.default.cyan("enact")} ${import_picocolors3.default.green("init")} ${import_picocolors3.default.yellow("--minimal")} ${import_picocolors3.default.dim("# Create minimal tool template")}
7798
+ ${import_picocolors3.default.cyan("enact")} ${import_picocolors3.default.dim("# Interactive mode")}
7799
+ ${import_picocolors3.default.cyan("enact")} ${import_picocolors3.default.green("search")} ${import_picocolors3.default.yellow("--tags")} web,api ${import_picocolors3.default.dim("# Search tools by tags")}
7800
+ ${import_picocolors3.default.cyan("enact")} ${import_picocolors3.default.green("exec")} enact/text/slugify ${import_picocolors3.default.dim("# Execute a tool")}
7801
+ ${import_picocolors3.default.cyan("enact")} ${import_picocolors3.default.green("sign")} verify my-tool.yaml ${import_picocolors3.default.dim("# Verify tool signatures")}
7802
+ ${import_picocolors3.default.cyan("enact")} ${import_picocolors3.default.green("publish")} my-tool.yaml ${import_picocolors3.default.dim("# Publish a tool")}
7803
+ ${import_picocolors3.default.cyan("enact")} ${import_picocolors3.default.green("auth")} login ${import_picocolors3.default.dim("# Login with OAuth")}
7804
+ ${import_picocolors3.default.cyan("enact")} ${import_picocolors3.default.green("init")} ${import_picocolors3.default.yellow("--minimal")} ${import_picocolors3.default.dim("# Create minimal tool template")}
7801
7805
 
7802
7806
  ${import_picocolors3.default.bold("More Help:")}
7803
7807
  ${import_picocolors3.default.cyan("enact")} ${import_picocolors3.default.green("<command>")} ${import_picocolors3.default.yellow("--help")} ${import_picocolors3.default.dim("# Show command-specific help")}
@@ -9442,6 +9446,7 @@ Options:
9442
9446
  --limit <number> Maximum number of results (default: 20)
9443
9447
  --tags <tags> Filter by tags (comma-separated)
9444
9448
  --format <format> Output format: table, json, list (default: table)
9449
+ --json Output results as JSON (shorthand for --format json)
9445
9450
  --author <author> Filter by author
9446
9451
 
9447
9452
  Examples:
@@ -9449,6 +9454,7 @@ Examples:
9449
9454
  enact search formatter --tags cli,text
9450
9455
  enact search --author myorg
9451
9456
  enact search prettier --limit 5 --format json
9457
+ enact search prettier --limit 5 --json
9452
9458
  `);
9453
9459
  return;
9454
9460
  }
@@ -9820,6 +9826,1102 @@ ${import_picocolors9.default.bold("EXAMPLES")}
9820
9826
  `);
9821
9827
  }
9822
9828
 
9829
+ // src/commands/exec.ts
9830
+ import { spawn } from "child_process";
9831
+ import { readFileSync as readFileSync3, existsSync as existsSync7 } from "fs";
9832
+ import { resolve } from "path";
9833
+ var import_picocolors10 = __toESM(require_picocolors(), 1);
9834
+
9835
+ // src/security/sign.ts
9836
+ import * as crypto from "crypto";
9837
+ import * as fs from "fs";
9838
+ import * as path from "path";
9839
+ function createCanonicalToolDefinition(tool) {
9840
+ const canonical = {};
9841
+ const orderedFields = [
9842
+ "name",
9843
+ "description",
9844
+ "command",
9845
+ "protocol_version",
9846
+ "version",
9847
+ "timeout",
9848
+ "tags",
9849
+ "input_schema",
9850
+ "output_schema",
9851
+ "annotations",
9852
+ "env_vars",
9853
+ "examples",
9854
+ "resources",
9855
+ "doc",
9856
+ "authors",
9857
+ "enact"
9858
+ ];
9859
+ for (const field of orderedFields) {
9860
+ if (tool[field] !== undefined) {
9861
+ canonical[field] = tool[field];
9862
+ }
9863
+ }
9864
+ const remainingFields = Object.keys(tool).filter((key) => !orderedFields.includes(key)).sort();
9865
+ for (const field of remainingFields) {
9866
+ if (tool[field] !== undefined) {
9867
+ canonical[field] = tool[field];
9868
+ }
9869
+ }
9870
+ return canonical;
9871
+ }
9872
+ function createCanonicalToolJson(toolData) {
9873
+ const toolRecord = {
9874
+ name: toolData.name,
9875
+ description: toolData.description,
9876
+ command: toolData.command,
9877
+ protocol_version: toolData.protocol_version,
9878
+ version: toolData.version,
9879
+ timeout: toolData.timeout,
9880
+ tags: toolData.tags,
9881
+ input_schema: toolData.input_schema,
9882
+ output_schema: toolData.output_schema,
9883
+ annotations: toolData.annotations,
9884
+ env_vars: toolData.env_vars,
9885
+ examples: toolData.examples,
9886
+ resources: toolData.resources,
9887
+ doc: toolData.doc,
9888
+ authors: toolData.authors,
9889
+ enact: toolData.enact || "1.0.0"
9890
+ };
9891
+ const canonical = createCanonicalToolDefinition(toolRecord);
9892
+ return JSON.stringify(canonical, Object.keys(canonical).sort());
9893
+ }
9894
+ var DEFAULT_POLICY = {
9895
+ minimumSignatures: 1,
9896
+ allowedAlgorithms: ["sha256"]
9897
+ };
9898
+ var TRUSTED_KEYS_DIR = path.join(process.env.HOME || ".", ".enact", "trusted-keys");
9899
+ function getTrustedPublicKeysMap() {
9900
+ const trustedKeys = new Map;
9901
+ if (fs.existsSync(TRUSTED_KEYS_DIR)) {
9902
+ try {
9903
+ const files = fs.readdirSync(TRUSTED_KEYS_DIR);
9904
+ for (const file of files) {
9905
+ if (file.endsWith(".pem")) {
9906
+ const keyPath = path.join(TRUSTED_KEYS_DIR, file);
9907
+ const pemContent = fs.readFileSync(keyPath, "utf8");
9908
+ const base64Key = pemToBase64(pemContent);
9909
+ trustedKeys.set(base64Key, pemContent);
9910
+ }
9911
+ }
9912
+ } catch (error) {
9913
+ console.error(`Error reading trusted keys: ${error.message}`);
9914
+ }
9915
+ }
9916
+ return trustedKeys;
9917
+ }
9918
+ function pemToBase64(pem) {
9919
+ return pem.replace(/-----BEGIN PUBLIC KEY-----/, "").replace(/-----END PUBLIC KEY-----/, "").replace(/\s/g, "");
9920
+ }
9921
+ function base64ToPem(base64) {
9922
+ return `-----BEGIN PUBLIC KEY-----
9923
+ ${base64.match(/.{1,64}/g)?.join(`
9924
+ `)}
9925
+ -----END PUBLIC KEY-----`;
9926
+ }
9927
+ async function signTool(toolPath, privateKeyPath, publicKeyPath, signerInfo, outputPath) {
9928
+ const toolYaml = fs.readFileSync(toolPath, "utf8");
9929
+ const privateKey = fs.readFileSync(privateKeyPath, "utf8");
9930
+ const publicKeyPem = fs.readFileSync(publicKeyPath, "utf8");
9931
+ const tool = $parse(toolYaml);
9932
+ const toolForSigning = { ...tool };
9933
+ delete toolForSigning.signatures;
9934
+ const canonicalJson = createCanonicalToolJson(toolForSigning);
9935
+ console.log("=== SIGNING DEBUG (WEBAPP COMPATIBLE) ===");
9936
+ console.log("Tool for signing:", JSON.stringify(toolForSigning, null, 2));
9937
+ console.log("Canonical JSON (webapp format):", canonicalJson);
9938
+ console.log("Canonical JSON length:", canonicalJson.length);
9939
+ console.log("==========================================");
9940
+ const toolHashBytes = await hashTool(toolForSigning);
9941
+ const { webcrypto } = await import("node:crypto");
9942
+ const privateKeyData = crypto.createPrivateKey({
9943
+ key: privateKey,
9944
+ format: "pem",
9945
+ type: "pkcs8"
9946
+ }).export({ format: "der", type: "pkcs8" });
9947
+ const privateKeyObj = await webcrypto.subtle.importKey("pkcs8", privateKeyData, { name: "ECDSA", namedCurve: "P-256" }, false, ["sign"]);
9948
+ const signatureArrayBuffer = await webcrypto.subtle.sign({ name: "ECDSA", hash: { name: "SHA-256" } }, privateKeyObj, toolHashBytes);
9949
+ const signature = new Uint8Array(signatureArrayBuffer);
9950
+ const signatureB64 = Buffer.from(signature).toString("base64");
9951
+ console.log("Generated signature (Web Crypto API):", signatureB64);
9952
+ console.log("Signature length:", signature.length, "bytes (should be 64 for P-256)");
9953
+ const publicKeyBase64 = pemToBase64(publicKeyPem);
9954
+ if (!tool.signatures) {
9955
+ tool.signatures = {};
9956
+ }
9957
+ tool.signatures[publicKeyBase64] = {
9958
+ algorithm: "sha256",
9959
+ type: "ecdsa-p256",
9960
+ signer: signerInfo.id,
9961
+ created: new Date().toISOString(),
9962
+ value: signatureB64,
9963
+ ...signerInfo.role && { role: signerInfo.role }
9964
+ };
9965
+ const signedToolYaml = $stringify(tool);
9966
+ if (outputPath) {
9967
+ fs.writeFileSync(outputPath, signedToolYaml);
9968
+ }
9969
+ return signedToolYaml;
9970
+ }
9971
+ async function hashTool(tool) {
9972
+ const canonical = createCanonicalToolDefinition(tool);
9973
+ const { signature, ...toolForSigning } = canonical;
9974
+ const canonicalJson = JSON.stringify(toolForSigning, Object.keys(toolForSigning).sort());
9975
+ console.log("\uD83D\uDD0D Canonical JSON for hashing:", canonicalJson);
9976
+ console.log("\uD83D\uDD0D Canonical JSON length:", canonicalJson.length);
9977
+ const encoder = new TextEncoder;
9978
+ const data = encoder.encode(canonicalJson);
9979
+ const { webcrypto } = await import("node:crypto");
9980
+ const hashBuffer = await webcrypto.subtle.digest("SHA-256", data);
9981
+ const hashBytes = new Uint8Array(hashBuffer);
9982
+ console.log("\uD83D\uDD0D SHA-256 hash length:", hashBytes.length, "bytes (should be 32)");
9983
+ return hashBytes;
9984
+ }
9985
+ async function verifyToolSignature(toolObject, signatureB64, publicKeyObj) {
9986
+ try {
9987
+ const toolHash = await hashTool(toolObject);
9988
+ const signatureBytes = new Uint8Array(atob(signatureB64).split("").map((char) => char.charCodeAt(0)));
9989
+ console.log("\uD83D\uDD0D Tool hash byte length:", toolHash.length, "(should be 32 for SHA-256)");
9990
+ console.log("\uD83D\uDD0D Signature bytes length:", signatureBytes.length, "(should be 64 for P-256)");
9991
+ const { webcrypto } = await import("node:crypto");
9992
+ const isValid = await webcrypto.subtle.verify({ name: "ECDSA", hash: { name: "SHA-256" } }, publicKeyObj, signatureBytes, toolHash);
9993
+ console.log("\uD83C\uDFAF Web Crypto API verification result:", isValid);
9994
+ return isValid;
9995
+ } catch (error) {
9996
+ console.error("❌ Verification error:", error);
9997
+ return false;
9998
+ }
9999
+ }
10000
+ async function verifyTool(toolYaml, policy = DEFAULT_POLICY) {
10001
+ const errors2 = [];
10002
+ const verifiedSigners = [];
10003
+ try {
10004
+ const trustedKeys = getTrustedPublicKeysMap();
10005
+ if (trustedKeys.size === 0) {
10006
+ return {
10007
+ isValid: false,
10008
+ message: "No trusted public keys available",
10009
+ validSignatures: 0,
10010
+ totalSignatures: 0,
10011
+ verifiedSigners: [],
10012
+ errors: ["No trusted keys configured"]
10013
+ };
10014
+ }
10015
+ if (process.env.DEBUG) {
10016
+ console.log("Trusted keys available:");
10017
+ for (const [key, pem] of trustedKeys.entries()) {
10018
+ console.log(` Key: ${key.substring(0, 20)}...`);
10019
+ }
10020
+ }
10021
+ const tool = typeof toolYaml === "string" ? $parse(toolYaml) : toolYaml;
10022
+ if (!tool.signatures || Object.keys(tool.signatures).length === 0) {
10023
+ return {
10024
+ isValid: false,
10025
+ message: "No signatures found in the tool",
10026
+ validSignatures: 0,
10027
+ totalSignatures: 0,
10028
+ verifiedSigners: [],
10029
+ errors: ["No signatures found"]
10030
+ };
10031
+ }
10032
+ const totalSignatures = Object.keys(tool.signatures).length;
10033
+ const toolForVerification = { ...tool };
10034
+ delete toolForVerification.signatures;
10035
+ const toolHashBytes = await hashTool(toolForVerification);
10036
+ if (true) {
10037
+ console.log("=== VERIFICATION DEBUG (WEBAPP COMPATIBLE) ===");
10038
+ console.log("Original tool signature field:", Object.keys(tool.signatures || {}));
10039
+ console.log("Tool before removing signatures:", JSON.stringify(tool, null, 2));
10040
+ console.log("Tool for verification:", JSON.stringify(toolForVerification, null, 2));
10041
+ console.log("Tool hash bytes length:", toolHashBytes.length, "(should be 32 for SHA-256)");
10042
+ console.log("==============================================");
10043
+ }
10044
+ let validSignatures = 0;
10045
+ for (const [publicKeyBase64, signatureData] of Object.entries(tool.signatures)) {
10046
+ try {
10047
+ if (policy.allowedAlgorithms && !policy.allowedAlgorithms.includes(signatureData.algorithm)) {
10048
+ errors2.push(`Signature by ${signatureData.signer}: unsupported algorithm ${signatureData.algorithm}`);
10049
+ continue;
10050
+ }
10051
+ if (policy.trustedSigners && !policy.trustedSigners.includes(signatureData.signer)) {
10052
+ errors2.push(`Signature by ${signatureData.signer}: signer not in trusted list`);
10053
+ continue;
10054
+ }
10055
+ const publicKeyPem = trustedKeys.get(publicKeyBase64);
10056
+ if (!publicKeyPem) {
10057
+ const reconstructedPem = base64ToPem(publicKeyBase64);
10058
+ if (!trustedKeys.has(pemToBase64(reconstructedPem))) {
10059
+ errors2.push(`Signature by ${signatureData.signer}: public key not trusted`);
10060
+ continue;
10061
+ }
10062
+ }
10063
+ if (process.env.DEBUG) {
10064
+ console.log("Looking for public key:", publicKeyBase64);
10065
+ console.log("Key found in trusted keys:", !!publicKeyPem);
10066
+ }
10067
+ let isValid2 = false;
10068
+ try {
10069
+ const publicKeyToUse = publicKeyPem || base64ToPem(publicKeyBase64);
10070
+ if (process.env.DEBUG) {
10071
+ console.log("Signature base64:", signatureData.value);
10072
+ console.log("Signature buffer length (should be 64):", Buffer.from(signatureData.value, "base64").length);
10073
+ console.log("Public key base64:", publicKeyBase64);
10074
+ }
10075
+ if (signatureData.type === "ecdsa-p256") {
10076
+ const { webcrypto } = await import("node:crypto");
10077
+ const publicKeyData = crypto.createPublicKey({
10078
+ key: publicKeyToUse,
10079
+ format: "pem",
10080
+ type: "spki"
10081
+ }).export({ format: "der", type: "spki" });
10082
+ const publicKeyObj = await webcrypto.subtle.importKey("spki", publicKeyData, { name: "ECDSA", namedCurve: "P-256" }, false, ["verify"]);
10083
+ isValid2 = await verifyToolSignature(toolForVerification, signatureData.value, publicKeyObj);
10084
+ if (process.env.DEBUG) {
10085
+ console.log("Web Crypto API verification result (webapp compatible):", isValid2);
10086
+ }
10087
+ } else {
10088
+ const verify = crypto.createVerify("SHA256");
10089
+ const canonicalJson = createCanonicalToolJson(toolForVerification);
10090
+ verify.update(canonicalJson, "utf8");
10091
+ const signature = Buffer.from(signatureData.value, "base64");
10092
+ isValid2 = verify.verify(publicKeyToUse, signature);
10093
+ }
10094
+ } catch (verifyError) {
10095
+ errors2.push(`Signature by ${signatureData.signer}: verification error - ${verifyError.message}`);
10096
+ continue;
10097
+ }
10098
+ if (isValid2) {
10099
+ validSignatures++;
10100
+ verifiedSigners.push({
10101
+ signer: signatureData.signer,
10102
+ role: signatureData.role,
10103
+ keyId: publicKeyBase64.substring(0, 8)
10104
+ });
10105
+ } else {
10106
+ errors2.push(`Signature by ${signatureData.signer}: cryptographic verification failed`);
10107
+ }
10108
+ } catch (error) {
10109
+ errors2.push(`Signature by ${signatureData.signer}: verification error - ${error.message}`);
10110
+ }
10111
+ }
10112
+ const policyErrors = [];
10113
+ if (policy.minimumSignatures && validSignatures < policy.minimumSignatures) {
10114
+ policyErrors.push(`Policy requires ${policy.minimumSignatures} signatures, but only ${validSignatures} valid`);
10115
+ }
10116
+ if (policy.requireRoles && policy.requireRoles.length > 0) {
10117
+ const verifiedRoles = verifiedSigners.map((s) => s.role).filter(Boolean);
10118
+ const missingRoles = policy.requireRoles.filter((role) => !verifiedRoles.includes(role));
10119
+ if (missingRoles.length > 0) {
10120
+ policyErrors.push(`Policy requires roles: ${missingRoles.join(", ")}`);
10121
+ }
10122
+ }
10123
+ const isValid = policyErrors.length === 0 && validSignatures > 0;
10124
+ const allErrors = [...errors2, ...policyErrors];
10125
+ let message;
10126
+ if (isValid) {
10127
+ message = `Tool "${tool.name}" verified with ${validSignatures}/${totalSignatures} valid signatures`;
10128
+ if (verifiedSigners.length > 0) {
10129
+ const signerInfo = verifiedSigners.map((s) => `${s.signer}${s.role ? ` (${s.role})` : ""}`).join(", ");
10130
+ message += ` from: ${signerInfo}`;
10131
+ }
10132
+ } else {
10133
+ message = `Tool "${tool.name}" verification failed: ${allErrors[0] || "Unknown error"}`;
10134
+ }
10135
+ return {
10136
+ isValid,
10137
+ message,
10138
+ validSignatures,
10139
+ totalSignatures,
10140
+ verifiedSigners,
10141
+ errors: allErrors
10142
+ };
10143
+ } catch (error) {
10144
+ return {
10145
+ isValid: false,
10146
+ message: `Verification error: ${error.message}`,
10147
+ validSignatures: 0,
10148
+ totalSignatures: 0,
10149
+ verifiedSigners: [],
10150
+ errors: [error.message]
10151
+ };
10152
+ }
10153
+ }
10154
+ var VERIFICATION_POLICIES = {
10155
+ PERMISSIVE: {
10156
+ minimumSignatures: 1,
10157
+ allowedAlgorithms: ["sha256"]
10158
+ },
10159
+ ENTERPRISE: {
10160
+ minimumSignatures: 2,
10161
+ requireRoles: ["author", "reviewer"],
10162
+ allowedAlgorithms: ["sha256"]
10163
+ },
10164
+ PARANOID: {
10165
+ minimumSignatures: 3,
10166
+ requireRoles: ["author", "reviewer", "approver"],
10167
+ allowedAlgorithms: ["sha256"]
10168
+ }
10169
+ };
10170
+
10171
+ // src/commands/exec.ts
10172
+ async function loadLocalTool(filePath) {
10173
+ const resolvedPath = resolve(filePath);
10174
+ if (!existsSync7(resolvedPath)) {
10175
+ throw new Error(`Tool file not found: ${resolvedPath}`);
10176
+ }
10177
+ try {
10178
+ const fileContent = readFileSync3(resolvedPath, "utf8");
10179
+ const toolData = $parse(fileContent);
10180
+ const toolDefinition = {
10181
+ ...toolData,
10182
+ raw_content: fileContent
10183
+ };
10184
+ if (!toolDefinition.name) {
10185
+ throw new Error("Tool must have a name");
10186
+ }
10187
+ if (!toolDefinition.command) {
10188
+ throw new Error("Tool must have a command");
10189
+ }
10190
+ return toolDefinition;
10191
+ } catch (error) {
10192
+ if (error instanceof $YAMLParseError) {
10193
+ throw new Error(`Invalid YAML in tool file: ${error.message}`);
10194
+ }
10195
+ throw error;
10196
+ }
10197
+ }
10198
+ function isLocalToolPath(toolIdentifier) {
10199
+ return toolIdentifier.includes("/") && (toolIdentifier.endsWith(".yaml") || toolIdentifier.endsWith(".yml") || existsSync7(resolve(toolIdentifier)));
10200
+ }
10201
+ async function handleExecCommand(args, options) {
10202
+ if (options.help) {
10203
+ console.log(`
10204
+ Usage: enact exec <tool-name-or-path> [options]
10205
+
10206
+ Execute an Enact tool by fetching its definition from the registry or loading from a local file.
10207
+
10208
+ Arguments:
10209
+ tool-name-or-path Name of the tool (e.g., "enact/text/slugify") or path to local YAML file
10210
+
10211
+ Options:
10212
+ --help, -h Show this help message
10213
+ --input <data> Input data as JSON string or stdin
10214
+ --params <params> Parameters as JSON object
10215
+ --timeout <time> Override tool timeout (Go duration format: 30s, 5m, 1h)
10216
+ --dry Show command that would be executed without running it
10217
+ --verbose, -v Show detailed execution information
10218
+ --skip-verification Skip signature verification (not recommended)
10219
+ --verify-policy Verification policy: permissive, enterprise, paranoid (default: permissive)
10220
+ --force Force execution even if signature verification fails
10221
+
10222
+ Security Options:
10223
+ permissive Require 1+ valid signatures from trusted keys (default)
10224
+ enterprise Require author + reviewer signatures
10225
+ paranoid Require author + reviewer + approver signatures
10226
+
10227
+ Examples:
10228
+ enact exec enact/text/slugify --input "Hello World"
10229
+ enact exec org/ai/review --params '{"file": "README.md"}' --verify-policy enterprise
10230
+ enact exec ./my-tool.yaml --input "test data"
10231
+ enact exec untrusted/tool --skip-verification # Not recommended
10232
+ `);
10233
+ return;
10234
+ }
10235
+ let toolIdentifier = args[0];
10236
+ if (!toolIdentifier) {
10237
+ Ie(import_picocolors10.default.bgMagenta(import_picocolors10.default.white(" Execute Enact Tool ")));
10238
+ toolIdentifier = await he({
10239
+ message: "Enter the tool name or path to execute:",
10240
+ placeholder: "e.g., enact/text/slugify, ./my-tool.yaml",
10241
+ validate: (value) => {
10242
+ if (!value.trim())
10243
+ return "Please enter a tool name or file path";
10244
+ const trimmed = value.trim();
10245
+ if (isLocalToolPath(trimmed)) {
10246
+ return;
10247
+ }
10248
+ if (!/^[a-zA-Z0-9_-]+\/[a-zA-Z0-9_\/-]+$/.test(trimmed)) {
10249
+ return "Tool name must follow hierarchical format: org/category/tool-name, or be a path to a YAML file";
10250
+ }
10251
+ return;
10252
+ }
10253
+ });
10254
+ if (!toolIdentifier) {
10255
+ Se(import_picocolors10.default.yellow("Execution cancelled"));
10256
+ return;
10257
+ }
10258
+ }
10259
+ const isLocalFile = isLocalToolPath(toolIdentifier);
10260
+ const spinner = Y2();
10261
+ spinner.start(isLocalFile ? "Loading local tool definition..." : "Fetching tool definition...");
10262
+ let toolDefinition;
10263
+ try {
10264
+ if (isLocalFile) {
10265
+ toolDefinition = await loadLocalTool(toolIdentifier);
10266
+ spinner.stop("Local tool definition loaded");
10267
+ } else {
10268
+ toolDefinition = await enactApi.getTool(toolIdentifier);
10269
+ spinner.stop("Tool definition fetched");
10270
+ }
10271
+ } catch (error) {
10272
+ spinner.stop(isLocalFile ? "Failed to load local tool" : "Failed to fetch tool definition");
10273
+ if (!isLocalFile && error instanceof EnactApiError && error.statusCode === 404) {
10274
+ Se(import_picocolors10.default.red(`✗ Tool "${toolIdentifier}" not found`));
10275
+ } else {
10276
+ Se(import_picocolors10.default.red(`✗ Failed to ${isLocalFile ? "load" : "fetch"} tool: ${error instanceof Error ? error.message : "Unknown error"}`));
10277
+ }
10278
+ return;
10279
+ }
10280
+ if (!options.skipVerification) {
10281
+ spinner.start("Verifying tool signatures...");
10282
+ try {
10283
+ const policyName = (options.verifyPolicy || "permissive").toUpperCase();
10284
+ const policy = VERIFICATION_POLICIES[policyName] || VERIFICATION_POLICIES.PERMISSIVE;
10285
+ if (options.verbose) {
10286
+ console.log(import_picocolors10.default.cyan(`
10287
+ \uD83D\uDD10 Using verification policy: ${policyName.toLowerCase()}`));
10288
+ if (policy.minimumSignatures)
10289
+ console.log(` - Minimum signatures: ${policy.minimumSignatures}`);
10290
+ }
10291
+ let toolForVerification;
10292
+ if (isLocalFile) {
10293
+ toolForVerification = toolDefinition.raw_content;
10294
+ } else if (toolDefinition.raw_content && toolDefinition.signatures) {
10295
+ const originalTool = JSON.parse(toolDefinition.raw_content);
10296
+ if (!originalTool.enact) {
10297
+ originalTool.enact = "1.0.0";
10298
+ }
10299
+ toolForVerification = {
10300
+ ...originalTool,
10301
+ signatures: toolDefinition.signatures
10302
+ };
10303
+ } else {
10304
+ toolForVerification = toolDefinition;
10305
+ if (!toolForVerification.enact) {
10306
+ toolForVerification.enact = "1.0.0";
10307
+ }
10308
+ }
10309
+ const verification = await verifyTool(toolForVerification, policy);
10310
+ spinner.stop("Signature verification completed");
10311
+ if (verification.isValid) {
10312
+ console.log(import_picocolors10.default.green(`✅ ${verification.message}`));
10313
+ if (options.verbose && verification.verifiedSigners.length > 0) {
10314
+ console.log(import_picocolors10.default.cyan(`
10315
+ \uD83D\uDD12 Verified signers:`));
10316
+ verification.verifiedSigners.forEach((signer) => {
10317
+ console.log(` - ${signer.signer}${signer.role ? ` (${signer.role})` : ""} [${signer.keyId}]`);
10318
+ });
10319
+ }
10320
+ } else {
10321
+ console.log(import_picocolors10.default.red(`❌ ${verification.message}`));
10322
+ if (verification.errors.length > 0) {
10323
+ console.log(import_picocolors10.default.red(`
10324
+ Verification errors:`));
10325
+ verification.errors.forEach((error) => {
10326
+ console.log(import_picocolors10.default.red(` - ${error}`));
10327
+ });
10328
+ }
10329
+ if (!options.force) {
10330
+ const shouldContinue = await ye({
10331
+ message: "Tool signature verification failed. Continue anyway?",
10332
+ initialValue: false
10333
+ });
10334
+ if (!shouldContinue) {
10335
+ Se(import_picocolors10.default.yellow("Execution cancelled for security"));
10336
+ return;
10337
+ }
10338
+ } else {
10339
+ console.log(import_picocolors10.default.yellow("⚠️ Proceeding due to --force flag"));
10340
+ }
10341
+ }
10342
+ } catch (error) {
10343
+ spinner.stop("Verification failed");
10344
+ console.log(import_picocolors10.default.red(`❌ Signature verification error: ${error.message}`));
10345
+ if (!options.force) {
10346
+ const shouldContinue = await ye({
10347
+ message: "Signature verification failed with error. Continue anyway?",
10348
+ initialValue: false
10349
+ });
10350
+ if (!shouldContinue) {
10351
+ Se(import_picocolors10.default.yellow("Execution cancelled for security"));
10352
+ return;
10353
+ }
10354
+ }
10355
+ }
10356
+ } else {
10357
+ console.log(import_picocolors10.default.yellow("⚠️ Signature verification skipped - tool may be untrusted"));
10358
+ }
10359
+ if (options.verbose) {
10360
+ console.log(import_picocolors10.default.cyan(`
10361
+ \uD83D\uDCCB Tool Information:`));
10362
+ console.log(`Name: ${toolDefinition.name}`);
10363
+ console.log(`Description: ${toolDefinition.description}`);
10364
+ console.log(`Command: ${toolDefinition.command}`);
10365
+ if (toolDefinition.timeout)
10366
+ console.log(`Timeout: ${toolDefinition.timeout}`);
10367
+ if (toolDefinition.tags)
10368
+ console.log(`Tags: ${toolDefinition.tags.join(", ")}`);
10369
+ if (toolDefinition.signatures) {
10370
+ const sigCount = Object.keys(toolDefinition.signatures).length;
10371
+ console.log(`Signatures: ${sigCount} signature(s) found`);
10372
+ } else {
10373
+ console.log(`Signatures: No signatures found`);
10374
+ }
10375
+ }
10376
+ let params = {};
10377
+ if (options.params) {
10378
+ try {
10379
+ params = JSON.parse(options.params);
10380
+ } catch (error) {
10381
+ Se(import_picocolors10.default.red(`✗ Invalid JSON in --params: ${error instanceof Error ? error.message : "Unknown error"}`));
10382
+ return;
10383
+ }
10384
+ }
10385
+ if (options.input) {
10386
+ try {
10387
+ const inputData = JSON.parse(options.input);
10388
+ params = { ...params, ...inputData };
10389
+ } catch {
10390
+ params.input = options.input;
10391
+ }
10392
+ }
10393
+ const remainingArgs = args.slice(1);
10394
+ for (const arg of remainingArgs) {
10395
+ if (arg.includes("=")) {
10396
+ const [key, ...valueParts] = arg.split("=");
10397
+ const value = valueParts.join("=");
10398
+ const cleanValue = value.replace(/^["']|["']$/g, "");
10399
+ params[key] = cleanValue;
10400
+ }
10401
+ }
10402
+ if (toolDefinition.inputSchema && Object.keys(params).length === 0) {
10403
+ const needsParams = await ye({
10404
+ message: "This tool requires parameters. Would you like to provide them interactively?",
10405
+ initialValue: true
10406
+ });
10407
+ if (needsParams) {
10408
+ params = await collectParametersInteractively(toolDefinition.inputSchema);
10409
+ }
10410
+ }
10411
+ const command = await buildCommand(toolDefinition.command, params);
10412
+ if (options.dry) {
10413
+ console.log(import_picocolors10.default.cyan(`
10414
+ \uD83D\uDD0D Command that would be executed:`));
10415
+ console.log(import_picocolors10.default.white(command));
10416
+ console.log(import_picocolors10.default.cyan(`
10417
+ Environment variables:`));
10418
+ if (toolDefinition.env) {
10419
+ Object.entries(toolDefinition.env).forEach(([key, config]) => {
10420
+ const value = process.env[key] || config.default || "<not set>";
10421
+ console.log(` ${key}=${value}`);
10422
+ });
10423
+ } else {
10424
+ console.log(" (none required)");
10425
+ }
10426
+ return;
10427
+ }
10428
+ if (toolDefinition.env) {
10429
+ const missingEnvVars = await checkEnvironmentVariables(toolDefinition.env);
10430
+ if (missingEnvVars.length > 0) {
10431
+ Se(import_picocolors10.default.red(`✗ Missing required environment variables: ${missingEnvVars.join(", ")}`));
10432
+ return;
10433
+ }
10434
+ }
10435
+ const timeout = options.timeout || toolDefinition.timeout || "30s";
10436
+ await executeCommand(command, timeout, options.verbose);
10437
+ if (!isLocalFile) {
10438
+ try {
10439
+ await enactApi.logToolUsage(toolIdentifier, {
10440
+ action: "execute",
10441
+ metadata: {
10442
+ hasParams: Object.keys(params).length > 0,
10443
+ timeout,
10444
+ verificationSkipped: options.skipVerification || false,
10445
+ verificationPolicy: options.verifyPolicy || "permissive",
10446
+ timestamp: new Date().toISOString()
10447
+ }
10448
+ });
10449
+ } catch (error) {
10450
+ if (options.verbose) {
10451
+ console.log(import_picocolors10.default.yellow("⚠ Failed to log usage statistics"));
10452
+ }
10453
+ }
10454
+ }
10455
+ }
10456
+ async function collectParametersInteractively(inputSchema) {
10457
+ const params = {};
10458
+ if (inputSchema.properties) {
10459
+ for (const [key, schema] of Object.entries(inputSchema.properties)) {
10460
+ const prop = schema;
10461
+ const isRequired = inputSchema.required?.includes(key) || false;
10462
+ let value;
10463
+ if (prop.type === "string") {
10464
+ value = await he({
10465
+ message: `Enter ${key}:`,
10466
+ placeholder: prop.description || `Value for ${key}`,
10467
+ validate: isRequired ? (val) => val.trim() ? undefined : `${key} is required` : undefined
10468
+ });
10469
+ } else if (prop.type === "number" || prop.type === "integer") {
10470
+ value = await he({
10471
+ message: `Enter ${key} (number):`,
10472
+ placeholder: prop.description || `Number value for ${key}`,
10473
+ validate: (val) => {
10474
+ if (!val.trim() && isRequired)
10475
+ return `${key} is required`;
10476
+ if (val.trim() && isNaN(Number(val)))
10477
+ return "Must be a valid number";
10478
+ return;
10479
+ }
10480
+ });
10481
+ if (value)
10482
+ value = Number(value);
10483
+ } else if (prop.type === "boolean") {
10484
+ value = await ye({
10485
+ message: `${key}:`,
10486
+ initialValue: false
10487
+ });
10488
+ } else {
10489
+ value = await he({
10490
+ message: `Enter ${key} (JSON):`,
10491
+ placeholder: prop.description || `JSON value for ${key}`,
10492
+ validate: (val) => {
10493
+ if (!val.trim() && isRequired)
10494
+ return `${key} is required`;
10495
+ if (val.trim()) {
10496
+ try {
10497
+ JSON.parse(val);
10498
+ } catch {
10499
+ return "Must be valid JSON";
10500
+ }
10501
+ }
10502
+ return;
10503
+ }
10504
+ });
10505
+ if (value) {
10506
+ try {
10507
+ value = JSON.parse(value);
10508
+ } catch {}
10509
+ }
10510
+ }
10511
+ if (value !== null && value !== undefined && value !== "") {
10512
+ params[key] = value;
10513
+ }
10514
+ }
10515
+ }
10516
+ return params;
10517
+ }
10518
+ async function buildCommand(template, params) {
10519
+ let command = template;
10520
+ for (const [key, value] of Object.entries(params)) {
10521
+ const placeholder = `\${${key}}`;
10522
+ const escapedValue = typeof value === "string" ? value.replace(/'/g, `'"'"'`) : String(value);
10523
+ command = command.replace(new RegExp(placeholder.replace(/[.*+?^${}()|[\]\\]/g, "\\$&"), "g"), escapedValue);
10524
+ }
10525
+ return command;
10526
+ }
10527
+ async function checkEnvironmentVariables(envConfig) {
10528
+ const missing = [];
10529
+ for (const [key, config] of Object.entries(envConfig)) {
10530
+ if (config.required && !process.env[key] && !config.default) {
10531
+ missing.push(key);
10532
+ }
10533
+ }
10534
+ return missing;
10535
+ }
10536
+ async function executeCommand(command, timeout, verbose = false) {
10537
+ return new Promise((resolve2, reject) => {
10538
+ if (verbose) {
10539
+ console.log(import_picocolors10.default.cyan(`
10540
+ \uD83D\uDE80 Executing command:`));
10541
+ console.log(import_picocolors10.default.white(command));
10542
+ }
10543
+ const spinner = Y2();
10544
+ spinner.start("Executing tool...");
10545
+ const timeoutMs = parseTimeout(timeout);
10546
+ const child = spawn("sh", ["-c", command], {
10547
+ stdio: ["pipe", "pipe", "pipe"],
10548
+ timeout: timeoutMs
10549
+ });
10550
+ let stdout = "";
10551
+ let stderr = "";
10552
+ child.stdout.on("data", (data) => {
10553
+ stdout += data.toString();
10554
+ });
10555
+ child.stderr.on("data", (data) => {
10556
+ stderr += data.toString();
10557
+ });
10558
+ child.on("close", (code) => {
10559
+ spinner.stop("Execution completed");
10560
+ if (code === 0) {
10561
+ console.log(import_picocolors10.default.green(`
10562
+ ✅ Tool executed successfully`));
10563
+ if (stdout.trim()) {
10564
+ console.log(import_picocolors10.default.cyan(`
10565
+ \uD83D\uDCE4 Output:`));
10566
+ console.log(stdout.trim());
10567
+ }
10568
+ resolve2();
10569
+ } else {
10570
+ console.log(import_picocolors10.default.red(`
10571
+ ❌ Tool execution failed (exit code: ${code})`));
10572
+ if (stderr.trim()) {
10573
+ console.log(import_picocolors10.default.red(`
10574
+ \uD83D\uDCE4 Error output:`));
10575
+ console.log(stderr.trim());
10576
+ }
10577
+ if (stdout.trim()) {
10578
+ console.log(import_picocolors10.default.yellow(`
10579
+ \uD83D\uDCE4 Standard output:`));
10580
+ console.log(stdout.trim());
10581
+ }
10582
+ reject(new Error(`Command failed with exit code ${code}`));
10583
+ }
10584
+ });
10585
+ child.on("error", (error) => {
10586
+ spinner.stop("Execution failed");
10587
+ console.log(import_picocolors10.default.red(`
10588
+ ❌ Failed to execute command: ${error.message}`));
10589
+ reject(error);
10590
+ });
10591
+ });
10592
+ }
10593
+ function parseTimeout(timeout) {
10594
+ const match = timeout.match(/^(\d+)([smh])$/);
10595
+ if (!match)
10596
+ return 30000;
10597
+ const [, value, unit] = match;
10598
+ const num = parseInt(value);
10599
+ switch (unit) {
10600
+ case "s":
10601
+ return num * 1000;
10602
+ case "m":
10603
+ return num * 60 * 1000;
10604
+ case "h":
10605
+ return num * 60 * 60 * 1000;
10606
+ default:
10607
+ return 30000;
10608
+ }
10609
+ }
10610
+
10611
+ // src/commands/sign.ts
10612
+ var import_picocolors11 = __toESM(require_picocolors(), 1);
10613
+ import * as fs2 from "fs";
10614
+ import * as path2 from "path";
10615
+ async function handleSignCommand(args, options) {
10616
+ if (options.help) {
10617
+ console.log(`
10618
+ Usage: enact sign <subcommand> [options]
10619
+
10620
+ Manage tool signatures and verification.
10621
+
10622
+ Subcommands:
10623
+ verify <tool-path> [policy] Verify tool signatures
10624
+ list-keys List trusted public keys
10625
+ sign <tool-path> Sign a tool (requires private key)
10626
+
10627
+ Options:
10628
+ --help, -h Show this help message
10629
+ --policy <policy> Verification policy: permissive, enterprise, paranoid
10630
+ --private-key <path> Path to private key for signing
10631
+ --role <role> Role for signature: author, reviewer, approver
10632
+ --signer <name> Signer identifier
10633
+ --verbose, -v Show detailed information
10634
+
10635
+ Verification Policies:
10636
+ permissive Require 1+ valid signatures from trusted keys (default)
10637
+ enterprise Require author + reviewer signatures
10638
+ paranoid Require author + reviewer + approver signatures
10639
+
10640
+ Examples:
10641
+ enact sign verify my-tool.yaml
10642
+ enact sign verify my-tool.yaml enterprise
10643
+ enact sign list-keys
10644
+ enact sign sign my-tool.yaml --private-key ~/.enact/private.pem --role author
10645
+ `);
10646
+ return;
10647
+ }
10648
+ const subcommand = args[0];
10649
+ if (!subcommand) {
10650
+ Ie(import_picocolors11.default.bgBlue(import_picocolors11.default.white(" Enact Tool Signing ")));
10651
+ const action = await ve({
10652
+ message: "What would you like to do?",
10653
+ options: [
10654
+ { value: "verify", label: "\uD83D\uDD0D Verify tool signatures" },
10655
+ { value: "list-keys", label: "\uD83D\uDD11 List trusted keys" },
10656
+ { value: "sign", label: "✍️ Sign a tool" },
10657
+ { value: "help", label: "❓ Show help" }
10658
+ ]
10659
+ });
10660
+ if (action === null) {
10661
+ Se(import_picocolors11.default.yellow("Operation cancelled"));
10662
+ return;
10663
+ }
10664
+ if (action === "help") {
10665
+ await handleSignCommand(["help"], { help: true });
10666
+ return;
10667
+ }
10668
+ if (action === "verify") {
10669
+ await handleVerifyCommand([], options);
10670
+ return;
10671
+ }
10672
+ if (action === "list-keys") {
10673
+ await handleListKeysCommand([], options);
10674
+ return;
10675
+ }
10676
+ if (action === "sign") {
10677
+ await handleSignToolCommand([], options);
10678
+ return;
10679
+ }
10680
+ return;
10681
+ }
10682
+ switch (subcommand.toLowerCase()) {
10683
+ case "verify":
10684
+ await handleVerifyCommand(args.slice(1), options);
10685
+ break;
10686
+ case "list-keys":
10687
+ await handleListKeysCommand(args.slice(1), options);
10688
+ break;
10689
+ case "sign":
10690
+ await handleSignToolCommand(args.slice(1), options);
10691
+ break;
10692
+ default:
10693
+ console.error(import_picocolors11.default.red(`Unknown subcommand: ${subcommand}`));
10694
+ console.log(import_picocolors11.default.yellow('Use "enact sign --help" for available commands'));
10695
+ process.exit(1);
10696
+ }
10697
+ }
10698
+ async function handleVerifyCommand(args, options) {
10699
+ let toolPath = args[0];
10700
+ let policyName = args[1] || options.policy || "permissive";
10701
+ if (!toolPath) {
10702
+ Ie(import_picocolors11.default.bgGreen(import_picocolors11.default.white(" Verify Tool Signatures ")));
10703
+ toolPath = await he({
10704
+ message: "Enter path to tool YAML file:",
10705
+ placeholder: "./my-tool.yaml",
10706
+ validate: (value) => {
10707
+ if (!value.trim())
10708
+ return "Please enter a file path";
10709
+ if (!fs2.existsSync(value.trim()))
10710
+ return "File does not exist";
10711
+ return;
10712
+ }
10713
+ });
10714
+ if (!toolPath) {
10715
+ Se(import_picocolors11.default.yellow("Verification cancelled"));
10716
+ return;
10717
+ }
10718
+ policyName = await ve({
10719
+ message: "Select verification policy:",
10720
+ options: [
10721
+ { value: "permissive", label: "Permissive - Require 1+ valid signatures (default)" },
10722
+ { value: "enterprise", label: "Enterprise - Require author + reviewer signatures" },
10723
+ { value: "paranoid", label: "Paranoid - Require author + reviewer + approver signatures" }
10724
+ ]
10725
+ });
10726
+ }
10727
+ const policyKey = policyName.toUpperCase();
10728
+ const policy = VERIFICATION_POLICIES[policyKey] || VERIFICATION_POLICIES.PERMISSIVE;
10729
+ if (options.verbose) {
10730
+ console.log(import_picocolors11.default.cyan(`
10731
+ \uD83D\uDCCB Verification Details:`));
10732
+ console.log(`Tool: ${toolPath}`);
10733
+ console.log(`Policy: ${policyName}`);
10734
+ if (policy.minimumSignatures)
10735
+ console.log(`Minimum signatures: ${policy.minimumSignatures}`);
10736
+ if (policy.requireRoles)
10737
+ console.log(`Required roles: ${policy.requireRoles.join(", ")}`);
10738
+ }
10739
+ try {
10740
+ const spinner = Y2();
10741
+ spinner.start("Verifying tool signatures...");
10742
+ const toolYaml = fs2.readFileSync(toolPath, "utf8");
10743
+ const result = await verifyTool(toolYaml, policy);
10744
+ spinner.stop("Verification completed");
10745
+ if (result.isValid) {
10746
+ console.log(import_picocolors11.default.green(`
10747
+ ✅ VERIFICATION PASSED`));
10748
+ console.log(import_picocolors11.default.green(`${result.message}`));
10749
+ } else {
10750
+ console.log(import_picocolors11.default.red(`
10751
+ ❌ VERIFICATION FAILED`));
10752
+ console.log(import_picocolors11.default.red(`${result.message}`));
10753
+ }
10754
+ console.log(import_picocolors11.default.cyan(`
10755
+ \uD83D\uDCCA Signature Summary:`));
10756
+ console.log(`Valid signatures: ${result.validSignatures}/${result.totalSignatures}`);
10757
+ if (result.verifiedSigners.length > 0) {
10758
+ console.log(import_picocolors11.default.cyan(`
10759
+ \uD83D\uDD12 Verified signers:`));
10760
+ result.verifiedSigners.forEach((signer) => {
10761
+ console.log(` - ${signer.signer}${signer.role ? ` (${signer.role})` : ""} [${signer.keyId}]`);
10762
+ });
10763
+ }
10764
+ if (result.errors.length > 0) {
10765
+ console.log(import_picocolors11.default.yellow(`
10766
+ ⚠️ Issues found:`));
10767
+ result.errors.forEach((error) => console.log(` - ${error}`));
10768
+ }
10769
+ if (!result.isValid) {
10770
+ Se(import_picocolors11.default.red("Tool verification failed"));
10771
+ process.exit(1);
10772
+ } else {
10773
+ Se(import_picocolors11.default.green("Tool verification passed"));
10774
+ }
10775
+ } catch (error) {
10776
+ console.error(import_picocolors11.default.red(`
10777
+ ❌ Error verifying tool: ${error.message}`));
10778
+ process.exit(1);
10779
+ }
10780
+ }
10781
+ async function handleListKeysCommand(args, options) {
10782
+ Ie(import_picocolors11.default.bgCyan(import_picocolors11.default.white(" Trusted Public Keys ")));
10783
+ try {
10784
+ const trustedKeys = getTrustedPublicKeysMap();
10785
+ if (trustedKeys.size === 0) {
10786
+ console.log(import_picocolors11.default.yellow(`
10787
+ \uD83D\uDCED No trusted keys found`));
10788
+ console.log(import_picocolors11.default.dim("Add trusted keys to: ~/.enact/trusted-keys/"));
10789
+ return;
10790
+ }
10791
+ console.log(import_picocolors11.default.cyan(`
10792
+ \uD83D\uDD11 Found ${trustedKeys.size} trusted key(s):
10793
+ `));
10794
+ let keyIndex = 1;
10795
+ for (const [base64Key, pemContent] of trustedKeys.entries()) {
10796
+ const keyId = base64Key.substring(0, 16);
10797
+ const shortKey = base64Key.substring(0, 32) + "...";
10798
+ console.log(`${keyIndex}. Key ID: ${import_picocolors11.default.green(keyId)}`);
10799
+ console.log(` Preview: ${import_picocolors11.default.dim(shortKey)}`);
10800
+ if (options.verbose) {
10801
+ console.log(` Full PEM:
10802
+ ${import_picocolors11.default.dim(pemContent)}`);
10803
+ }
10804
+ console.log("");
10805
+ keyIndex++;
10806
+ }
10807
+ if (!options.verbose) {
10808
+ console.log(import_picocolors11.default.dim("Use --verbose to see full PEM content"));
10809
+ }
10810
+ const trustedKeysDir = path2.join(process.env.HOME || ".", ".enact", "trusted-keys");
10811
+ console.log(import_picocolors11.default.cyan(`\uD83D\uDCC1 Trusted keys directory: ${trustedKeysDir}`));
10812
+ Se(import_picocolors11.default.green(`Listed ${trustedKeys.size} trusted keys`));
10813
+ } catch (error) {
10814
+ console.error(import_picocolors11.default.red(`
10815
+ ❌ Error listing keys: ${error.message}`));
10816
+ process.exit(1);
10817
+ }
10818
+ }
10819
+ async function handleSignToolCommand(args, options) {
10820
+ let toolPath = args[0];
10821
+ let privateKeyPath = options.privateKey;
10822
+ let role = options.role;
10823
+ let signerName = options.signer;
10824
+ if (!toolPath) {
10825
+ Ie(import_picocolors11.default.bgMagenta(import_picocolors11.default.white(" Sign Tool ")));
10826
+ toolPath = await he({
10827
+ message: "Enter path to tool YAML file:",
10828
+ placeholder: "./my-tool.yaml",
10829
+ validate: (value) => {
10830
+ if (!value.trim())
10831
+ return "Please enter a file path";
10832
+ if (!fs2.existsSync(value.trim()))
10833
+ return "File does not exist";
10834
+ return;
10835
+ }
10836
+ });
10837
+ if (!toolPath) {
10838
+ Se(import_picocolors11.default.yellow("Signing cancelled"));
10839
+ return;
10840
+ }
10841
+ }
10842
+ if (!privateKeyPath) {
10843
+ privateKeyPath = await he({
10844
+ message: "Enter path to private key PEM file:",
10845
+ placeholder: "~/.enact/private.pem",
10846
+ validate: (value) => {
10847
+ if (!value.trim())
10848
+ return "Please enter a private key path";
10849
+ const expandedPath = value.replace(/^~/, process.env.HOME || "");
10850
+ if (!fs2.existsSync(expandedPath))
10851
+ return "Private key file does not exist";
10852
+ return;
10853
+ }
10854
+ });
10855
+ if (!privateKeyPath) {
10856
+ Se(import_picocolors11.default.yellow("Signing cancelled"));
10857
+ return;
10858
+ }
10859
+ }
10860
+ const expandedPrivateKeyPath = privateKeyPath.replace(/^~/, process.env.HOME || "");
10861
+ const publicKeyPath = expandedPrivateKeyPath.replace(/private\.pem$/, "public.pem").replace(/-private\.pem$/, "-public.pem");
10862
+ if (!fs2.existsSync(publicKeyPath)) {
10863
+ console.error(import_picocolors11.default.red(`
10864
+ ❌ Public key not found at: ${publicKeyPath}`));
10865
+ console.log(import_picocolors11.default.yellow("Expected public key file alongside private key"));
10866
+ return;
10867
+ }
10868
+ if (!role) {
10869
+ role = await ve({
10870
+ message: "Select your role for this signature:",
10871
+ options: [
10872
+ { value: "author", label: "Author - Original creator of the tool" },
10873
+ { value: "reviewer", label: "Reviewer - Code reviewer/auditor" },
10874
+ { value: "approver", label: "Approver - Final approver for production use" }
10875
+ ]
10876
+ });
10877
+ }
10878
+ if (!signerName) {
10879
+ signerName = await he({
10880
+ message: "Enter your signer identifier:",
10881
+ placeholder: "your-username or email@domain.com",
10882
+ validate: (value) => {
10883
+ if (!value.trim())
10884
+ return "Please enter a signer identifier";
10885
+ return;
10886
+ }
10887
+ });
10888
+ if (!signerName) {
10889
+ Se(import_picocolors11.default.yellow("Signing cancelled"));
10890
+ return;
10891
+ }
10892
+ }
10893
+ try {
10894
+ const spinner = Y2();
10895
+ spinner.start("Signing tool...");
10896
+ const signedYaml = await signTool(toolPath, expandedPrivateKeyPath, publicKeyPath, { id: signerName, role }, toolPath);
10897
+ spinner.stop("Tool signed successfully");
10898
+ console.log(import_picocolors11.default.green(`
10899
+ ✅ Tool signed successfully!`));
10900
+ console.log(import_picocolors11.default.cyan(`
10901
+ \uD83D\uDCCB Signature Details:`));
10902
+ console.log(`Tool: ${toolPath}`);
10903
+ console.log(`Signer: ${signerName}`);
10904
+ console.log(`Role: ${role}`);
10905
+ console.log(`Private key: ${expandedPrivateKeyPath}`);
10906
+ console.log(`Public key: ${publicKeyPath}`);
10907
+ if (options.verbose) {
10908
+ console.log(import_picocolors11.default.cyan(`
10909
+ \uD83D\uDCC4 Signed YAML preview:`));
10910
+ const lines = signedYaml.split(`
10911
+ `);
10912
+ const previewLines = lines.slice(0, 10).join(`
10913
+ `);
10914
+ console.log(import_picocolors11.default.dim(previewLines + (lines.length > 10 ? `
10915
+ ...` : "")));
10916
+ }
10917
+ Se(import_picocolors11.default.green("Tool signature added to YAML file"));
10918
+ } catch (error) {
10919
+ console.error(import_picocolors11.default.red(`
10920
+ ❌ Error signing tool: ${error.message}`));
10921
+ process.exit(1);
10922
+ }
10923
+ }
10924
+
9823
10925
  // src/index.ts
9824
10926
  var { values, positionals } = parseArgs({
9825
10927
  args: process.argv,
@@ -9862,9 +10964,38 @@ var { values, positionals } = parseArgs({
9862
10964
  type: "string",
9863
10965
  short: "f"
9864
10966
  },
10967
+ json: {
10968
+ type: "boolean"
10969
+ },
9865
10970
  author: {
9866
10971
  type: "string",
9867
10972
  short: "a"
10973
+ },
10974
+ input: {
10975
+ type: "string",
10976
+ short: "i"
10977
+ },
10978
+ params: {
10979
+ type: "string"
10980
+ },
10981
+ dry: {
10982
+ type: "boolean"
10983
+ },
10984
+ verbose: {
10985
+ type: "boolean",
10986
+ short: "v"
10987
+ },
10988
+ policy: {
10989
+ type: "string"
10990
+ },
10991
+ "private-key": {
10992
+ type: "string"
10993
+ },
10994
+ role: {
10995
+ type: "string"
10996
+ },
10997
+ signer: {
10998
+ type: "string"
9868
10999
  }
9869
11000
  },
9870
11001
  allowPositionals: true,
@@ -9902,7 +11033,7 @@ async function main() {
9902
11033
  help: values.help,
9903
11034
  limit: values.limit ? parseInt(values.limit) : undefined,
9904
11035
  tags: values.tags ? (values.tags + "").split(",").map((t) => t.trim()) : undefined,
9905
- format: values.format,
11036
+ format: values.json ? "json" : values.format,
9906
11037
  author: values.author
9907
11038
  });
9908
11039
  break;
@@ -9925,17 +11056,39 @@ async function main() {
9925
11056
  format: values.format
9926
11057
  });
9927
11058
  break;
11059
+ case "exec":
11060
+ await handleExecCommand(commandArgs, {
11061
+ help: values.help,
11062
+ input: values.input,
11063
+ params: values.params,
11064
+ timeout: values.timeout,
11065
+ dry: values.dry,
11066
+ verbose: values.verbose
11067
+ });
11068
+ break;
11069
+ case "sign":
11070
+ await handleSignCommand(commandArgs, {
11071
+ help: values.help,
11072
+ policy: values.policy,
11073
+ privateKey: values["private-key"],
11074
+ role: values.role,
11075
+ signer: values.signer,
11076
+ verbose: values.verbose
11077
+ });
11078
+ break;
9928
11079
  case undefined:
9929
11080
  if (values.help) {
9930
11081
  showHelp();
9931
11082
  } else {
9932
- Ie(import_picocolors10.default.bgCyan(import_picocolors10.default.black(" Enact CLI ")));
11083
+ Ie(import_picocolors12.default.bgCyan(import_picocolors12.default.black(" Enact CLI ")));
9933
11084
  const action = await ve({
9934
11085
  message: "What would you like to do?",
9935
11086
  options: [
9936
11087
  { value: "search", label: "\uD83D\uDD0D Search for tools" },
11088
+ { value: "exec", label: "⚡ Execute a tool" },
9937
11089
  { value: "publish", label: "\uD83D\uDCE4 Publish a tool" },
9938
11090
  { value: "init", label: "\uD83D\uDCDD Create a new tool definition" },
11091
+ { value: "sign", label: "✍️ Sign & verify tools" },
9939
11092
  { value: "auth", label: "\uD83D\uDD10 Manage authentication" },
9940
11093
  { value: "remote", label: "\uD83C\uDF10 Manage remote servers" },
9941
11094
  { value: "user", label: "\uD83D\uDC64 User operations" },
@@ -9955,6 +11108,14 @@ async function main() {
9955
11108
  await handleSearchCommand([], {});
9956
11109
  return;
9957
11110
  }
11111
+ if (action === "exec") {
11112
+ await handleExecCommand([], {});
11113
+ return;
11114
+ }
11115
+ if (action === "sign") {
11116
+ await handleSignCommand([], {});
11117
+ return;
11118
+ }
9958
11119
  if (action === "auth") {
9959
11120
  const authAction = await ve({
9960
11121
  message: "Authentication:",
@@ -10007,12 +11168,12 @@ async function main() {
10007
11168
  }
10008
11169
  break;
10009
11170
  default:
10010
- console.error(import_picocolors10.default.red(`Unknown command: ${command}`));
11171
+ console.error(import_picocolors12.default.red(`Unknown command: ${command}`));
10011
11172
  showHelp();
10012
11173
  process.exit(1);
10013
11174
  }
10014
11175
  } catch (error) {
10015
- console.error(import_picocolors10.default.red(`Error: ${error.message}`));
11176
+ console.error(import_picocolors12.default.red(`Error: ${error.message}`));
10016
11177
  process.exit(1);
10017
11178
  }
10018
11179
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "enact-cli",
3
- "version": "1.0.5",
3
+ "version": "1.0.7",
4
4
  "description": "Official CLI for the Enact Protocol - package, secure, and discover AI tools",
5
5
  "type": "module",
6
6
  "main": "./dist/index.js",