Skip to content
Merged
1 change: 0 additions & 1 deletion src/commands/internal/dev/npm-link-or-shim.ts
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,6 @@ export default class InternalDevNpmLinkOrShimCommand extends NemoClawCommand {
static usage = ["internal dev npm-link-or-shim [--repo-root <path>]"];
static examples = ["<%= config.bin %> internal dev npm-link-or-shim"];
static flags = {
help: Flags.help({ char: "h" }),
"repo-root": Flags.string({ description: "Repository root to expose", hidden: true }),
};

Expand Down
3 changes: 1 addition & 2 deletions src/commands/internal/dns/fix-coredns.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
// SPDX-FileCopyrightText: Copyright (c) 2026 NVIDIA CORPORATION & AFFILIATES. All rights reserved.
// SPDX-License-Identifier: Apache-2.0

import { Args, Flags } from "@oclif/core";
import { Args } from "@oclif/core";
import { NemoClawCommand } from "../../../lib/cli/nemoclaw-oclif-command";

import { runFixCoreDns } from "../../../lib/actions/dns";
Expand All @@ -17,7 +17,6 @@ export default class InternalDnsFixCoreDnsCommand extends NemoClawCommand {
gatewayName: Args.string({ description: "OpenShell gateway name", required: false }),
};
static flags = {
help: Flags.help({ char: "h" }),
};

public async run(): Promise<void> {
Expand Down
3 changes: 1 addition & 2 deletions src/commands/internal/dns/setup-proxy.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
// SPDX-FileCopyrightText: Copyright (c) 2026 NVIDIA CORPORATION & AFFILIATES. All rights reserved.
// SPDX-License-Identifier: Apache-2.0

import { Args, Flags } from "@oclif/core";
import { Args } from "@oclif/core";
import { NemoClawCommand } from "../../../lib/cli/nemoclaw-oclif-command";

import { runSetupDnsProxy } from "../../../lib/actions/dns";
Expand All @@ -18,7 +18,6 @@ export default class InternalDnsSetupProxyCommand extends NemoClawCommand {
sandboxName: Args.string({ description: "Sandbox name", required: true }),
};
static flags = {
help: Flags.help({ char: "h" }),
};

public async run(): Promise<void> {
Expand Down
3 changes: 1 addition & 2 deletions src/commands/internal/installer/normalize-env.ts
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,6 @@ export default class InternalInstallerNormalizeEnvCommand extends NemoClawComman
static usage = ["internal installer normalize-env [--json]"];
static examples = ["<%= config.bin %> internal installer normalize-env --provider cloud --json"];
static flags = {
help: Flags.help({ char: "h" }),
json: Flags.boolean({ description: "Print normalized values as JSON" }),
"install-ref": Flags.string({ description: "NEMOCLAW_INSTALL_REF value" }),
"install-tag": Flags.string({ description: "NEMOCLAW_INSTALL_TAG value" }),
Expand All @@ -29,7 +28,7 @@ export default class InternalInstallerNormalizeEnvCommand extends NemoClawComman
NEMOCLAW_PROVIDER: flags.provider ?? process.env.NEMOCLAW_PROVIDER,
});

if (flags.json) console.log(JSON.stringify(normalized, null, 2));
if (flags.json) this.logJson(normalized);
else console.log(`ref=${normalized.installRef} provider=${normalized.provider.normalized ?? ""}`);
}
}
3 changes: 1 addition & 2 deletions src/commands/internal/installer/plan.ts
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,6 @@ export default class InternalInstallerPlanCommand extends NemoClawCommand {
static usage = ["internal installer plan [--json]"];
static examples = ["<%= config.bin %> internal installer plan --json --provider nim --install-ref v0.1.0"];
static flags = {
help: Flags.help({ char: "h" }),
json: Flags.boolean({ description: "Print the installer plan as JSON" }),
"install-ref": Flags.string({ description: "Install ref override" }),
"install-tag": Flags.string({ description: "Install tag fallback" }),
Expand Down Expand Up @@ -44,7 +43,7 @@ export default class InternalInstallerPlanCommand extends NemoClawCommand {
stampedVersion: flags["stamped-version"],
});

if (flags.json) console.log(JSON.stringify(plan, null, 2));
if (flags.json) this.logJson(plan);
else console.log(`Installer plan: ref '${plan.installRef}', version '${plan.installerVersion}'`);
}
}
3 changes: 1 addition & 2 deletions src/commands/internal/installer/resolve-release-tag.ts
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,6 @@ export default class InternalInstallerResolveReleaseTagCommand extends NemoClawC
static usage = ["internal installer resolve-release-tag [--json]"];
static examples = ["<%= config.bin %> internal installer resolve-release-tag --install-ref v0.1.0"];
static flags = {
help: Flags.help({ char: "h" }),
json: Flags.boolean({ description: "Print the resolved ref as JSON" }),
"install-ref": Flags.string({ description: "NEMOCLAW_INSTALL_REF value" }),
"install-tag": Flags.string({ description: "NEMOCLAW_INSTALL_TAG value" }),
Expand All @@ -27,7 +26,7 @@ export default class InternalInstallerResolveReleaseTagCommand extends NemoClawC
NEMOCLAW_INSTALL_TAG: flags["install-tag"] ?? process.env.NEMOCLAW_INSTALL_TAG,
});

if (flags.json) console.log(JSON.stringify({ installRef }, null, 2));
if (flags.json) this.logJson({ installRef });
else console.log(installRef);
}
}
3 changes: 1 addition & 2 deletions src/commands/internal/uninstall/classify-shim.ts
Original file line number Diff line number Diff line change
Expand Up @@ -17,14 +17,13 @@ export default class InternalUninstallClassifyShimCommand extends NemoClawComman
path: Args.string({ description: "Shim path to classify", required: true }),
};
static flags = {
help: Flags.help({ char: "h" }),
json: Flags.boolean({ description: "Print classification as JSON" }),
};

public async run(): Promise<void> {
const { args, flags } = await this.parse(InternalUninstallClassifyShimCommand);
const classification = classifyShimPath(args.path);
if (flags.json) console.log(JSON.stringify(classification, null, 2));
if (flags.json) this.logJson(classification);
else console.log(`${classification.kind}: ${classification.reason}`);
}
}
3 changes: 1 addition & 2 deletions src/commands/internal/uninstall/plan.ts
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,6 @@ export default class InternalUninstallPlanCommand extends NemoClawCommand {
static usage = ["internal uninstall plan [--json] [--delete-models] [--keep-openshell]"];
static examples = [`${CLI_NAME} internal uninstall plan --json --yes`];
static flags = {
help: Flags.help({ char: "h" }),
json: Flags.boolean({ description: "Print the uninstall plan as JSON" }),
yes: Flags.boolean({ description: "Accepted for parity with run-plan; ignored while planning" }),
"delete-models": Flags.boolean({ description: `Plan removal of ${CLI_DISPLAY_NAME}-pulled Ollama models` }),
Expand All @@ -31,7 +30,7 @@ export default class InternalUninstallPlanCommand extends NemoClawCommand {
gatewayName: flags.gateway,
keepOpenShell: flags["keep-openshell"] ?? false,
});
if (flags.json) console.log(JSON.stringify(plan, null, 2));
if (flags.json) this.logJson(plan);
else console.log(`Uninstall plan: ${plan.steps.length} steps for gateway '${plan.gatewayName}'`);
}
}
1 change: 0 additions & 1 deletion src/commands/internal/uninstall/run-plan.ts
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,6 @@ export default class InternalUninstallRunPlanCommand extends NemoClawCommand {
static usage = ["internal uninstall run-plan [--yes] [--keep-openshell] [--delete-models]"];
static examples = [`${CLI_NAME} internal uninstall run-plan --yes`];
static flags = {
help: Flags.help({ char: "h" }),
yes: Flags.boolean({ description: "Skip the confirmation prompt" }),
"keep-openshell": Flags.boolean({ description: "Leave the openshell binary installed" }),
"delete-models": Flags.boolean({ description: `Remove ${CLI_DISPLAY_NAME}-pulled Ollama models` }),
Expand Down
24 changes: 24 additions & 0 deletions src/lib/cli/oclif-command-metadata.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,10 @@
import { Config as OclifConfig } from "@oclif/core";
import { describe, expect, it } from "vitest";

type OclifCommandClass = {
flags?: Record<string, unknown>;
};

function extendsNemoClawCommand(commandClass: unknown): boolean {
if (typeof commandClass !== "function") return false;
let current = Object.getPrototypeOf(commandClass) as { name?: string } | null;
Expand All @@ -14,6 +18,14 @@ function extendsNemoClawCommand(commandClass: unknown): boolean {
return false;
}

function commandOwnsHelpFlag(commandClass: unknown): boolean {
return (
typeof commandClass === "function" &&
Object.hasOwn(commandClass as OclifCommandClass, "flags") &&
Object.hasOwn((commandClass as OclifCommandClass).flags ?? {}, "help")
);
}

describe("oclif command metadata", () => {
it("keeps discovered commands on the shared NemoClaw oclif base", async () => {
const config = await OclifConfig.load(process.cwd());
Expand All @@ -27,6 +39,18 @@ describe("oclif command metadata", () => {
expect(nonConforming).toEqual([]);
});

it("keeps the help flag centralized on the shared base command", async () => {
const config = await OclifConfig.load(process.cwd());
const duplicatedHelpFlags: string[] = [];

for (const command of config.commands) {
const commandClass = await command.load();
if (commandOwnsHelpFlag(commandClass)) duplicatedHelpFlags.push(command.id);
}

expect(duplicatedHelpFlags).toEqual([]);
});

it("keeps public discovered commands documented in oclif statics", async () => {
const config = await OclifConfig.load(process.cwd());
const publicCommands = config.commands.filter((command) => command.hidden !== true);
Expand Down
2 changes: 0 additions & 2 deletions src/lib/commands/credentials.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,6 @@
// SPDX-FileCopyrightText: Copyright (c) 2026 NVIDIA CORPORATION & AFFILIATES. All rights reserved.
// SPDX-License-Identifier: Apache-2.0

import { Flags } from "@oclif/core";
import { NemoClawCommand } from "../cli/nemoclaw-oclif-command";

import { printCredentialsUsage } from "./credentials/common";
Expand All @@ -18,7 +17,6 @@ export default class CredentialsCommand extends NemoClawCommand {
"<%= config.bin %> credentials reset nvidia-prod --yes",
];
static flags = {
help: Flags.help({ char: "h" }),
};

public async run(): Promise<void> {
Expand Down
2 changes: 0 additions & 2 deletions src/lib/commands/credentials/list.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,6 @@
// SPDX-FileCopyrightText: Copyright (c) 2026 NVIDIA CORPORATION & AFFILIATES. All rights reserved.
// SPDX-License-Identifier: Apache-2.0

import { Flags } from "@oclif/core";
import { NemoClawCommand } from "../../cli/nemoclaw-oclif-command";

import { CLI_NAME } from "../../cli/branding";
Expand All @@ -17,7 +16,6 @@ export default class CredentialsListCommand extends NemoClawCommand {
static usage = ["credentials list"];
static examples = ["<%= config.bin %> credentials list"];
static flags = {
help: Flags.help({ char: "h" }),
};

public async run(): Promise<void> {
Expand Down
1 change: 0 additions & 1 deletion src/lib/commands/credentials/reset.ts
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,6 @@ export default class CredentialsResetCommand extends NemoClawCommand {
}),
};
static flags = {
help: Flags.help({ char: "h" }),
yes: Flags.boolean({ char: "y", description: "Skip the confirmation prompt" }),
};

Expand Down
1 change: 0 additions & 1 deletion src/lib/commands/debug.ts
Original file line number Diff line number Diff line change
Expand Up @@ -80,7 +80,6 @@ export default class DebugCliCommand extends NemoClawCommand {
"<%= config.bin %> debug --output /tmp/nemoclaw-debug.tar.gz",
];
static flags = {
help: Flags.help({ char: "h" }),
quick: Flags.boolean({ char: "q", description: "Only collect minimal diagnostics" }),
output: Flags.string({ char: "o", description: "Write a tarball to FILE" }),
sandbox: Flags.string({ description: "Target sandbox name" }),
Expand Down
3 changes: 1 addition & 2 deletions src/lib/commands/deploy.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
// SPDX-FileCopyrightText: Copyright (c) 2026 NVIDIA CORPORATION & AFFILIATES. All rights reserved.
// SPDX-License-Identifier: Apache-2.0

import { Args, Flags } from "@oclif/core";
import { Args } from "@oclif/core";
import { NemoClawCommand } from "../cli/nemoclaw-oclif-command";

import { runDeployAction } from "../actions/global";
Expand All @@ -21,7 +21,6 @@ export default class DeployCliCommand extends NemoClawCommand {
}),
};
static flags = {
help: Flags.help({ char: "h" }),
};

public async run(): Promise<void> {
Expand Down
2 changes: 0 additions & 2 deletions src/lib/commands/deprecated/start.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,6 @@
// SPDX-FileCopyrightText: Copyright (c) 2026 NVIDIA CORPORATION & AFFILIATES. All rights reserved.
// SPDX-License-Identifier: Apache-2.0

import { Flags } from "@oclif/core";
import { NemoClawCommand } from "../../cli/nemoclaw-oclif-command";

import { CLI_NAME } from "../../cli/branding";
Expand All @@ -17,7 +16,6 @@ export default class DeprecatedStartCommand extends NemoClawCommand {
static usage = ["start"];
static examples = ["<%= config.bin %> start"];
static flags = {
help: Flags.help({ char: "h" }),
};

public async run(): Promise<void> {
Expand Down
2 changes: 0 additions & 2 deletions src/lib/commands/deprecated/stop.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,6 @@
// SPDX-FileCopyrightText: Copyright (c) 2026 NVIDIA CORPORATION & AFFILIATES. All rights reserved.
// SPDX-License-Identifier: Apache-2.0

import { Flags } from "@oclif/core";
import { NemoClawCommand } from "../../cli/nemoclaw-oclif-command";

import { CLI_NAME } from "../../cli/branding";
Expand All @@ -17,7 +16,6 @@ export default class DeprecatedStopCommand extends NemoClawCommand {
static usage = ["stop"];
static examples = ["<%= config.bin %> stop"];
static flags = {
help: Flags.help({ char: "h" }),
};

public async run(): Promise<void> {
Expand Down
1 change: 0 additions & 1 deletion src/lib/commands/gateway-token.ts
Original file line number Diff line number Diff line change
Expand Up @@ -59,7 +59,6 @@ export default class GatewayTokenCliCommand extends NemoClawCommand {
}),
};
static flags = {
help: Flags.help({ char: "h" }),
quiet: Flags.boolean({ char: "q", description: "Suppress the stderr security warning" }),
};

Expand Down
1 change: 0 additions & 1 deletion src/lib/commands/onboard/common.ts
Original file line number Diff line number Diff line change
Expand Up @@ -41,7 +41,6 @@ export type OnboardFlags = {

export function buildOnboardFlags(): Record<string, any> {
return {
help: Flags.help({ char: "h" }),
"non-interactive": Flags.boolean({ description: "Run without interactive prompts" }),
resume: Flags.boolean({
description: "Resume an interrupted onboarding session",
Expand Down
1 change: 0 additions & 1 deletion src/lib/commands/sandbox/channels/common.ts
Original file line number Diff line number Diff line change
Expand Up @@ -54,6 +54,5 @@ export const channelMutationArgs = {
};

export const channelMutationFlags = {
help: Flags.help({ char: "h" }),
"dry-run": Flags.boolean({ description: "Preview the change without applying it" }),
};
2 changes: 0 additions & 2 deletions src/lib/commands/sandbox/channels/list.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,6 @@
// SPDX-FileCopyrightText: Copyright (c) 2026 NVIDIA CORPORATION & AFFILIATES. All rights reserved.
// SPDX-License-Identifier: Apache-2.0

import { Flags } from "@oclif/core";
import { NemoClawCommand } from "../../../cli/nemoclaw-oclif-command";

import { listSandboxChannels } from "../../../actions/sandbox/policy-channel";
Expand All @@ -18,7 +17,6 @@ export default class SandboxChannelsListCommand extends NemoClawCommand {
sandboxName: sandboxNameArg,
};
static flags = {
help: Flags.help({ char: "h" }),
};

public async run(): Promise<void> {
Expand Down
1 change: 0 additions & 1 deletion src/lib/commands/sandbox/config/get.ts
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,6 @@ export default class SandboxConfigGetCommand extends NemoClawCommand {
sandboxName: sandboxNameArg,
};
static flags = {
help: Flags.help({ char: "h" }),
key: Flags.string({ description: "Dotpath to read from the sanitized config" }),
format: Flags.string({
description: "Output format",
Expand Down
1 change: 0 additions & 1 deletion src/lib/commands/sandbox/config/set.ts
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,6 @@ export default class SandboxConfigSetCommand extends NemoClawCommand {
sandboxName: sandboxNameArg,
};
static flags = {
help: Flags.help({ char: "h" }),
key: Flags.string({ description: "Dotpath to update in the config" }),
value: Flags.string({ description: "Value to write; JSON values are parsed when possible" }),
restart: Flags.boolean({ description: "Signal the sandbox agent process to reload after writing" }),
Expand Down
1 change: 0 additions & 1 deletion src/lib/commands/sandbox/connect.ts
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,6 @@ export default class ConnectCliCommand extends NemoClawCommand {
sandboxName: Args.string({ name: "sandbox", description: "Sandbox name", required: true }),
};
static flags = {
help: Flags.help({ char: "h" }),
"probe-only": Flags.boolean({ description: "Recover and check the sandbox without opening SSH" }),
"dangerously-skip-permissions": Flags.boolean({ hidden: true }),
};
Expand Down
1 change: 0 additions & 1 deletion src/lib/commands/sandbox/doctor.ts
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,6 @@ export default class SandboxDoctorCliCommand extends NemoClawCommand {
}),
};
static flags = {
help: Flags.help({ char: "h" }),
json: Flags.boolean({ description: "Emit machine-readable JSON diagnostics" }),
};

Expand Down
1 change: 0 additions & 1 deletion src/lib/commands/sandbox/hosts/common.ts
Original file line number Diff line number Diff line change
Expand Up @@ -50,6 +50,5 @@ export const hostAliasAddArgs = {
};

export const hostAliasMutationFlags = {
help: Flags.help({ char: "h" }),
"dry-run": Flags.boolean({ description: "Preview the JSON patch without applying it" }),
};
2 changes: 0 additions & 2 deletions src/lib/commands/sandbox/hosts/list.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,6 @@
// SPDX-FileCopyrightText: Copyright (c) 2026 NVIDIA CORPORATION & AFFILIATES. All rights reserved.
// SPDX-License-Identifier: Apache-2.0

import { Flags } from "@oclif/core";
import { NemoClawCommand } from "../../../cli/nemoclaw-oclif-command";

import { getHostsRuntimeBridge, hostAliasSandboxArgs } from "./common";
Expand All @@ -15,7 +14,6 @@ export default class HostsListCommand extends NemoClawCommand {
static examples = ["<%= config.bin %> sandbox hosts list alpha"];
static args = hostAliasSandboxArgs;
static flags = {
help: Flags.help({ char: "h" }),
};

public async run(): Promise<void> {
Expand Down
1 change: 0 additions & 1 deletion src/lib/commands/sandbox/logs.ts
Original file line number Diff line number Diff line change
Expand Up @@ -52,7 +52,6 @@ export default class SandboxLogsCommand extends NemoClawCommand {
}),
};
static flags = {
help: Flags.help({ char: "h" }),
follow: Flags.boolean({ description: "Follow logs until interrupted" }),
tail: Flags.integer({
char: "n",
Expand Down
1 change: 0 additions & 1 deletion src/lib/commands/sandbox/policy/common.ts
Original file line number Diff line number Diff line change
Expand Up @@ -50,7 +50,6 @@ export function appendCommonPolicyFlags(
export const policyMutationArgs = { sandboxName: sandboxNameArg, preset: presetArg };

export const policyMutationFlags = {
help: Flags.help({ char: "h" }),
yes: Flags.boolean({ char: "y", description: "Skip the confirmation prompt" }),
force: Flags.boolean({ description: "Skip the confirmation prompt" }),
"dry-run": Flags.boolean({ description: "Preview without applying" }),
Expand Down
Loading
Loading