From a345e27a64f941ff0704df8f93c70bdde9416b95 Mon Sep 17 00:00:00 2001 From: Tian Zhang Date: Wed, 27 May 2026 13:15:33 +0000 Subject: [PATCH 1/2] fix(cli): add tunnel status command Signed-off-by: Tian Zhang --- docs/reference/commands.mdx | 9 ++++++++ src/commands/tunnel.ts | 23 ++++++++++++++++++++ src/commands/tunnel/status.ts | 21 ++++++++++++++++++ src/lib/cli/command-registry.test.ts | 23 ++++++++++---------- src/lib/cli/public-display-defaults.ts | 7 ++++++ test/cli.test.ts | 30 ++++++++++++++++++++++++++ 6 files changed, 102 insertions(+), 11 deletions(-) create mode 100644 src/commands/tunnel.ts create mode 100644 src/commands/tunnel/status.ts diff --git a/docs/reference/commands.mdx b/docs/reference/commands.mdx index 9107f2126a5..20186f2e530 100644 --- a/docs/reference/commands.mdx +++ b/docs/reference/commands.mdx @@ -1021,6 +1021,15 @@ $ nemoclaw tunnel stop `nemoclaw stop` remains as a deprecated alias that prints a warning and delegates to `tunnel stop`. +### `nemoclaw tunnel status` + +Show the current cloudflared public-URL tunnel status for the default sandbox dashboard. +The output reports whether cloudflared is running, stopped, or stale, and includes the same recovery hint used by `nemoclaw status`. + +```console +$ nemoclaw tunnel status +``` + ### `nemoclaw start` diff --git a/src/commands/tunnel.ts b/src/commands/tunnel.ts new file mode 100644 index 00000000000..17c685889d8 --- /dev/null +++ b/src/commands/tunnel.ts @@ -0,0 +1,23 @@ +// SPDX-FileCopyrightText: Copyright (c) 2026 NVIDIA CORPORATION & AFFILIATES. All rights reserved. +// SPDX-License-Identifier: Apache-2.0 + +import { NemoClawCommand } from "../lib/cli/nemoclaw-oclif-command"; + +export default class TunnelCommand extends NemoClawCommand { + static id = "tunnel"; + static strict = true; + static summary = "Manage the cloudflared public-URL tunnel"; + static description = "Manage the cloudflared public-URL tunnel for the default sandbox dashboard."; + static usage = ["tunnel "]; + static examples = [ + "<%= config.bin %> tunnel start", + "<%= config.bin %> tunnel stop", + "<%= config.bin %> tunnel status", + ]; + static flags = {}; + + public async run(): Promise { + await this.parse(TunnelCommand); + this.error("Missing tunnel subcommand. Run `nemoclaw tunnel --help` for usage.", { exit: 2 }); + } +} diff --git a/src/commands/tunnel/status.ts b/src/commands/tunnel/status.ts new file mode 100644 index 00000000000..9253c3d4b6d --- /dev/null +++ b/src/commands/tunnel/status.ts @@ -0,0 +1,21 @@ +// SPDX-FileCopyrightText: Copyright (c) 2026 NVIDIA CORPORATION & AFFILIATES. All rights reserved. +// SPDX-License-Identifier: Apache-2.0 + +import { NemoClawCommand } from "../../lib/cli/nemoclaw-oclif-command"; + +import { showStatus } from "../../lib/tunnel/services"; + +export default class TunnelStatusCommand extends NemoClawCommand { + static id = "tunnel:status"; + static strict = true; + static summary = "Show cloudflared public-URL tunnel status"; + static description = "Show the cloudflared public-URL tunnel status for the default sandbox dashboard."; + static usage = ["tunnel status"]; + static examples = ["<%= config.bin %> tunnel status"]; + static flags = {}; + + public async run(): Promise { + await this.parse(TunnelStatusCommand); + showStatus(); + } +} diff --git a/src/lib/cli/command-registry.test.ts b/src/lib/cli/command-registry.test.ts index 275523be1a2..262209786ca 100644 --- a/src/lib/cli/command-registry.test.ts +++ b/src/lib/cli/command-registry.test.ts @@ -17,10 +17,10 @@ import { getRegisteredOclifCommandsMetadata } from "./oclif-metadata"; describe("command-registry", () => { describe("COMMANDS array", () => { - it("should contain exactly 62 commands", () => { - // 28 global (22 visible + 6 hidden help/version aliases) - // 34 sandbox (28 visible + 6 hidden shields/config) - expect(COMMANDS).toHaveLength(62); + it("partitions commands into global and sandbox scopes", () => { + const partitioned = [...globalCommands(), ...sandboxCommands()]; + expect(partitioned).toHaveLength(COMMANDS.length); + expect(new Set(partitioned).size).toBe(COMMANDS.length); }); it("should have no duplicate usage strings", () => { @@ -39,9 +39,12 @@ describe("command-registry", () => { }); describe("globalCommands()", () => { - it("should return exactly 28 entries", () => { - // 22 visible + 6 hidden (help, --help, -h, version, --version, -v) - expect(globalCommands()).toHaveLength(28); + it("includes public global service commands", () => { + const usages = globalCommands().map((cmd) => cmd.usage); + expect(usages).toContain("nemoclaw tunnel start"); + expect(usages).toContain("nemoclaw tunnel stop"); + expect(usages).toContain("nemoclaw tunnel status"); + expect(usages).toContain("nemoclaw status"); }); it("every entry has scope global", () => { @@ -65,10 +68,8 @@ describe("command-registry", () => { }); describe("visibleCommands()", () => { - it("should exclude 12 hidden commands (50 visible)", () => { - // 6 hidden global (help, --help, -h, version, --version, -v) + - // 6 hidden sandbox (shields×3, config get/set/rotate-token) - expect(visibleCommands()).toHaveLength(50); + it("returns exactly the non-hidden commands", () => { + expect(visibleCommands()).toEqual(COMMANDS.filter((cmd) => !cmd.hidden)); }); it("no visible command has hidden=true", () => { diff --git a/src/lib/cli/public-display-defaults.ts b/src/lib/cli/public-display-defaults.ts index f1b77b50d2a..00ec4588f21 100644 --- a/src/lib/cli/public-display-defaults.ts +++ b/src/lib/cli/public-display-defaults.ts @@ -434,6 +434,13 @@ const PUBLIC_DISPLAY_LAYOUT: Record = { "order": 33 } ], + "tunnel:status": [ + { + "group": "Services", + "order": 33.5, + "description": "Show cloudflared public-URL tunnel status" + } + ], "uninstall": [ { "group": "Cleanup", diff --git a/test/cli.test.ts b/test/cli.test.ts index 59c87d9dba6..80b38fc7eaf 100644 --- a/test/cli.test.ts +++ b/test/cli.test.ts @@ -955,6 +955,23 @@ describe("CLI dispatch", () => { expect(r.out).toContain("Unexpected argument: bogus"); }); + it("tunnel --help exits 0 and shows tunnel subcommands", () => { + const r = run("tunnel --help"); + expect(r.code).toBe(0); + expect(r.out).toContain("tunnel "); + expect(r.out).toContain("tunnel start"); + expect(r.out).toContain("tunnel stop"); + expect(r.out).toContain("tunnel status"); + }); + + it("root help shows tunnel status with tunnel start and stop", () => { + const r = run("--help"); + expect(r.code).toBe(0); + expect(r.out).toContain("nemoclaw tunnel start"); + expect(r.out).toContain("nemoclaw tunnel stop"); + expect(r.out).toContain("nemoclaw tunnel status"); + }); + it("tunnel start --help exits 0 and shows tunnel usage", () => { const r = run("tunnel start --help"); expect(r.code).toBe(0); @@ -976,6 +993,19 @@ describe("CLI dispatch", () => { expect(r.out).toContain("Stop the cloudflared public-URL tunnel"); }); + it("tunnel status --help exits 0 and shows tunnel status usage", () => { + const r = run("tunnel status --help"); + expect(r.code).toBe(0); + expect(r.out).toContain("tunnel status"); + expect(r.out).toContain("Show cloudflared public-URL tunnel status"); + }); + + it("tunnel status exits 0 and prints cloudflared status", () => { + const r = run("tunnel status"); + expect(r.code).toBe(0); + expect(r.out).toContain("cloudflared"); + }); + it("deprecated stop --help exits 0 and shows alias usage", () => { const r = run("stop --help"); expect(r.code).toBe(0); From 46492ce53f93e84edf800a9d9b7eb9cbc93004a2 Mon Sep 17 00:00:00 2001 From: Tian Zhang Date: Fri, 5 Jun 2026 11:42:17 +0900 Subject: [PATCH 2/2] docs: sync Hermes tunnel status command reference --- docs/reference/commands-nemohermes.mdx | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/docs/reference/commands-nemohermes.mdx b/docs/reference/commands-nemohermes.mdx index 4c45ce04371..6a062b67636 100644 --- a/docs/reference/commands-nemohermes.mdx +++ b/docs/reference/commands-nemohermes.mdx @@ -1212,6 +1212,15 @@ nemohermes tunnel stop `nemohermes stop` remains as a deprecated alias that prints a warning and delegates to `tunnel stop`. +### `nemohermes tunnel status` + +Show the current cloudflared public-URL tunnel status for the default sandbox dashboard. +The output reports whether cloudflared is running, stopped, or stale, and includes the same recovery hint used by `nemohermes status`. + +```console +nemohermes tunnel status +``` + ### `nemohermes start`