@enactprotocol/cli 2.2.2 → 2.3.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +4 -37
- package/dist/commands/cache/index.js +5 -5
- package/dist/commands/cache/index.js.map +1 -1
- package/dist/commands/index.d.ts +1 -0
- package/dist/commands/index.d.ts.map +1 -1
- package/dist/commands/index.js +2 -0
- package/dist/commands/index.js.map +1 -1
- package/dist/commands/inspect/index.d.ts.map +1 -1
- package/dist/commands/inspect/index.js +3 -2
- package/dist/commands/inspect/index.js.map +1 -1
- package/dist/commands/install/index.d.ts +1 -1
- package/dist/commands/install/index.d.ts.map +1 -1
- package/dist/commands/install/index.js +64 -4
- package/dist/commands/install/index.js.map +1 -1
- package/dist/commands/learn/index.d.ts.map +1 -1
- package/dist/commands/learn/index.js +54 -0
- package/dist/commands/learn/index.js.map +1 -1
- package/dist/commands/list/index.d.ts +1 -1
- package/dist/commands/list/index.d.ts.map +1 -1
- package/dist/commands/list/index.js +7 -3
- package/dist/commands/list/index.js.map +1 -1
- package/dist/commands/run/index.d.ts.map +1 -1
- package/dist/commands/run/index.js +142 -41
- package/dist/commands/run/index.js.map +1 -1
- package/dist/commands/serve/index.d.ts +9 -0
- package/dist/commands/serve/index.d.ts.map +1 -0
- package/dist/commands/serve/index.js +24 -0
- package/dist/commands/serve/index.js.map +1 -0
- package/dist/commands/validate/index.d.ts.map +1 -1
- package/dist/commands/validate/index.js +7 -37
- package/dist/commands/validate/index.js.map +1 -1
- package/dist/index.d.ts +1 -1
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +4 -2
- package/dist/index.js.map +1 -1
- package/dist/utils/errors.js +2 -2
- package/package.json +7 -6
- package/src/commands/cache/index.ts +5 -5
- package/src/commands/env/README.md +1 -1
- package/src/commands/index.ts +3 -0
- package/src/commands/inspect/index.ts +3 -2
- package/src/commands/install/README.md +2 -2
- package/src/commands/install/index.ts +98 -4
- package/src/commands/learn/index.ts +67 -0
- package/src/commands/list/index.ts +12 -3
- package/src/commands/run/README.md +1 -1
- package/src/commands/run/index.ts +195 -48
- package/src/commands/serve/index.ts +26 -0
- package/src/commands/validate/index.ts +7 -42
- package/src/index.ts +5 -1
- package/src/utils/errors.ts +2 -2
- package/tests/commands/cache.test.ts +2 -2
- package/tests/commands/install-integration.test.ts +11 -12
- package/tests/commands/publish.test.ts +12 -2
- package/tests/commands/run.test.ts +3 -1
- package/tests/commands/serve.test.ts +82 -0
- package/tests/commands/sign.test.ts +1 -1
- package/tests/e2e.test.ts +56 -34
- package/tests/fixtures/calculator/skill.yaml +38 -0
- package/tests/fixtures/echo-tool/SKILL.md +3 -10
- package/tests/fixtures/env-tool/{enact.yaml → skill.yaml} +0 -6
- package/tests/fixtures/greeter/skill.yaml +22 -0
- package/tests/utils/ignore.test.ts +3 -1
- package/tsconfig.json +2 -1
- package/tsconfig.tsbuildinfo +1 -1
- package/tests/fixtures/calculator/enact.yaml +0 -34
- package/tests/fixtures/greeter/enact.yaml +0 -18
- /package/tests/fixtures/invalid-tool/{enact.yaml → skill.yaml} +0 -0
|
@@ -5,10 +5,14 @@
|
|
|
5
5
|
* - Default: project tools (via .enact/tools.json)
|
|
6
6
|
* - --global/-g: global tools (via ~/.enact/tools.json)
|
|
7
7
|
*
|
|
8
|
-
* All tools are stored in ~/.
|
|
8
|
+
* All tools are stored in ~/.agent/skills/{tool}/
|
|
9
9
|
*/
|
|
10
10
|
|
|
11
|
-
import {
|
|
11
|
+
import {
|
|
12
|
+
getAliasesForTool,
|
|
13
|
+
listInstalledTools,
|
|
14
|
+
tryLoadManifestFromDir,
|
|
15
|
+
} from "@enactprotocol/shared";
|
|
12
16
|
import type { Command } from "commander";
|
|
13
17
|
import type { CommandContext, GlobalOptions } from "../../types";
|
|
14
18
|
import {
|
|
@@ -33,6 +37,7 @@ interface ToolInfo {
|
|
|
33
37
|
version: string;
|
|
34
38
|
location: string;
|
|
35
39
|
scope: string;
|
|
40
|
+
alias: string;
|
|
36
41
|
[key: string]: string; // Index signature for table compatibility
|
|
37
42
|
}
|
|
38
43
|
|
|
@@ -46,12 +51,15 @@ function listToolsFromRegistry(scope: "global" | "project", cwd?: string): ToolI
|
|
|
46
51
|
for (const tool of installedTools) {
|
|
47
52
|
// Load manifest from cache to get description
|
|
48
53
|
const loaded = tryLoadManifestFromDir(tool.cachePath);
|
|
54
|
+
// Get aliases for this tool
|
|
55
|
+
const aliases = getAliasesForTool(tool.name, scope, cwd);
|
|
49
56
|
tools.push({
|
|
50
57
|
name: tool.name,
|
|
51
58
|
description: loaded?.manifest.description ?? "-",
|
|
52
59
|
version: tool.version,
|
|
53
60
|
location: tool.cachePath,
|
|
54
61
|
scope,
|
|
62
|
+
alias: aliases.length > 0 ? aliases.join(", ") : "-",
|
|
55
63
|
});
|
|
56
64
|
}
|
|
57
65
|
|
|
@@ -96,7 +104,8 @@ async function listHandler(options: ListOptions, ctx: CommandContext): Promise<v
|
|
|
96
104
|
|
|
97
105
|
const columns: TableColumn[] = [
|
|
98
106
|
{ key: "name", header: "Name", width: 28 },
|
|
99
|
-
{ key: "
|
|
107
|
+
{ key: "alias", header: "Alias", width: 12 },
|
|
108
|
+
{ key: "description", header: "Description", width: 40 },
|
|
100
109
|
];
|
|
101
110
|
|
|
102
111
|
if (options.verbose) {
|
|
@@ -10,7 +10,7 @@ enact run <tool> [options]
|
|
|
10
10
|
|
|
11
11
|
## Description
|
|
12
12
|
|
|
13
|
-
The `run` command executes a tool using the command defined in its manifest (`
|
|
13
|
+
The `run` command executes a tool using the command defined in its manifest (`skill.yaml` or `SKILL.md`). The tool runs in an isolated container environment with:
|
|
14
14
|
|
|
15
15
|
- Input validation against the tool's JSON Schema
|
|
16
16
|
- Automatic secret resolution from the OS keyring
|
|
@@ -33,17 +33,28 @@ import {
|
|
|
33
33
|
getToolVersion,
|
|
34
34
|
verifyAllAttestations,
|
|
35
35
|
} from "@enactprotocol/api";
|
|
36
|
-
import {
|
|
36
|
+
import {
|
|
37
|
+
DaggerExecutionProvider,
|
|
38
|
+
DockerExecutionProvider,
|
|
39
|
+
type ExecutionResult,
|
|
40
|
+
ExecutionRouter,
|
|
41
|
+
LocalExecutionProvider,
|
|
42
|
+
} from "@enactprotocol/execution";
|
|
37
43
|
import { resolveSecrets, resolveToolEnv } from "@enactprotocol/secrets";
|
|
38
44
|
import {
|
|
45
|
+
type Action,
|
|
46
|
+
type ActionsManifest,
|
|
39
47
|
type ToolManifest,
|
|
40
48
|
type ToolResolution,
|
|
41
49
|
applyDefaults,
|
|
42
50
|
getCacheDir,
|
|
51
|
+
getEffectiveInputSchema,
|
|
43
52
|
getMinimumAttestations,
|
|
44
53
|
getTrustPolicy,
|
|
45
54
|
getTrustedAuditors,
|
|
46
55
|
loadConfig,
|
|
56
|
+
parseActionSpecifier,
|
|
57
|
+
prepareActionCommand,
|
|
47
58
|
prepareCommand,
|
|
48
59
|
toolNameToPath,
|
|
49
60
|
tryResolveTool,
|
|
@@ -85,6 +96,7 @@ interface RunOptions extends GlobalOptions {
|
|
|
85
96
|
output?: string;
|
|
86
97
|
apply?: boolean;
|
|
87
98
|
debug?: boolean;
|
|
99
|
+
action?: string;
|
|
88
100
|
}
|
|
89
101
|
|
|
90
102
|
/**
|
|
@@ -635,7 +647,6 @@ function displayDryRun(
|
|
|
635
647
|
* Display debug information about parameter resolution
|
|
636
648
|
*/
|
|
637
649
|
function displayDebugInfo(
|
|
638
|
-
manifest: ToolManifest,
|
|
639
650
|
rawInputs: Record<string, unknown>,
|
|
640
651
|
inputsWithDefaults: Record<string, unknown>,
|
|
641
652
|
finalInputs: Record<string, unknown>,
|
|
@@ -646,21 +657,7 @@ function displayDebugInfo(
|
|
|
646
657
|
info(colors.bold("Debug: Parameter Resolution"));
|
|
647
658
|
newline();
|
|
648
659
|
|
|
649
|
-
//
|
|
650
|
-
if (manifest.inputSchema?.properties) {
|
|
651
|
-
info("Schema Properties:");
|
|
652
|
-
const required = new Set(manifest.inputSchema.required || []);
|
|
653
|
-
for (const [name, prop] of Object.entries(manifest.inputSchema.properties)) {
|
|
654
|
-
const propSchema = prop as { type?: string; default?: unknown; description?: string };
|
|
655
|
-
const isRequired = required.has(name);
|
|
656
|
-
const hasDefault = propSchema.default !== undefined;
|
|
657
|
-
const status = isRequired ? colors.error("required") : colors.dim("optional");
|
|
658
|
-
dim(
|
|
659
|
-
` ${name}: ${propSchema.type || "any"} [${status}]${hasDefault ? ` (default: ${JSON.stringify(propSchema.default)})` : ""}`
|
|
660
|
-
);
|
|
661
|
-
}
|
|
662
|
-
newline();
|
|
663
|
-
}
|
|
660
|
+
// Schema information is available per-script via action.inputSchema
|
|
664
661
|
|
|
665
662
|
// Show raw inputs (what was provided)
|
|
666
663
|
info("Raw Inputs (provided by user):");
|
|
@@ -768,6 +765,48 @@ function displayResult(result: ExecutionResult, options: RunOptions): void {
|
|
|
768
765
|
}
|
|
769
766
|
}
|
|
770
767
|
|
|
768
|
+
/**
|
|
769
|
+
* Check if a specifier looks like a file path
|
|
770
|
+
*/
|
|
771
|
+
function isFilePathSpecifier(specifier: string): boolean {
|
|
772
|
+
return (
|
|
773
|
+
specifier.startsWith("/") ||
|
|
774
|
+
specifier.startsWith("./") ||
|
|
775
|
+
specifier.startsWith("../") ||
|
|
776
|
+
specifier === "."
|
|
777
|
+
);
|
|
778
|
+
}
|
|
779
|
+
|
|
780
|
+
/**
|
|
781
|
+
* For file paths like /tmp/skill/action, try to parse the last segment as an action
|
|
782
|
+
* Returns { parentPath, actionName } or null if not applicable
|
|
783
|
+
*/
|
|
784
|
+
function tryParseFilePathAction(
|
|
785
|
+
specifier: string
|
|
786
|
+
): { parentPath: string; actionName: string } | null {
|
|
787
|
+
if (!isFilePathSpecifier(specifier)) {
|
|
788
|
+
return null;
|
|
789
|
+
}
|
|
790
|
+
|
|
791
|
+
const normalized = specifier.replace(/\\/g, "/");
|
|
792
|
+
const lastSlash = normalized.lastIndexOf("/");
|
|
793
|
+
|
|
794
|
+
// Need at least one slash and something after it
|
|
795
|
+
if (lastSlash <= 0 || lastSlash === normalized.length - 1) {
|
|
796
|
+
return null;
|
|
797
|
+
}
|
|
798
|
+
|
|
799
|
+
const parentPath = normalized.slice(0, lastSlash);
|
|
800
|
+
const actionName = normalized.slice(lastSlash + 1);
|
|
801
|
+
|
|
802
|
+
// Action name shouldn't look like a file extension
|
|
803
|
+
if (actionName.includes(".")) {
|
|
804
|
+
return null;
|
|
805
|
+
}
|
|
806
|
+
|
|
807
|
+
return { parentPath, actionName };
|
|
808
|
+
}
|
|
809
|
+
|
|
771
810
|
/**
|
|
772
811
|
* Run command handler
|
|
773
812
|
*/
|
|
@@ -775,8 +814,16 @@ async function runHandler(tool: string, options: RunOptions, ctx: CommandContext
|
|
|
775
814
|
let resolution: ToolResolution | null = null;
|
|
776
815
|
let resolveResult: ReturnType<typeof tryResolveToolDetailed> | null = null;
|
|
777
816
|
|
|
817
|
+
// Parse the tool specifier to check for action (owner/skill/action format)
|
|
818
|
+
const { skillName, actionName: parsedActionName } = parseActionSpecifier(tool);
|
|
819
|
+
|
|
820
|
+
// Use --action flag if provided, otherwise use parsed action name
|
|
821
|
+
let actionName = options.action ?? parsedActionName;
|
|
822
|
+
let hasActionSpecifier = actionName !== undefined;
|
|
823
|
+
|
|
778
824
|
// Check if --remote flag is valid (requires namespace/name format)
|
|
779
|
-
const isRegistryFormat =
|
|
825
|
+
const isRegistryFormat =
|
|
826
|
+
skillName.includes("/") && !skillName.startsWith("/") && !skillName.startsWith(".");
|
|
780
827
|
if (options.remote && !isRegistryFormat) {
|
|
781
828
|
throw new ValidationError(
|
|
782
829
|
`--remote requires a registry tool name (e.g., user/tool), got: ${tool}`
|
|
@@ -786,21 +833,44 @@ async function runHandler(tool: string, options: RunOptions, ctx: CommandContext
|
|
|
786
833
|
// Skip local resolution if --remote is set
|
|
787
834
|
if (!options.remote) {
|
|
788
835
|
// First, try to resolve locally (project → user → cache)
|
|
836
|
+
// Use the skill name (without action) for initial resolution
|
|
789
837
|
if (!options.verbose) {
|
|
790
|
-
resolveResult = tryResolveToolDetailed(
|
|
838
|
+
resolveResult = tryResolveToolDetailed(skillName, { startDir: ctx.cwd });
|
|
791
839
|
resolution = resolveResult.resolution;
|
|
792
840
|
} else {
|
|
793
841
|
const spinner = clack.spinner();
|
|
794
|
-
spinner.start(
|
|
795
|
-
|
|
842
|
+
spinner.start(
|
|
843
|
+
`Resolving tool: ${skillName}${hasActionSpecifier ? ` (action: ${actionName})` : ""}`
|
|
844
|
+
);
|
|
845
|
+
resolveResult = tryResolveToolDetailed(skillName, { startDir: ctx.cwd });
|
|
796
846
|
resolution = resolveResult.resolution;
|
|
797
847
|
if (resolution) {
|
|
798
|
-
spinner.stop(`${symbols.success} Resolved: ${
|
|
848
|
+
spinner.stop(`${symbols.success} Resolved: ${skillName}`);
|
|
799
849
|
} else {
|
|
800
850
|
spinner.stop(`${symbols.info} Checking registry...`);
|
|
801
851
|
}
|
|
802
852
|
}
|
|
803
853
|
|
|
854
|
+
// If resolution failed and this is a file path, try treating the last segment as an action
|
|
855
|
+
// e.g., /tmp/skill/hello -> try /tmp/skill with action "hello"
|
|
856
|
+
if (!resolution && isFilePathSpecifier(tool) && !options.action) {
|
|
857
|
+
const parsed = tryParseFilePathAction(tool);
|
|
858
|
+
if (parsed) {
|
|
859
|
+
const parentResult = tryResolveToolDetailed(parsed.parentPath, { startDir: ctx.cwd });
|
|
860
|
+
if (parentResult.resolution?.actionsManifest) {
|
|
861
|
+
// Found a skill with actions at the parent path
|
|
862
|
+
resolution = parentResult.resolution;
|
|
863
|
+
resolveResult = parentResult;
|
|
864
|
+
actionName = parsed.actionName;
|
|
865
|
+
hasActionSpecifier = true;
|
|
866
|
+
|
|
867
|
+
if (options.verbose) {
|
|
868
|
+
info(`Detected action from path: ${parsed.actionName}`);
|
|
869
|
+
}
|
|
870
|
+
}
|
|
871
|
+
}
|
|
872
|
+
}
|
|
873
|
+
|
|
804
874
|
// If manifest was found but had errors, throw a descriptive error immediately
|
|
805
875
|
if (!resolution && resolveResult?.manifestFound && resolveResult?.error) {
|
|
806
876
|
const errorMessage = resolveResult.error.message;
|
|
@@ -816,31 +886,61 @@ async function runHandler(tool: string, options: RunOptions, ctx: CommandContext
|
|
|
816
886
|
// Check if this looks like a tool name (namespace/name format)
|
|
817
887
|
if (isRegistryFormat) {
|
|
818
888
|
resolution = !options.verbose
|
|
819
|
-
? await fetchAndCacheTool(
|
|
889
|
+
? await fetchAndCacheTool(skillName, options, ctx)
|
|
820
890
|
: await withSpinner(
|
|
821
|
-
`Fetching ${
|
|
822
|
-
async () => fetchAndCacheTool(
|
|
823
|
-
`${symbols.success} Cached: ${
|
|
891
|
+
`Fetching ${skillName} from registry...`,
|
|
892
|
+
async () => fetchAndCacheTool(skillName, options, ctx),
|
|
893
|
+
`${symbols.success} Cached: ${skillName}`
|
|
824
894
|
);
|
|
825
895
|
}
|
|
826
896
|
}
|
|
827
897
|
|
|
828
898
|
if (!resolution) {
|
|
829
899
|
if (options.local) {
|
|
830
|
-
throw new ToolNotFoundError(
|
|
900
|
+
throw new ToolNotFoundError(skillName, {
|
|
831
901
|
localOnly: true,
|
|
832
902
|
...(resolveResult?.searchedLocations && {
|
|
833
903
|
searchedLocations: resolveResult.searchedLocations,
|
|
834
904
|
}),
|
|
835
905
|
});
|
|
836
906
|
}
|
|
837
|
-
throw new ToolNotFoundError(
|
|
907
|
+
throw new ToolNotFoundError(skillName, {
|
|
838
908
|
...(resolveResult?.searchedLocations && {
|
|
839
909
|
searchedLocations: resolveResult.searchedLocations,
|
|
840
910
|
}),
|
|
841
911
|
});
|
|
842
912
|
}
|
|
843
913
|
|
|
914
|
+
// If a script was specified via colon syntax, resolve it from the manifest's scripts
|
|
915
|
+
let resolvedAction: Action | undefined;
|
|
916
|
+
let actionsManifest: ActionsManifest | undefined;
|
|
917
|
+
|
|
918
|
+
// Use resolved skill identifier for error messages (manifest name or source directory)
|
|
919
|
+
const resolvedSkillId = resolution.manifest.name ?? resolution.sourceDir;
|
|
920
|
+
|
|
921
|
+
if (hasActionSpecifier) {
|
|
922
|
+
if (!resolution.actionsManifest) {
|
|
923
|
+
throw new ValidationError(
|
|
924
|
+
`Skill "${resolvedSkillId}" does not define any scripts. Cannot execute script "${actionName}".`
|
|
925
|
+
);
|
|
926
|
+
}
|
|
927
|
+
|
|
928
|
+
actionsManifest = resolution.actionsManifest;
|
|
929
|
+
// Map lookup - action name is the key
|
|
930
|
+
resolvedAction = actionsManifest.actions[actionName!];
|
|
931
|
+
|
|
932
|
+
if (!resolvedAction) {
|
|
933
|
+
const availableActions = Object.keys(actionsManifest.actions).join(", ");
|
|
934
|
+
throw new ValidationError(
|
|
935
|
+
`Action "${actionName}" not found in skill "${resolvedSkillId}". Available actions: ${availableActions}`
|
|
936
|
+
);
|
|
937
|
+
}
|
|
938
|
+
|
|
939
|
+
if (options.verbose) {
|
|
940
|
+
info(`Using action: ${actionName}`);
|
|
941
|
+
}
|
|
942
|
+
}
|
|
943
|
+
|
|
844
944
|
const manifest = resolution.manifest;
|
|
845
945
|
|
|
846
946
|
// Parse --input flags to separate key=value params from path inputs
|
|
@@ -852,13 +952,16 @@ async function runHandler(tool: string, options: RunOptions, ctx: CommandContext
|
|
|
852
952
|
// Merge inputs: path params override other inputs
|
|
853
953
|
const inputs = { ...otherInputs, ...pathParams };
|
|
854
954
|
|
|
955
|
+
// Use action's inputSchema if executing an action, otherwise no schema
|
|
956
|
+
const effectiveInputSchema = resolvedAction ? getEffectiveInputSchema(resolvedAction) : undefined;
|
|
957
|
+
|
|
855
958
|
// Apply defaults from schema
|
|
856
|
-
const inputsWithDefaults =
|
|
857
|
-
? applyDefaults(inputs,
|
|
959
|
+
const inputsWithDefaults = effectiveInputSchema
|
|
960
|
+
? applyDefaults(inputs, effectiveInputSchema)
|
|
858
961
|
: inputs;
|
|
859
962
|
|
|
860
963
|
// Validate inputs against schema
|
|
861
|
-
const validation = validateInputs(inputsWithDefaults,
|
|
964
|
+
const validation = validateInputs(inputsWithDefaults, effectiveInputSchema);
|
|
862
965
|
if (!validation.valid) {
|
|
863
966
|
const errors = validation.errors.map((err) => `${err.path}: ${err.message}`).join(", ");
|
|
864
967
|
throw new ValidationError(`Input validation failed: ${errors}`);
|
|
@@ -905,8 +1008,8 @@ async function runHandler(tool: string, options: RunOptions, ctx: CommandContext
|
|
|
905
1008
|
}
|
|
906
1009
|
}
|
|
907
1010
|
|
|
908
|
-
// Check if this is an instruction-based tool (no command)
|
|
909
|
-
if (!manifest.command) {
|
|
1011
|
+
// Check if this is an instruction-based tool (no command) - but actions always have commands
|
|
1012
|
+
if (!manifest.command && !resolvedAction) {
|
|
910
1013
|
// For instruction tools, just display the markdown body
|
|
911
1014
|
let instructions: string | undefined;
|
|
912
1015
|
|
|
@@ -947,15 +1050,25 @@ async function runHandler(tool: string, options: RunOptions, ctx: CommandContext
|
|
|
947
1050
|
return;
|
|
948
1051
|
}
|
|
949
1052
|
|
|
950
|
-
// Prepare command
|
|
951
|
-
|
|
952
|
-
|
|
953
|
-
|
|
954
|
-
|
|
955
|
-
|
|
956
|
-
|
|
957
|
-
|
|
958
|
-
|
|
1053
|
+
// Prepare command
|
|
1054
|
+
// For actions: use {{param}} template system (array form, no shell)
|
|
1055
|
+
// For regular tools: use ${param} template system (shell interpolation)
|
|
1056
|
+
let command: string[];
|
|
1057
|
+
|
|
1058
|
+
if (resolvedAction) {
|
|
1059
|
+
// Action execution: use prepareActionCommand for {{param}} templates
|
|
1060
|
+
const actionCommand = resolvedAction.command;
|
|
1061
|
+
if (typeof actionCommand === "string") {
|
|
1062
|
+
// String form (no templates allowed by validation)
|
|
1063
|
+
command = actionCommand.split(/\s+/).filter((s) => s.length > 0);
|
|
1064
|
+
} else {
|
|
1065
|
+
// Array form with {{param}} templates
|
|
1066
|
+
command = prepareActionCommand(actionCommand, finalInputs, resolvedAction.inputSchema);
|
|
1067
|
+
}
|
|
1068
|
+
} else {
|
|
1069
|
+
// Regular tool execution: use prepareCommand for ${param} templates
|
|
1070
|
+
command = prepareCommand(manifest.command!, finalInputs);
|
|
1071
|
+
}
|
|
959
1072
|
|
|
960
1073
|
// Resolve environment variables (non-secrets)
|
|
961
1074
|
const { resolved: envResolved } = resolveToolEnv(manifest.env ?? {}, ctx.cwd);
|
|
@@ -1016,7 +1129,7 @@ async function runHandler(tool: string, options: RunOptions, ctx: CommandContext
|
|
|
1016
1129
|
|
|
1017
1130
|
// Debug mode - show detailed parameter resolution info
|
|
1018
1131
|
if (options.debug) {
|
|
1019
|
-
displayDebugInfo(
|
|
1132
|
+
displayDebugInfo(inputs, inputsWithDefaults, finalInputs, envVars, command);
|
|
1020
1133
|
}
|
|
1021
1134
|
|
|
1022
1135
|
// Dry run mode
|
|
@@ -1033,7 +1146,7 @@ async function runHandler(tool: string, options: RunOptions, ctx: CommandContext
|
|
|
1033
1146
|
return;
|
|
1034
1147
|
}
|
|
1035
1148
|
|
|
1036
|
-
// Execute the tool
|
|
1149
|
+
// Execute the tool — select backend via execution router
|
|
1037
1150
|
const providerConfig: { defaultTimeout?: number; verbose?: boolean } = {};
|
|
1038
1151
|
if (options.timeout) {
|
|
1039
1152
|
providerConfig.defaultTimeout = parseTimeout(options.timeout);
|
|
@@ -1042,7 +1155,20 @@ async function runHandler(tool: string, options: RunOptions, ctx: CommandContext
|
|
|
1042
1155
|
providerConfig.verbose = true;
|
|
1043
1156
|
}
|
|
1044
1157
|
|
|
1045
|
-
const
|
|
1158
|
+
const config = loadConfig();
|
|
1159
|
+
const router = new ExecutionRouter({
|
|
1160
|
+
default: config.execution?.default,
|
|
1161
|
+
fallback: config.execution?.fallback,
|
|
1162
|
+
trusted_scopes: config.execution?.trusted_scopes,
|
|
1163
|
+
});
|
|
1164
|
+
router.registerProvider("local", new LocalExecutionProvider(providerConfig));
|
|
1165
|
+
router.registerProvider("docker", new DockerExecutionProvider(providerConfig));
|
|
1166
|
+
router.registerProvider("dagger", new DaggerExecutionProvider(providerConfig));
|
|
1167
|
+
|
|
1168
|
+
const provider = await router.selectProvider(manifest.name, {
|
|
1169
|
+
forceLocal: options.local,
|
|
1170
|
+
forceRemote: options.remote,
|
|
1171
|
+
});
|
|
1046
1172
|
|
|
1047
1173
|
// For --apply, we export to a temp directory first, then atomically replace
|
|
1048
1174
|
let tempOutputDir: string | undefined;
|
|
@@ -1070,6 +1196,21 @@ async function runHandler(tool: string, options: RunOptions, ctx: CommandContext
|
|
|
1070
1196
|
execOptions.outputPath = resolve(options.output);
|
|
1071
1197
|
}
|
|
1072
1198
|
|
|
1199
|
+
// Use executeAction for actions, execute for regular tools
|
|
1200
|
+
if (resolvedAction && actionsManifest && actionName) {
|
|
1201
|
+
return provider.executeAction(
|
|
1202
|
+
manifest,
|
|
1203
|
+
actionsManifest,
|
|
1204
|
+
actionName,
|
|
1205
|
+
resolvedAction,
|
|
1206
|
+
{
|
|
1207
|
+
params: finalInputs,
|
|
1208
|
+
envOverrides: envVars,
|
|
1209
|
+
},
|
|
1210
|
+
execOptions
|
|
1211
|
+
);
|
|
1212
|
+
}
|
|
1213
|
+
|
|
1073
1214
|
return provider.execute(
|
|
1074
1215
|
manifest,
|
|
1075
1216
|
{
|
|
@@ -1082,7 +1223,9 @@ async function runHandler(tool: string, options: RunOptions, ctx: CommandContext
|
|
|
1082
1223
|
|
|
1083
1224
|
// Build a descriptive message - container may need to be pulled
|
|
1084
1225
|
const containerImage = manifest.from ?? "node:18-alpine";
|
|
1085
|
-
const
|
|
1226
|
+
const toolDisplayName =
|
|
1227
|
+
resolvedAction && actionName ? `${manifest.name}:${actionName}` : manifest.name;
|
|
1228
|
+
const spinnerMessage = `Running ${toolDisplayName} (${containerImage})...`;
|
|
1086
1229
|
|
|
1087
1230
|
const result = !options.verbose
|
|
1088
1231
|
? await executeTask()
|
|
@@ -1153,8 +1296,11 @@ function parseTimeout(timeout: string): number {
|
|
|
1153
1296
|
export function configureRunCommand(program: Command): void {
|
|
1154
1297
|
program
|
|
1155
1298
|
.command("run")
|
|
1156
|
-
.description("Execute a tool with its manifest-defined command")
|
|
1157
|
-
.argument(
|
|
1299
|
+
.description("Execute a tool or action with its manifest-defined command")
|
|
1300
|
+
.argument(
|
|
1301
|
+
"<tool>",
|
|
1302
|
+
"Tool or action to run (name, owner/skill/action, path, or '.' for current directory)"
|
|
1303
|
+
)
|
|
1158
1304
|
.option("-a, --args <json>", "Input arguments as JSON string (recommended)")
|
|
1159
1305
|
.option("-f, --input-file <path>", "Load input arguments from JSON file")
|
|
1160
1306
|
.option(
|
|
@@ -1172,6 +1318,7 @@ export function configureRunCommand(program: Command): void {
|
|
|
1172
1318
|
.option("-r, --remote", "Skip local resolution and fetch from registry")
|
|
1173
1319
|
.option("--dry-run", "Show what would be executed without running")
|
|
1174
1320
|
.option("--debug", "Show detailed parameter and environment variable resolution")
|
|
1321
|
+
.option("--action <name>", "Script to execute (alternative to colon syntax: tool:script)")
|
|
1175
1322
|
.option("-v, --verbose", "Show progress spinners and detailed output")
|
|
1176
1323
|
.option("--json", "Output result as JSON")
|
|
1177
1324
|
.action(async (tool: string, options: RunOptions) => {
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* enact serve command
|
|
3
|
+
*
|
|
4
|
+
* Start a self-hosted Enact registry server.
|
|
5
|
+
* Uses SQLite + local file storage — no external dependencies.
|
|
6
|
+
*/
|
|
7
|
+
|
|
8
|
+
import { resolve } from "node:path";
|
|
9
|
+
import type { Command } from "commander";
|
|
10
|
+
|
|
11
|
+
export function configureServeCommand(program: Command): void {
|
|
12
|
+
program
|
|
13
|
+
.command("serve")
|
|
14
|
+
.description("Start a self-hosted Enact registry server")
|
|
15
|
+
.option("-p, --port <port>", "Port to listen on", "3000")
|
|
16
|
+
.option("-d, --data <path>", "Data directory for storage", "./registry-data")
|
|
17
|
+
.option("--host <host>", "Host to bind to", "0.0.0.0")
|
|
18
|
+
.action(async (options: { port: string; data: string; host: string }) => {
|
|
19
|
+
const { startServer } = await import("@enactprotocol/registry");
|
|
20
|
+
await startServer({
|
|
21
|
+
port: Number.parseInt(options.port, 10),
|
|
22
|
+
dataDir: resolve(options.data),
|
|
23
|
+
host: options.host,
|
|
24
|
+
});
|
|
25
|
+
});
|
|
26
|
+
}
|
|
@@ -102,49 +102,14 @@ function validateManifest(manifest: ToolManifest, sourceDir: string): Validation
|
|
|
102
102
|
message: "No 'command' field - this is an LLM instruction tool",
|
|
103
103
|
});
|
|
104
104
|
} else {
|
|
105
|
-
// Command-based tool -
|
|
105
|
+
// Command-based tool - basic validation
|
|
106
106
|
const commandParams = extractCommandParams(manifest.command);
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
for (const param of commandParams) {
|
|
114
|
-
if (!schemaProperties.includes(param)) {
|
|
115
|
-
issues.push({
|
|
116
|
-
level: "error",
|
|
117
|
-
message: `Command uses \${${param}} but it's not defined in inputSchema.properties`,
|
|
118
|
-
suggestion: `Add '${param}' to inputSchema.properties`,
|
|
119
|
-
});
|
|
120
|
-
}
|
|
121
|
-
}
|
|
122
|
-
|
|
123
|
-
// Check for required params without command usage (potential issue)
|
|
124
|
-
for (const param of requiredParams) {
|
|
125
|
-
if (!commandParams.includes(param)) {
|
|
126
|
-
issues.push({
|
|
127
|
-
level: "info",
|
|
128
|
-
message: `Required parameter '${param}' is not used in command template`,
|
|
129
|
-
suggestion: "This is fine if you access it via environment or files",
|
|
130
|
-
});
|
|
131
|
-
}
|
|
132
|
-
}
|
|
133
|
-
|
|
134
|
-
// Check for optional params without defaults
|
|
135
|
-
for (const prop of schemaProperties) {
|
|
136
|
-
if (!requiredParams.includes(prop)) {
|
|
137
|
-
const propSchema = manifest.inputSchema?.properties?.[prop] as
|
|
138
|
-
| { default?: unknown }
|
|
139
|
-
| undefined;
|
|
140
|
-
if (propSchema?.default === undefined) {
|
|
141
|
-
issues.push({
|
|
142
|
-
level: "warning",
|
|
143
|
-
message: `Optional parameter '${prop}' has no default value`,
|
|
144
|
-
suggestion: "Add a default value or it will be empty string in commands",
|
|
145
|
-
});
|
|
146
|
-
}
|
|
147
|
-
}
|
|
107
|
+
if (commandParams.length > 0) {
|
|
108
|
+
issues.push({
|
|
109
|
+
level: "info",
|
|
110
|
+
message: `Command uses parameters: ${commandParams.join(", ")}`,
|
|
111
|
+
suggestion: "Consider using scripts with {{param}} templates for better parameter handling",
|
|
112
|
+
});
|
|
148
113
|
}
|
|
149
114
|
|
|
150
115
|
// Check for double-quoting in command
|
package/src/index.ts
CHANGED
|
@@ -26,6 +26,7 @@ import {
|
|
|
26
26
|
configureReportCommand,
|
|
27
27
|
configureRunCommand,
|
|
28
28
|
configureSearchCommand,
|
|
29
|
+
configureServeCommand,
|
|
29
30
|
configureSetupCommand,
|
|
30
31
|
configureSignCommand,
|
|
31
32
|
configureTrustCommand,
|
|
@@ -36,7 +37,7 @@ import {
|
|
|
36
37
|
} from "./commands";
|
|
37
38
|
import { error, formatError } from "./utils";
|
|
38
39
|
|
|
39
|
-
export const version = "2.
|
|
40
|
+
export const version = "2.3.1";
|
|
40
41
|
|
|
41
42
|
// Export types for external use
|
|
42
43
|
export type { GlobalOptions, CommandContext } from "./types";
|
|
@@ -89,6 +90,9 @@ async function main() {
|
|
|
89
90
|
// Validation command
|
|
90
91
|
configureValidateCommand(program);
|
|
91
92
|
|
|
93
|
+
// Self-hosted registry
|
|
94
|
+
configureServeCommand(program);
|
|
95
|
+
|
|
92
96
|
// Global error handler - handle Commander's help/version exits gracefully
|
|
93
97
|
program.exitOverride((err) => {
|
|
94
98
|
// Commander throws errors for help, version, and other "exit" scenarios
|
package/src/utils/errors.ts
CHANGED
|
@@ -84,7 +84,7 @@ export class ManifestError extends CliError {
|
|
|
84
84
|
super(
|
|
85
85
|
fullMessage,
|
|
86
86
|
EXIT_MANIFEST_ERROR,
|
|
87
|
-
"Ensure the directory contains a valid
|
|
87
|
+
"Ensure the directory contains a valid skill.yaml or SKILL.md file."
|
|
88
88
|
);
|
|
89
89
|
this.name = "ManifestError";
|
|
90
90
|
}
|
|
@@ -377,7 +377,7 @@ export const ErrorMessages = {
|
|
|
377
377
|
message: `No manifest found in ${dir}`,
|
|
378
378
|
suggestions: [
|
|
379
379
|
`Create a manifest: ${colors.command("enact init")}`,
|
|
380
|
-
"Ensure the directory contains
|
|
380
|
+
"Ensure the directory contains skill.yaml or SKILL.md",
|
|
381
381
|
],
|
|
382
382
|
}),
|
|
383
383
|
|
|
@@ -271,14 +271,14 @@ describe("cache command", () => {
|
|
|
271
271
|
}
|
|
272
272
|
|
|
273
273
|
const info: CacheInfo = {
|
|
274
|
-
path: "/Users/test/.
|
|
274
|
+
path: "/Users/test/.agent/skills",
|
|
275
275
|
totalSize: 10_485_760, // 10 MB
|
|
276
276
|
toolCount: 25,
|
|
277
277
|
oldestEntry: "2024-01-01T00:00:00Z",
|
|
278
278
|
newestEntry: "2024-01-20T12:00:00Z",
|
|
279
279
|
};
|
|
280
280
|
|
|
281
|
-
expect(info.path).toContain(".
|
|
281
|
+
expect(info.path).toContain(".agent");
|
|
282
282
|
expect(info.totalSize).toBeGreaterThan(0);
|
|
283
283
|
expect(info.toolCount).toBeGreaterThanOrEqual(0);
|
|
284
284
|
});
|