Skip to content
Merged
Show file tree
Hide file tree
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 4 additions & 0 deletions bin/lib/debug.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
// SPDX-FileCopyrightText: Copyright (c) 2026 NVIDIA CORPORATION & AFFILIATES. All rights reserved.
// SPDX-License-Identifier: Apache-2.0

module.exports = require("../../nemoclaw/dist/lib/debug.js");
47 changes: 36 additions & 11 deletions bin/nemoclaw.js
Original file line number Diff line number Diff line change
Expand Up @@ -216,16 +216,41 @@ function stop() {
run(`bash "${SCRIPTS}/start-services.sh" --stop`);
}

function debug(args) {
const result = spawnSync("bash", [path.join(SCRIPTS, "debug.sh"), ...args], {
stdio: "inherit",
cwd: ROOT,
env: {
...process.env,
SANDBOX_NAME: registry.listSandboxes().defaultSandbox || "",
},
});
exitWithSpawnResult(result);
async function debug(args) {
const { runDebug } = require("./lib/debug");
const opts = {};
for (let i = 0; i < args.length; i++) {
if (args[i] === "--help" || args[i] === "-h") {
console.log(`Usage: nemoclaw debug [OPTIONS]

Collect NemoClaw diagnostic information for bug reports.

Options:
--sandbox NAME Target sandbox (default: $NEMOCLAW_SANDBOX or auto-detect)
--quick, -q Collect minimal diagnostics only
--output PATH Write tarball to PATH (e.g. /tmp/nemoclaw-debug.tar.gz)
--help Show this help

Examples:
nemoclaw debug
nemoclaw debug --quick
nemoclaw debug --output /tmp/diag.tar.gz`);
return;
} else if (args[i] === "--quick" || args[i] === "-q") {
opts.quick = true;
} else if ((args[i] === "--output" || args[i] === "-o") && args[i + 1]) {
opts.output = args[++i];
} else if (args[i] === "--sandbox" && args[i + 1]) {
opts.sandboxName = args[++i];
} else {
console.error(`Unknown option: ${args[i]} (see --help)`);
process.exit(1);
}
}
if (!opts.sandboxName) {
opts.sandboxName = registry.listSandboxes().defaultSandbox || "";
}
await runDebug(opts);
}

function uninstall(args) {
Expand Down Expand Up @@ -466,7 +491,7 @@ const [cmd, ...args] = process.argv.slice(2);
case "start": await start(); break;
case "stop": stop(); break;
case "status": showStatus(); break;
case "debug": debug(args); break;
case "debug": await debug(args); break;
case "uninstall": uninstall(args); break;
case "list": listSandboxes(); break;
case "--version":
Expand Down
47 changes: 47 additions & 0 deletions nemoclaw/src/lib/debug.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,47 @@
// SPDX-FileCopyrightText: Copyright (c) 2026 NVIDIA CORPORATION & AFFILIATES. All rights reserved.
// SPDX-License-Identifier: Apache-2.0

import { describe, it, expect } from "vitest";
import { redact } from "../../dist/lib/debug.js";

describe("redact", () => {
it("redacts NVIDIA_API_KEY=value patterns", () => {
const key = ["NVIDIA", "API", "KEY"].join("_");
expect(redact(`${key}=some-value`)).toBe(`${key}=<REDACTED>`);
});

it("redacts generic KEY/TOKEN/SECRET/PASSWORD env vars", () => {
expect(redact("API_KEY=secret123")).toBe("API_KEY=<REDACTED>");
expect(redact("MY_TOKEN=tok_abc")).toBe("MY_TOKEN=<REDACTED>");
expect(redact("DB_PASSWORD=hunter2")).toBe("DB_PASSWORD=<REDACTED>");
expect(redact("MY_SECRET=s3cret")).toBe("MY_SECRET=<REDACTED>");
expect(redact("CREDENTIAL=cred")).toBe("CREDENTIAL=<REDACTED>");
});

it("redacts nvapi- prefixed keys", () => {
expect(redact("using key nvapi-AbCdEfGhIj1234")).toBe("using key <REDACTED>");
});

it("redacts GitHub personal access tokens", () => {
expect(redact("token: ghp_" + "a".repeat(36))).toBe("token: <REDACTED>");
});

it("redacts Bearer tokens", () => {
expect(redact("Authorization: Bearer eyJhbGciOiJIUzI1NiJ9.payload.sig")).toBe(
"Authorization: Bearer <REDACTED>",
);
});

it("handles multiple patterns in one string", () => {
const input = "API_KEY=secret nvapi-abcdefghijk Bearer tok123";
const result = redact(input);
expect(result).not.toContain("secret");
expect(result).not.toContain("nvapi-abcdefghijk");
expect(result).not.toContain("tok123");
});

it("leaves clean text unchanged", () => {
const clean = "Hello world, no secrets here";
expect(redact(clean)).toBe(clean);
});
});
Loading