Skip to content

feat(onboard): onboard presets#1463

Merged
ericksoa merged 7 commits intoNVIDIA:mainfrom
cheese-head:onboard-presets
Apr 6, 2026
Merged

feat(onboard): onboard presets#1463
ericksoa merged 7 commits intoNVIDIA:mainfrom
cheese-head:onboard-presets

Conversation

@cheese-head
Copy link
Copy Markdown
Contributor

@cheese-head cheese-head commented Apr 3, 2026

Summary

Users navigate presets with arrow keys or j/k, toggle with Space, select all/none with a, and confirm with Enter. Credential-detected presets (pypi, npm, slack, etc.) are pre-checked automatically.

Related Issue

Changes

  • Added presetsCheckboxSelector — a raw-mode TUI that renders a navigable checklist with green checkmarks, redraws in-place on each keystroke, and falls back to a plain text prompt when stdin is not a TTY.

Type of Change

  • Code change for a new feature, bug fix, or refactor.

Testing

  • npx prek run --all-files passes (or equivalently make check).
  • npm test passes.
  • make docs builds without warnings. (for doc-only changes)

Note: 6 pre-existing test failures unrelated to this PR (preflight.test.ts port/lsof tests fail due to SSH running on the dev machine; service-env.test.js bash script tests time out in this environment; cli.test.js exit-code test returns null). All failures reproduce on main before these changes.

Checklist

General

Code Changes

  • Formatters applied — npx prek run --all-files auto-fixes formatting (or make format for targeted runs).
  • Tests added or updated for new or changed behavior.
  • No secrets, API keys, or credentials committed.
  • Doc pages updated for any user-facing behavior changes (new commands, changed defaults, new features, bug fixes that contradict existing docs).

Doc Changes

  • Follows the style guide. Try running the update-docs agent skill to draft changes while complying with the style guide. For example, prompt your agent with "/update-docs catch up the docs for the new changes I made in this PR."
  • New pages include SPDX license header and frontmatter, if creating a new page.
  • Cross-references and links verified.

Signed-off-by: Patrick Riel priel@nvidia.com

Summary by CodeRabbit

  • New Features

    • Interactive checkbox UI for policy preset selection (keyboard navigation, bulk select/clear) plus a non-interactive fallback that accepts comma-separated preset names; empty input skips selection.
    • Initial selections pre-populated with already-applied and suggested presets.
  • Bug Fixes / Reliability

    • Applies only newly selected presets and retries transient failures; unknown preset names are ignored with a warning.
  • Tests

    • Added tests validating non-interactive parsing, messaging, listing output, and color handling.

Signed-off-by: Patrick Riel <priel@nvidia.com>
Signed-off-by: Patrick Riel <priel@nvidia.com>
Signed-off-by: Patrick Riel <priel@nvidia.com>
Signed-off-by: Patrick Riel <priel@nvidia.com>
@coderabbitai
Copy link
Copy Markdown
Contributor

coderabbitai bot commented Apr 3, 2026

No actionable comments were generated in the recent review. 🎉

ℹ️ Recent review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

Run ID: 00a79553-93a9-41ce-876f-be899c78fa23

📥 Commits

Reviewing files that changed from the base of the PR and between 4bd1b66 and d6d724c.

📒 Files selected for processing (2)
  • bin/lib/onboard.js
  • test/presets-checkbox.test.js
✅ Files skipped from review due to trivial changes (1)
  • test/presets-checkbox.test.js

📝 Walkthrough

Walkthrough

Added a selectable policy-presets UI (presetsCheckboxSelector) to bin/lib/onboard.js with a TTY TUI and non‑TTY comma-separated fallback; updated setupPoliciesWithSelection to seed initial selections, compute/apply only newly selected presets with per-preset retry logic, and exported the helper.

Changes

Cohort / File(s) Summary
Interactive Policy Preset Selector
bin/lib/onboard.js
Added presetsCheckboxSelector(allPresets, initialSelected) providing a TTY (arrow/j/k, Space toggle, a select/clear-all, Enter confirm) and a non‑TTY comma-separated input fallback. Replaced prior prompt flow in setupPoliciesWithSelection to seed initial selections from sandbox-applied and credential-suggested presets, compute newlySelected, apply only those presets, and retry each policies.applyPreset up to 3 times. Exported the helper via module.exports. Minor formatting simplification in non-interactive branch.
Tests
test/presets-checkbox.test.js
Added Vitest tests exercising non‑TTY behavior by running bin/lib/onboard.js in subprocesses with stubbed input: zero-presets handling, empty input skipping, parsing single/multiple comma-separated names, trimming, ignoring unknown names (logs to stderr), listing rendering with check marks/descriptions, and ensuring no ANSI output when NO_COLOR=1.

Sequence Diagram(s)

sequenceDiagram
    rect rgba(200,200,255,0.5)
    participant User
    end
    rect rgba(200,255,200,0.5)
    participant Onboard
    end
    rect rgba(255,200,200,0.5)
    participant PoliciesService
    end

    User->>Onboard: invoke onboarding (TTY or non-TTY)
    Onboard->>Onboard: call presetsCheckboxSelector(allPresets, initialSelected)
    alt TTY
        Onboard->>User: render TUI, accept navigation/toggle/select/all, confirm
    else non-TTY
        Onboard->>User: print presets, read comma-separated input
    end
    User-->>Onboard: selected presets
    Onboard->>Onboard: compute newlySelected (exclude already applied)
    loop for each preset in newlySelected
        Onboard->>PoliciesService: policies.applyPreset(preset)
        PoliciesService-->>Onboard: success / transient error
        alt transient error
            Onboard->>PoliciesService: retry up to 2 more times
        end
    end
    Onboard->>User: final result / messages
Loading

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~25 minutes

Poem

🐰 I hopped through options, toggles bright and quick,
Pressed Space, pressed Enter, picked presets real slick.
TTY or plain text, I still made my pick—
Retries for the stubborn, applied each little trick.
Hooray for choices, and carrots for the click!

🚥 Pre-merge checks | ✅ 2 | ❌ 1

❌ Failed checks (1 inconclusive)

Check name Status Explanation Resolution
Title check ❓ Inconclusive The title 'feat(onboard): onboard presets' is generic and does not clearly summarize the main change. While it references the feature area, it uses vague phrasing that fails to convey the specific addition of an interactive checkbox-based preset selector UI component. Use a more specific title that highlights the core feature, such as 'feat(onboard): add interactive preset selector with keyboard navigation' or 'feat(onboard): add presetsCheckboxSelector TUI for policy presets'.
✅ Passed checks (2 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Docstring Coverage ✅ Passed Docstring coverage is 80.00% which is sufficient. The required threshold is 80.00%.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests

Comment @coderabbitai help to get the list of available commands and usage tips.

Copy link
Copy Markdown
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 2

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (1)
bin/lib/onboard.js (1)

3355-3370: ⚠️ Potential issue | 🟠 Major

Apply only newly selected presets, through the same guarded path as the other branches.

initialSelected is seeded from applied, so accepting the default selection on a reused sandbox will call policies.applyPreset again for presets that are already active. This block also bypasses the existing Unimplemented / transient failure handling, so those duplicate writes now go straight to an exception.

Suggested fix
   const knownNames = new Set(allPresets.map((p) => p.name));
   const initialSelected = [
     ...applied.filter((name) => knownNames.has(name)),
     ...suggestions.filter((name) => knownNames.has(name) && !applied.includes(name)),
   ];
   const interactiveChoice = await presetsCheckboxSelector(allPresets, initialSelected);
+  const appliedSet = new Set(applied);
 
   if (onSelection) onSelection(interactiveChoice);
   if (!waitForSandboxReady(sandboxName)) {
     console.error(`  Sandbox '${sandboxName}' was not ready for policy application.`);
     process.exit(1);
   }
 
   for (const name of interactiveChoice) {
-    policies.applyPreset(sandboxName, name);
+    if (appliedSet.has(name)) continue;
+    for (let attempt = 0; attempt < 3; attempt += 1) {
+      try {
+        policies.applyPreset(sandboxName, name);
+        break;
+      } catch (err) {
+        const message = err && err.message ? err.message : String(err);
+        if (message.includes("Unimplemented")) {
+          console.error("  OpenShell policy updates are not supported by this gateway build.");
+          console.error("  This is a known issue tracked in NemoClaw `#536`.");
+          throw err;
+        }
+        if (!message.includes("sandbox not found") || attempt === 2) {
+          throw err;
+        }
+        sleep(2);
+      }
+    }
   }
   return interactiveChoice;
 }
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@bin/lib/onboard.js` around lines 3355 - 3370, interactiveChoice currently
includes presets already in applied and then calls policies.applyPreset
directly, causing duplicate writes and bypassing the guarded handler for
Unimplemented/transient errors; change this to compute newlySelected =
interactiveChoice.filter(name => !applied.includes(name)) and only process those
names, and invoke them via the same guarded application path used elsewhere
(i.e., the helper/wrapper that handles Unimplemented/transient failures and
retries) rather than calling policies.applyPreset directly; keep the existing
waitForSandboxReady check and onSelection call intact.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@bin/lib/onboard.js`:
- Around line 3173-3174: Guard entering raw/interactive mode when no presets
exist: before creating the Set/starting the interactive handlers, check if
allPresets.length (n) === 0 and abort/return or show a friendly message instead
of enabling raw mode; also update any key handlers that use cursor % n or access
allPresets[cursor].name (e.g., Space/navigation handlers) to avoid
modulo-by-zero by short-circuiting when n === 0. Ensure the early exit happens
prior to enabling raw mode so the terminal state is not left altered; apply the
same guard to the other interactive block that uses n (the block referenced
around the second handler group).
- Around line 3177-3183: The preset-listing currently writes raw ANSI escapes to
stdout and only checks process.stdin.isTTY; change the guard so the redraw UI
runs only when both process.stdin.isTTY and process.stdout.isTTY are true, and
honor the existing USE_COLOR (or equivalent color flag) instead of emitting
hardcoded green escapes—i.e., compute marker using the color helper only when
USE_COLOR is true (fall back to plain "[✓]" or "[ ]" without escapes when false)
and avoid writing any cursor-control/color codes when stdout is not a TTY; apply
the same change to the other similar block that renders policy presets (the code
using allPresets, selected, and marker).

---

Outside diff comments:
In `@bin/lib/onboard.js`:
- Around line 3355-3370: interactiveChoice currently includes presets already in
applied and then calls policies.applyPreset directly, causing duplicate writes
and bypassing the guarded handler for Unimplemented/transient errors; change
this to compute newlySelected = interactiveChoice.filter(name =>
!applied.includes(name)) and only process those names, and invoke them via the
same guarded application path used elsewhere (i.e., the helper/wrapper that
handles Unimplemented/transient failures and retries) rather than calling
policies.applyPreset directly; keep the existing waitForSandboxReady check and
onSelection call intact.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

Run ID: ed00fcb6-fa42-4864-b420-85268f03e30b

📥 Commits

Reviewing files that changed from the base of the PR and between 5bd75cc and 196bc0b.

📒 Files selected for processing (1)
  • bin/lib/onboard.js

Copy link
Copy Markdown
Contributor

@ericksoa ericksoa left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Nice work on this UX upgrade, @cheese-head! The TUI checkbox selector is a big improvement over the old text-based flow, and the vim-style j/k bindings are a thoughtful touch. A few things I noticed while reviewing:

Bugs

  1. Zero-presets crash — If listPresets() returns an empty array, n === 0 causes modulo-by-zero (NaN cursor), a TypeError on allPresets[cursor].name, and leaves the terminal stuck in raw mode. Would a guard at the top of the function make sense here?

    if (n === 0) {
      console.log("  No policy presets are available.");
      return [];
    }
  2. removeAllListeners("data") is aggressive — This removes all data listeners on stdin, not just yours. If anything else has attached a listener, it gets clobbered. What do you think about storing the handler reference and using removeListener("data", onData) instead?

  3. No raw-mode safety net — If the process receives SIGTERM while in raw mode, the terminal stays corrupted. Would it be worth wrapping in try/catch or adding a signal handler that calls cleanup?

  4. Non-TTY empty input gives no feedback — The old code printed "Skipping policy presets." when the user opted out. Now returning [] works functionally but the user gets silence. Intentional?

  5. Non-TTY fallback silently drops unknown names — If someone typos a preset name in a piped scenario, it's filtered out with no warning. Worth logging unrecognized names?

Style

  1. Hardcoded ANSI escapes bypass USE_COLOR — The file already defines USE_COLOR respecting NO_COLOR, but the new checkmarks hardcode \x1b[32m. Could these be gated on USE_COLOR?

  2. Only stdin.isTTY is checked — If stdin is a TTY but stdout is redirected (nemoclaw onboard | tee log.txt), the cursor-control escapes will appear as garbage. Might be worth checking both?

Testing

No tests yet (acknowledged in the PR). The TUI is inherently hard to test, but the non-TTY fallback and renderLines logic could be extracted and covered fairly easily.

Overall this is a solid improvement — mainly just needs the zero-presets guard and the raw-mode safety before merge. 👍

@wscurran wscurran added priority: medium Issue that should be addressed in upcoming releases NemoClaw CLI Use this label to identify issues with the NemoClaw command-line interface (CLI). enhancement: feature Use this label to identify requests for new capabilities in NemoClaw. enhancement: ui Use this label to identify requests to improve NemoClaw web interface. labels Apr 4, 2026
@wscurran
Copy link
Copy Markdown
Contributor

wscurran commented Apr 4, 2026

✨ Thanks for submitting this pull request, which proposes a way to improve the onboarding experience by adding an interactive preset selector with keyboard navigation and auto-detection.

cheese-head and others added 2 commits April 6, 2026 19:30
Copy link
Copy Markdown
Contributor

@ericksoa ericksoa left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Really nice work on this, @cheese-head! The TUI selector is a big step up from the old Y/n/list flow, and I appreciate you addressing all the feedback from the first round — the zero-presets guard, SIGTERM cleanup, USE_COLOR gating, and the newlySelected retry logic all look solid.

A few small nits that don't need to block this PR — would you mind filing an issue to track them as follow-ups?

  • The [resume] path (line ~3529) doesn't have the retry/Unimplemented guard that the interactive and non-interactive paths now have
  • process.exit(1) on Ctrl+C could be process.kill(process.pid, 'SIGINT') after cleanup for more conventional signal propagation
  • renderLines could be extracted and unit-tested to get some coverage on the TUI rendering without needing a real TTY
  • _setupPolicies is dead code that could be removed in a cleanup pass

Approving as-is — thanks for the contribution! 👍

@ericksoa ericksoa changed the title feat(onboard): Onboard presets feat(onboard): onboard presets Apr 6, 2026
Signed-off-by: Aaron Erickson <aerickson@nvidia.com>
@ericksoa ericksoa merged commit d712f53 into NVIDIA:main Apr 6, 2026
10 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

enhancement: feature Use this label to identify requests for new capabilities in NemoClaw. enhancement: ui Use this label to identify requests to improve NemoClaw web interface. NemoClaw CLI Use this label to identify issues with the NemoClaw command-line interface (CLI). priority: medium Issue that should be addressed in upcoming releases

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants