enact-cli 1.0.4 → 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 +1172 -10
  2. package/package.json +2 -2
package/dist/index.js CHANGED
@@ -1,3 +1,4 @@
1
+ #!/usr/bin/env node
1
2
  import { createRequire } from "node:module";
2
3
  var __create = Object.create;
3
4
  var __getProtoOf = Object.getPrototypeOf;
@@ -7049,7 +7050,7 @@ var require_public_api = __commonJS((exports) => {
7049
7050
  });
7050
7051
 
7051
7052
  // src/index.ts
7052
- var import_picocolors10 = __toESM(require_picocolors(), 1);
7053
+ var import_picocolors12 = __toESM(require_picocolors(), 1);
7053
7054
  import { parseArgs } from "util";
7054
7055
 
7055
7056
  // node_modules/@clack/prompts/dist/index.mjs
@@ -7781,9 +7782,11 @@ ${import_picocolors3.default.bold("Usage:")}
7781
7782
 
7782
7783
  ${import_picocolors3.default.bold("Commands:")}
7783
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
7784
7786
  ${import_picocolors3.default.green("init")} Create a new tool definition
7785
7787
  ${import_picocolors3.default.green("publish")} Publish a tool to the registry
7786
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
7787
7790
  ${import_picocolors3.default.green("remote")} Manage remote servers (add, list, remove)
7788
7791
  ${import_picocolors3.default.green("user")} User operations (get public key)
7789
7792
 
@@ -7792,11 +7795,13 @@ ${import_picocolors3.default.bold("Global Options:")}
7792
7795
  ${import_picocolors3.default.yellow("--version, -v")} Show version information
7793
7796
 
7794
7797
  ${import_picocolors3.default.bold("Examples:")}
7795
- ${import_picocolors3.default.cyan("enact")} ${import_picocolors3.default.dim("# Interactive mode")}
7796
- ${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")}
7797
- ${import_picocolors3.default.cyan("enact")} ${import_picocolors3.default.green("publish")} my-tool.yaml ${import_picocolors3.default.dim("# Publish a tool")}
7798
- ${import_picocolors3.default.cyan("enact")} ${import_picocolors3.default.green("auth")} login ${import_picocolors3.default.dim("# Login with OAuth")}
7799
- ${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")}
7800
7805
 
7801
7806
  ${import_picocolors3.default.bold("More Help:")}
7802
7807
  ${import_picocolors3.default.cyan("enact")} ${import_picocolors3.default.green("<command>")} ${import_picocolors3.default.yellow("--help")} ${import_picocolors3.default.dim("# Show command-specific help")}
@@ -9441,6 +9446,7 @@ Options:
9441
9446
  --limit <number> Maximum number of results (default: 20)
9442
9447
  --tags <tags> Filter by tags (comma-separated)
9443
9448
  --format <format> Output format: table, json, list (default: table)
9449
+ --json Output results as JSON (shorthand for --format json)
9444
9450
  --author <author> Filter by author
9445
9451
 
9446
9452
  Examples:
@@ -9448,6 +9454,7 @@ Examples:
9448
9454
  enact search formatter --tags cli,text
9449
9455
  enact search --author myorg
9450
9456
  enact search prettier --limit 5 --format json
9457
+ enact search prettier --limit 5 --json
9451
9458
  `);
9452
9459
  return;
9453
9460
  }
@@ -9819,6 +9826,1102 @@ ${import_picocolors9.default.bold("EXAMPLES")}
9819
9826
  `);
9820
9827
  }
9821
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
+
9822
10925
  // src/index.ts
9823
10926
  var { values, positionals } = parseArgs({
9824
10927
  args: process.argv,
@@ -9861,9 +10964,38 @@ var { values, positionals } = parseArgs({
9861
10964
  type: "string",
9862
10965
  short: "f"
9863
10966
  },
10967
+ json: {
10968
+ type: "boolean"
10969
+ },
9864
10970
  author: {
9865
10971
  type: "string",
9866
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"
9867
10999
  }
9868
11000
  },
9869
11001
  allowPositionals: true,
@@ -9901,7 +11033,7 @@ async function main() {
9901
11033
  help: values.help,
9902
11034
  limit: values.limit ? parseInt(values.limit) : undefined,
9903
11035
  tags: values.tags ? (values.tags + "").split(",").map((t) => t.trim()) : undefined,
9904
- format: values.format,
11036
+ format: values.json ? "json" : values.format,
9905
11037
  author: values.author
9906
11038
  });
9907
11039
  break;
@@ -9924,17 +11056,39 @@ async function main() {
9924
11056
  format: values.format
9925
11057
  });
9926
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;
9927
11079
  case undefined:
9928
11080
  if (values.help) {
9929
11081
  showHelp();
9930
11082
  } else {
9931
- Ie(import_picocolors10.default.bgCyan(import_picocolors10.default.black(" Enact CLI ")));
11083
+ Ie(import_picocolors12.default.bgCyan(import_picocolors12.default.black(" Enact CLI ")));
9932
11084
  const action = await ve({
9933
11085
  message: "What would you like to do?",
9934
11086
  options: [
9935
11087
  { value: "search", label: "\uD83D\uDD0D Search for tools" },
11088
+ { value: "exec", label: "⚡ Execute a tool" },
9936
11089
  { value: "publish", label: "\uD83D\uDCE4 Publish a tool" },
9937
11090
  { value: "init", label: "\uD83D\uDCDD Create a new tool definition" },
11091
+ { value: "sign", label: "✍️ Sign & verify tools" },
9938
11092
  { value: "auth", label: "\uD83D\uDD10 Manage authentication" },
9939
11093
  { value: "remote", label: "\uD83C\uDF10 Manage remote servers" },
9940
11094
  { value: "user", label: "\uD83D\uDC64 User operations" },
@@ -9954,6 +11108,14 @@ async function main() {
9954
11108
  await handleSearchCommand([], {});
9955
11109
  return;
9956
11110
  }
11111
+ if (action === "exec") {
11112
+ await handleExecCommand([], {});
11113
+ return;
11114
+ }
11115
+ if (action === "sign") {
11116
+ await handleSignCommand([], {});
11117
+ return;
11118
+ }
9957
11119
  if (action === "auth") {
9958
11120
  const authAction = await ve({
9959
11121
  message: "Authentication:",
@@ -10006,12 +11168,12 @@ async function main() {
10006
11168
  }
10007
11169
  break;
10008
11170
  default:
10009
- console.error(import_picocolors10.default.red(`Unknown command: ${command}`));
11171
+ console.error(import_picocolors12.default.red(`Unknown command: ${command}`));
10010
11172
  showHelp();
10011
11173
  process.exit(1);
10012
11174
  }
10013
11175
  } catch (error) {
10014
- console.error(import_picocolors10.default.red(`Error: ${error.message}`));
11176
+ console.error(import_picocolors12.default.red(`Error: ${error.message}`));
10015
11177
  process.exit(1);
10016
11178
  }
10017
11179
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "enact-cli",
3
- "version": "1.0.4",
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",
@@ -13,7 +13,7 @@
13
13
  "LICENSE"
14
14
  ],
15
15
  "scripts": {
16
- "build": "bun build ./src/index.ts --outdir ./dist --target node",
16
+ "build": "bun build ./src/index.ts --outdir ./dist --target node && echo '#!/usr/bin/env node' | cat - ./dist/index.js > temp && mv temp ./dist/index.js && chmod +x ./dist/index.js",
17
17
  "prepublishOnly": "npm run build",
18
18
  "start": "bun ./dist/index.js",
19
19
  "dev": "bun run --watch src/index.ts",