CLI: unify interactive root argv scanning

This commit is contained in:
Benjamin Jesuiter
2026-02-17 10:42:06 +01:00
parent d8812d5590
commit 47dbf9beda
2 changed files with 62 additions and 62 deletions

View File

@@ -2,6 +2,7 @@ import { describe, expect, it } from "vitest";
import {
isCommanderExitError,
rewriteUpdateFlagArgv,
scanInteractiveRootArgv,
shouldEnsureCliPath,
shouldRegisterPrimarySubcommand,
shouldSkipPluginCommandRegistration,
@@ -119,6 +120,27 @@ describe("shouldSkipPluginCommandRegistration", () => {
});
});
describe("scanInteractiveRootArgv", () => {
it("extracts interactive flag, primary, and stripped argv in one pass", () => {
expect(
scanInteractiveRootArgv(["node", "openclaw", "-i", "--profile", "dev"]).hasInteractiveFlag,
).toBe(true);
expect(
scanInteractiveRootArgv(["node", "openclaw", "-i", "--profile", "dev"]).primary,
).toBeNull();
expect(
scanInteractiveRootArgv(["node", "openclaw", "-i", "--profile", "dev"]).strippedArgv,
).toEqual(["node", "openclaw", "--profile", "dev"]);
});
it("detects primary commands while stripping interactive flags", () => {
const scanned = scanInteractiveRootArgv(["node", "openclaw", "-i", "status", "--json"]);
expect(scanned.hasInteractiveFlag).toBe(true);
expect(scanned.primary).toBe("status");
expect(scanned.strippedArgv).toEqual(["node", "openclaw", "status", "--json"]);
});
});
describe("shouldUseInteractiveCommandSelector", () => {
it("enables selector for -i", () => {
expect(

View File

@@ -63,42 +63,56 @@ export function shouldEnsureCliPath(argv: string[]): boolean {
const ROOT_OPTIONS_WITH_VALUE = new Set(["--profile"]);
function parseRootInvocation(argv: string[]): {
export function scanInteractiveRootArgv(argv: string[]): {
primary: string | null;
hasInteractiveFlag: boolean;
strippedArgv: string[];
} {
const args = argv.slice(2);
const next: string[] = [];
let primary: string | null = null;
let hasInteractiveFlag = false;
let expectOptionValue = false;
let sawPrimary = false;
for (const arg of args) {
if (expectOptionValue) {
expectOptionValue = false;
continue;
if (!sawPrimary) {
if (expectOptionValue) {
expectOptionValue = false;
next.push(arg);
continue;
}
if (arg === "--") {
sawPrimary = true;
next.push(arg);
continue;
}
if (arg === "-i" || arg === "--interactive") {
hasInteractiveFlag = true;
continue;
}
if (arg.startsWith("--profile=")) {
next.push(arg);
continue;
}
if (ROOT_OPTIONS_WITH_VALUE.has(arg)) {
expectOptionValue = true;
next.push(arg);
continue;
}
if (!arg.startsWith("-")) {
primary = arg;
sawPrimary = true;
}
}
if (arg === "--") {
break;
}
if (arg === "-i" || arg === "--interactive") {
hasInteractiveFlag = true;
continue;
}
if (arg.startsWith("--profile=")) {
continue;
}
if (ROOT_OPTIONS_WITH_VALUE.has(arg)) {
expectOptionValue = true;
continue;
}
if (arg.startsWith("-")) {
continue;
}
primary = arg;
break;
next.push(arg);
}
return { primary, hasInteractiveFlag };
return {
primary,
hasInteractiveFlag,
strippedArgv: [...argv.slice(0, 2), ...next],
};
}
export function shouldUseInteractiveCommandSelector(params: {
@@ -111,7 +125,7 @@ export function shouldUseInteractiveCommandSelector(params: {
if (hasHelpOrVersion(params.argv)) {
return false;
}
const root = parseRootInvocation(params.argv);
const root = scanInteractiveRootArgv(params.argv);
if (!root.hasInteractiveFlag) {
return false;
}
@@ -130,43 +144,7 @@ export function shouldUseInteractiveCommandSelector(params: {
}
export function stripInteractiveSelectorArgs(argv: string[]): string[] {
const args = argv.slice(2);
const next: string[] = [];
let sawPrimary = false;
let expectOptionValue = false;
for (const arg of args) {
if (!sawPrimary) {
if (expectOptionValue) {
expectOptionValue = false;
next.push(arg);
continue;
}
if (arg === "--") {
sawPrimary = true;
next.push(arg);
continue;
}
if (arg === "-i" || arg === "--interactive") {
continue;
}
if (arg.startsWith("--profile=")) {
next.push(arg);
continue;
}
if (ROOT_OPTIONS_WITH_VALUE.has(arg)) {
expectOptionValue = true;
next.push(arg);
continue;
}
if (!arg.startsWith("-")) {
sawPrimary = true;
}
}
next.push(arg);
}
return [...argv.slice(0, 2), ...next];
return scanInteractiveRootArgv(argv).strippedArgv;
}
export function isCommanderExitError(error: unknown): boolean {