fix(cli): preserve bootstrap for version-pinned skill commands (#114924)

This commit is contained in:
Peter Steinberger
2026-07-28 00:34:20 -04:00
committed by GitHub
parent 61bd5af77e
commit 929a8cf641
5 changed files with 211 additions and 2 deletions
+41
View File
@@ -25,6 +25,47 @@ describe("argv-invocation", () => {
});
});
it.each([
{
name: "version-pinned install",
argv: ["node", "openclaw", "skills", "install", "@owner/weather", "--version", "1.2.3"],
commandPath: ["skills", "install"],
},
{
name: "version-pinned verification",
argv: ["node", "openclaw", "skills", "verify", "@owner/weather", "--version", "1.2.3"],
commandPath: ["skills", "verify"],
},
{
name: "equals-form version-pinned install",
argv: ["node", "openclaw", "skills", "install", "@owner/weather", "--version=1.2.3"],
commandPath: ["skills", "install"],
},
{
name: "profiled version-pinned verification",
argv: [
"node",
"openclaw",
"--profile",
"work",
"skills",
"verify",
"@owner/weather",
"--version",
"1.2.3",
],
commandPath: ["skills", "verify"],
},
])("keeps $name in command execution mode", ({ argv, commandPath }) => {
expect(resolveCliArgvInvocation(argv)).toEqual({
argv,
commandPath,
primary: "skills",
hasHelpOrVersion: false,
isRootHelpInvocation: false,
});
});
it("consumes agent parent option values before the exec subcommand", () => {
expect(
resolveCliArgvInvocation([
+69
View File
@@ -293,6 +293,75 @@ describe("argv helpers", () => {
argv: ["node", "openclaw", "nodes", "invoke", "--", "--version"],
expected: false,
},
{
name: "root version flag",
argv: ["node", "openclaw", "--version"],
expected: true,
},
{
name: "root short version flag",
argv: ["node", "openclaw", "-V"],
expected: true,
},
{
name: "root version alias after profile",
argv: ["node", "openclaw", "--profile", "work", "-v"],
expected: true,
},
{
name: "root version flag after profile",
argv: ["node", "openclaw", "--profile", "work", "--version"],
expected: true,
},
{
name: "version-pinned skill install",
argv: ["node", "openclaw", "skills", "install", "@owner/weather", "--version", "1.2.3"],
expected: false,
},
{
name: "version-pinned skill verification",
argv: ["node", "openclaw", "skills", "verify", "@owner/weather", "--version", "1.2.3"],
expected: false,
},
{
name: "equals-form version-pinned skill install",
argv: ["node", "openclaw", "skills", "install", "@owner/weather", "--version=1.2.3"],
expected: false,
},
{
name: "profiled version-pinned skill verification",
argv: [
"node",
"openclaw",
"--profile",
"work",
"skills",
"verify",
"@owner/weather",
"--version",
"1.2.3",
],
expected: false,
},
{
name: "help for a version-pinned skill command",
argv: [
"node",
"openclaw",
"skills",
"verify",
"@owner/weather",
"--version",
"1.2.3",
"--help",
],
expected: true,
},
{
name: "unknown root option does not turn version into root help",
argv: ["node", "openclaw", "--unknown", "--version"],
expected: false,
},
])("detects help/version invocations: $name", ({ argv, expected }) => {
expect(isHelpOrVersionInvocation(argv)).toBe(expected);
});
+2 -2
View File
@@ -30,7 +30,7 @@ const ROOT_COMMANDS_WITH_SUBCOMMANDS: ReadonlySet<string> = new Set(
);
export function isHelpOrVersionInvocation(argv: string[]): boolean {
if (hasRootVersionAlias(argv)) {
if (isRootVersionInvocation(argv)) {
return true;
}
@@ -47,7 +47,7 @@ export function isHelpOrVersionInvocation(argv: string[]): boolean {
i += rootConsumed - 1;
continue;
}
if (HELP_FLAGS.has(arg) || VERSION_FLAGS.has(arg)) {
if (HELP_FLAGS.has(arg)) {
return true;
}
if (arg.startsWith("-")) {
+53
View File
@@ -201,6 +201,14 @@ describe("registerPreActionHooks", () => {
.action(() => {});
programLocal.command("completion").action(() => {});
programLocal.command("secrets").action(() => {});
const skills = programLocal.command("skills");
for (const skillCommand of ["install", "verify"]) {
skills
.command(skillCommand)
.argument("<skill-ref>")
.option("--version <version>")
.action(() => {});
}
programLocal
.command("qa")
.command("suite")
@@ -655,6 +663,51 @@ describe("registerPreActionHooks", () => {
expect(ensureConfigReadyMock).toHaveBeenCalledTimes(1);
});
it.each([
{
name: "version-pinned skill install",
action: "install",
argv: ["node", "openclaw", "skills", "install", "@owner/weather", "--version", "1.2.3"],
},
{
name: "version-pinned skill verification",
action: "verify",
argv: ["node", "openclaw", "skills", "verify", "@owner/weather", "--version", "1.2.3"],
},
{
name: "equals-form version-pinned skill install",
action: "install",
argv: ["node", "openclaw", "skills", "install", "@owner/weather", "--version=1.2.3"],
},
{
name: "profiled version-pinned skill verification",
action: "verify",
argv: [
"node",
"openclaw",
"--profile",
"work",
"skills",
"verify",
"@owner/weather",
"--version",
"1.2.3",
],
},
])("runs the execution bootstrap for $name", async ({ action, argv }) => {
await runPreAction({
parseArgv: ["skills", action],
processArgv: argv,
});
expect(ensureConfigReadyMock).toHaveBeenCalledWith(
expect.objectContaining({
runtime: runtimeMock,
commandPath: ["skills", action],
}),
);
});
it("applies --json stdout suppression only for explicit JSON output commands", async () => {
await runPreAction({
parseArgv: ["status"],
+46
View File
@@ -2211,6 +2211,52 @@ describe("runCli exit behavior", () => {
expect(startProxyMock).toHaveBeenCalledWith(undefined);
});
it.each([
{
name: "version-pinned skill install",
argv: ["node", "openclaw", "skills", "install", "@owner/weather", "--version", "1.2.3"],
},
{
name: "version-pinned skill verification",
argv: ["node", "openclaw", "skills", "verify", "@owner/weather", "--version", "1.2.3"],
},
{
name: "equals-form version-pinned skill install",
argv: ["node", "openclaw", "skills", "install", "@owner/weather", "--version=1.2.3"],
},
{
name: "profiled version-pinned skill verification",
argv: [
"node",
"openclaw",
"--profile",
"work",
"skills",
"verify",
"@owner/weather",
"--version",
"1.2.3",
],
},
])("starts the managed proxy for $name", async ({ argv }) => {
await withEnvAsync(
{
OPENCLAW_PROFILE: undefined,
OPENCLAW_STATE_DIR: undefined,
OPENCLAW_CONFIG_PATH: undefined,
},
async () => {
hasEnvHttpProxyAgentConfiguredMock.mockReturnValue(true);
tryRouteCliMock.mockResolvedValueOnce(true);
await runCli(argv);
expect(startProxyMock).toHaveBeenCalledWith(undefined);
expect(ensureGlobalUndiciEnvProxyDispatcherMock).toHaveBeenCalledOnce();
},
);
});
it.each([
["JSON flag", ["node", "openclaw", "plugins", "marketplace", "list", "--json"]],
["models status JSON alias", ["node", "openclaw", "models", "--status-json"]],