Skip to content

refactor(cli): extract pure functions from onboard.js to typed TypeScript modules#1240

Merged
cv merged 7 commits intomainfrom
cv/extract-onboard-pure-fns
Apr 1, 2026
Merged

refactor(cli): extract pure functions from onboard.js to typed TypeScript modules#1240
cv merged 7 commits intomainfrom
cv/extract-onboard-pure-fns

Conversation

@cv
Copy link
Copy Markdown
Contributor

@cv cv commented Apr 1, 2026

Summary

  • Extract ~210 lines of pure, side-effect-free functions from the 3,800-line onboard.js into 5 typed TypeScript modules under src/lib/:
    • gateway-state.ts — gateway/sandbox state classification from openshell output
    • validation.ts — failure classification, API key validation, model ID checks
    • url-utils.ts — URL normalization, text compaction, env formatting
    • build-context.ts — Docker build context filtering, recovery hints
    • dashboard.ts — dashboard URL resolution and construction
  • Add 56 co-located unit tests (src/lib/*.test.ts) for the extracted modules
  • Set up CLI TypeScript compilation: tsconfig.src.json compiles src/dist/ as CJS
  • onboard.js imports from compiled dist/lib/ output — transparent to callers
  • Pre-commit hook updated to build TS and include dist/lib/ in coverage

These functions are not touched by any #924 blocker PR (#781, #782, #819, #672, #634, #890), so this extraction is safe to land immediately.

Test plan

  • 598 CLI tests pass (542 existing + 56 new)
  • tsc -p tsconfig.src.json compiles cleanly
  • tsc -p tsconfig.cli.json type-checks cleanly
  • tsc -p jsconfig.json type-checks cleanly
  • Coverage ratchet passes with dist/lib/ included

Closes #1237. Relates to #924 (shell consolidation).

🤖 Generated with Claude Code

Summary by CodeRabbit

  • New Features

    • Improved sandbox-creation recovery hints and targeted remediation commands.
    • Smarter dashboard URL resolution and control-UI URL construction.
    • New preflight checks for port, memory, and swap with safer handling and dry-run support.
  • Bug Fixes

    • More accurate gateway and sandbox state detection.
    • Enhanced classification of validation/apply failures and safer model/key validation.
    • Better provider URL normalization and loopback handling.
  • Tests

    • Added comprehensive tests covering new utilities and preflight logic.
  • Chores

    • CLI now builds before CLI tests; CI and commit hooks updated to run the CLI build.

cv and others added 3 commits April 1, 2026 01:05
…ript modules

Move ~210 lines of pure, side-effect-free functions from the 3,800-line
onboard.js into five typed TypeScript modules under src/lib/:

- gateway-state.ts: gateway/sandbox state classification
- validation.ts: failure classification, API key validation, model ID checks
- url-utils.ts: URL normalization, text compaction, env formatting
- build-context.ts: Docker build context filtering, recovery hints
- dashboard.ts: dashboard URL resolution and construction

Infrastructure:
- tsconfig.src.json compiles src/ → dist/ as CJS (dist/ already gitignored)
- tsconfig.cli.json updated to type-check src/
- npm run build:cli added to package.json
- Pre-commit test-cli hook builds TS and includes dist/lib/ in coverage

onboard.js imports from compiled dist/lib/ output. All 542 CLI tests pass.
No user-facing behavior changes.

Closes #1237. Relates to #924 (shell consolidation).

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
56 new tests across three co-located test files:

- src/lib/validation.test.ts: classifyValidationFailure, classifyApplyFailure,
  classifySandboxCreateFailure, validateNvidiaApiKeyValue, isSafeModelId
- src/lib/dashboard.test.ts: resolveDashboardForwardTarget, buildControlUiUrls
- src/lib/url-utils.test.ts: compactText, stripEndpointSuffix,
  normalizeProviderBaseUrl, isLoopbackHostname, formatEnvAssignment,
  parsePolicyPresetEnv

vitest.config.ts updated to include src/**/*.test.ts in the CLI project.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
The existing callsite in printDashboard calls buildControlUiUrls()
with no arguments when no token is available.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
@coderabbitai
Copy link
Copy Markdown
Contributor

coderabbitai bot commented Apr 1, 2026

📝 Walkthrough

Walkthrough

Extracted multiple pure helper functions from bin/lib/onboard.js into typed TypeScript modules under src/lib/, added corresponding tests, introduced a CLI TypeScript build step and tsconfigs, updated CI/pre-commit hooks to build the CLI, and replaced several runtime JS shims to re-export compiled dist/lib/* outputs.

Changes

Cohort / File(s) Summary
Build & CI Configs
\.pre-commit-config\.yaml, package\.json, .github/actions/basic-checks/action.yaml, tsconfig\.src\.json, tsconfig\.cli\.json, vitest\.config\.ts
Added build:cli script and CI step; pre-commit hooks now run npm run build:cli and expanded hook matchers to include src/; added tsconfig.src.json; broadened tsconfig.cli.json include; Vitest CLI project now includes src/**/*.test.ts.
CLI Entry Points (onboard)
bin/lib/onboard.js
Removed many inline helper implementations and now imports those helpers from compiled dist/lib/* modules; onboarding orchestration continues to call the same-named functions via imports.
CLI Entry Points (preflight shim)
bin/lib/preflight.js
Replaced in-file implementation with a runtime re-export of ../../dist/lib/preflight, delegating all preflight behavior to compiled output.
Gateway & Sandbox State
src/lib/gateway-state.ts
New TS module exporting gateway/sandbox classification helpers and types (e.g., isSandboxReady, isGatewayHealthy, getGatewayReuseState, getSandboxStateFromOutputs).
Validation & Failure Classification
src/lib/validation.ts, src/lib/validation.test.ts
New validation and failure-classification utilities (classifyValidationFailure, classifyApplyFailure, classifySandboxCreateFailure, validateNvidiaApiKeyValue, isSafeModelId) with tests covering branching and regex-based classification.
URL & String Utilities
src/lib/url-utils.ts, src/lib/url-utils.test.ts
New pure string/URL helpers (compactText, stripEndpointSuffix, normalizeProviderBaseUrl, isLoopbackHostname, formatEnvAssignment, parsePolicyPresetEnv) with tests for normalization and edge cases.
Build Context & Recovery
src/lib/build-context.ts
New helpers for Docker build-context path filtering and copying (shouldIncludeBuildContextPath, copyBuildContextDir) and printSandboxCreateRecoveryHints to classify sandbox-create failures and emit recovery guidance.
Dashboard & Control UI
src/lib/dashboard.ts, src/lib/dashboard.test.ts
New functions to resolve dashboard forward targets and construct control UI URLs (resolveDashboardForwardTarget, buildControlUiUrls) with tests for loopback detection, token fragments, env var inclusion, and deduplication.
Preflight Implementation & Tests
src/lib/preflight.ts, src/lib/preflight.test.ts, test/preflight.test.js (removed)
Added a new dependency-injection-friendly preflight implementation (port probing, memory/swap detection and ensureSwap) with comprehensive tests; removed the old test/preflight.test.js suite.

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~45 minutes

Poem

🐇 I nudged the big file into tidy rows,

helpers hopped home where TypeScript grows.
Tests on guard, builds hum bright,
compiled bits now take the flight.
🥕✨

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 26.42% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (4 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The PR title accurately summarizes the main change: extracting pure functions from onboard.js into typed TypeScript modules in src/lib/.
Linked Issues check ✅ Passed All five extraction groups from #1237 are implemented: gateway-state, validation, url-utils, build-context, and dashboard modules with co-located tests. The changes meet the stated objectives of extracting pure functions, maintaining CommonJS compilation via tsconfig.src.json, and updating tooling to support the build.
Out of Scope Changes check ✅ Passed All changes are directly related to extracting pure functions and supporting the TypeScript build infrastructure. No modifications to blocked functions or user-facing behavior; tooling updates (pre-commit hooks, TypeScript configs, vitest config, CI actions) are in scope for supporting the extraction goal.

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

✨ Finishing Touches
📝 Generate docstrings
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch cv/extract-onboard-pure-fns

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.

🧹 Nitpick comments (1)
src/lib/url-utils.ts (1)

60-65: Consider stricter parameter typing or explicit union type.

The parameter value is typed as string but the implementation handles null/undefined via (value || ""). This defensive coding is fine, but the type could be string | null | undefined to accurately reflect the runtime handling.

💡 Optional: Align type with runtime behavior
-export function parsePolicyPresetEnv(value: string): string[] {
+export function parsePolicyPresetEnv(value: string | null | undefined): string[] {
   return (value || "")
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/lib/url-utils.ts` around lines 60 - 65, The function parsePolicyPresetEnv
declares its parameter as string but defensively handles null/undefined via
(value || ""); change the parameter type to string | null | undefined in
parsePolicyPresetEnv to match runtime behavior, update any call sites that rely
on strict string typing if necessary, and run type checks to ensure no
additional casts are required; keep the internal implementation (value || "")
as-is so behavior remains unchanged.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Nitpick comments:
In `@src/lib/url-utils.ts`:
- Around line 60-65: The function parsePolicyPresetEnv declares its parameter as
string but defensively handles null/undefined via (value || ""); change the
parameter type to string | null | undefined in parsePolicyPresetEnv to match
runtime behavior, update any call sites that rely on strict string typing if
necessary, and run type checks to ensure no additional casts are required; keep
the internal implementation (value || "") as-is so behavior remains unchanged.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

Run ID: 9c626f2c-ae88-40e4-a29c-54bd11b300ab

📥 Commits

Reviewing files that changed from the base of the PR and between e06c147 and eec919d.

📒 Files selected for processing (14)
  • .pre-commit-config.yaml
  • bin/lib/onboard.js
  • package.json
  • src/lib/build-context.ts
  • src/lib/dashboard.test.ts
  • src/lib/dashboard.ts
  • src/lib/gateway-state.ts
  • src/lib/url-utils.test.ts
  • src/lib/url-utils.ts
  • src/lib/validation.test.ts
  • src/lib/validation.ts
  • tsconfig.cli.json
  • tsconfig.src.json
  • vitest.config.ts

cv and others added 3 commits April 1, 2026 10:36
The tsc-js pre-push hook (jsconfig.json) type-checks bin/lib/onboard.js
which requires dist/lib/ to exist. CI was not running npm run build:cli
before the prek checks, causing TS2307 "Cannot find module" errors.

- Add npm run build:cli step to .github/actions/basic-checks
- Update tsc-js hook to run build:cli before tsc

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
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.

🧹 Nitpick comments (1)
.github/actions/basic-checks/action.yaml (1)

37-39: Step placement is correct; build artifacts will exist before hooks run.

The new step ensures dist/lib/ artifacts are compiled before prek hooks execute. This provides clearer error isolation in CI if the TypeScript build fails.

Minor observation: the tsc-js and test-cli hooks (lines 197-204 and 228-234 in .pre-commit-config.yaml) each include npm run build:cli && in their entry commands, resulting in the CLI TypeScript compiling three times per CI run. Consider either removing the explicit build from the hook entries (since this action step guarantees artifacts exist) or accepting the redundancy for the defense-in-depth benefit when hooks run locally outside CI.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In @.github/actions/basic-checks/action.yaml around lines 37 - 39, The CI now
builds CLI TypeScript artifacts in the action step named "Build CLI TypeScript
modules", so remove the redundant "npm run build:cli &&" prefixes from the
pre-commit hook entry commands for the tsc-js and test-cli hooks in
.pre-commit-config.yaml (or alternatively keep them if you prefer
defense-in-depth); update the hook entries for the tsc-js and test-cli hooks to
run only their checks (e.g., remove the explicit build invocation) so the CLI is
not compiled three times per CI run.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Nitpick comments:
In @.github/actions/basic-checks/action.yaml:
- Around line 37-39: The CI now builds CLI TypeScript artifacts in the action
step named "Build CLI TypeScript modules", so remove the redundant "npm run
build:cli &&" prefixes from the pre-commit hook entry commands for the tsc-js
and test-cli hooks in .pre-commit-config.yaml (or alternatively keep them if you
prefer defense-in-depth); update the hook entries for the tsc-js and test-cli
hooks to run only their checks (e.g., remove the explicit build invocation) so
the CLI is not compiled three times per CI run.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

Run ID: 29f6e4dc-0801-45f9-9317-c2d1d4cf19d6

📥 Commits

Reviewing files that changed from the base of the PR and between eb16b9e and d3e2c33.

📒 Files selected for processing (2)
  • .github/actions/basic-checks/action.yaml
  • .pre-commit-config.yaml
🚧 Files skipped from review as they are similar to previous changes (1)
  • .pre-commit-config.yaml

@cv cv enabled auto-merge (squash) April 1, 2026 18:37
## Summary

- Convert `bin/lib/preflight.js` (357 lines) to `src/lib/preflight.ts`
with full type definitions
- Typed interfaces for all opts objects and return types:
`PortProbeResult`, `MemoryInfo`, `SwapResult`, `CheckPortOpts`,
`GetMemoryInfoOpts`, `EnsureSwapOpts`
- Extract `parseLsofLines` helper to reduce duplication in
`checkPortAvailable`
- Incorporate #1227 fix: `sudo` → `sudo -n` (non-interactive) for lsof
retry
- `bin/lib/preflight.js` becomes a thin re-export shim — existing
consumers unaffected
- Co-locate tests: `test/preflight.test.js` →
`src/lib/preflight.test.ts`
- Add real net probe tests (EADDRINUSE detection on occupied ports)
- Fix all co-located test imports to use `dist/` paths for coverage
attribution
- Add targeted dashboard/validation branch tests to maintain ratchet

Stacked on #1240. Not touched by any #924 blocker PR.

## Test plan

- [x] 612 CLI tests pass (601 existing + 11 new)
- [x] `tsc -p tsconfig.src.json` compiles cleanly
- [x] `tsc -p tsconfig.cli.json` type-checks cleanly
- [x] `tsc -p jsconfig.json` type-checks cleanly (the pre-push check
that caught the union issue)
- [x] Coverage ratchet passes

Relates to #924 (shell consolidation). Supersedes #1227.

🤖 Generated with [Claude Code](https://claude.com/claude-code)

---------

Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
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: 1

🧹 Nitpick comments (1)
src/lib/preflight.test.ts (1)

318-326: Consider strengthening the default threshold test.

The test name says "uses default 12000 MB threshold" but doesn't actually verify that value. To truly test the default, consider using a memory value between potential defaults (e.g., 10000 MB which is below 12000 but above 0):

💡 Suggested test improvement
  it("uses default 12000 MB threshold when minTotalMB is undefined", () => {
+   // 10000 MB is below the 12000 default, so swap should be offered
+   const resultBelowDefault = ensureSwap(undefined, {
+     platform: "linux",
+     memoryInfo: { totalRamMB: 10000, totalSwapMB: 0, totalMB: 10000 },
+     dryRun: true,
+     swapfileExists: false,
+   });
+   expect(resultBelowDefault.swapCreated).toBe(true);
+
+   // 16000 MB exceeds the default, so no swap needed
    const result = ensureSwap(undefined, {
      platform: "linux",
      memoryInfo: { totalRamMB: 16000, totalSwapMB: 0, totalMB: 16000 },
    });
    expect(result.ok).toBe(true);
    expect(result.swapCreated).toBe(false);
-   expect(result.totalMB).toBe(16000);
  });
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/lib/preflight.test.ts` around lines 318 - 326, Update the test for
ensureSwap to actually validate the default 12000 MB threshold by using a RAM
value between possible thresholds (e.g., set memoryInfo.totalRamMB to 10000) so
the default should trigger swap creation; assert that ensureSwap(...) returns ok
true and swapCreated true (and any other relevant fields your implementation
fills) to prove the default 12000 MB threshold was applied.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@src/lib/preflight.ts`:
- Around line 359-407: The code computes o.interactive but never uses it; before
attempting to create swap, check o.interactive (the interactive flag on the
local options object passed into ensureSwap) and if it's false return a
SwapResult that skips creation (e.g., { ok: true, totalMB: mem.totalMB,
swapCreated: false, reason: "non-interactive mode, skipping swap creation" })
instead of calling createSwapfile; add this check after diskSpaceResult is
computed (before calling createSwapfile(mem)) so non-interactive environments do
not try to create a swapfile.

---

Nitpick comments:
In `@src/lib/preflight.test.ts`:
- Around line 318-326: Update the test for ensureSwap to actually validate the
default 12000 MB threshold by using a RAM value between possible thresholds
(e.g., set memoryInfo.totalRamMB to 10000) so the default should trigger swap
creation; assert that ensureSwap(...) returns ok true and swapCreated true (and
any other relevant fields your implementation fills) to prove the default 12000
MB threshold was applied.
🪄 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: ece9092d-d2cd-4f7e-89ba-a9b91f8d7e8e

📥 Commits

Reviewing files that changed from the base of the PR and between d3e2c33 and 2ac85ce.

📒 Files selected for processing (7)
  • bin/lib/preflight.js
  • src/lib/dashboard.test.ts
  • src/lib/preflight.test.ts
  • src/lib/preflight.ts
  • src/lib/url-utils.test.ts
  • src/lib/validation.test.ts
  • test/preflight.test.js
💤 Files with no reviewable changes (1)
  • test/preflight.test.js
✅ Files skipped from review due to trivial changes (3)
  • src/lib/url-utils.test.ts
  • src/lib/dashboard.test.ts
  • src/lib/validation.test.ts

Comment on lines +359 to +407
export function ensureSwap(minTotalMB?: number, opts: EnsureSwapOpts = {}): SwapResult {
const o = {
platform: process.platform as NodeJS.Platform,
memoryInfo: null as MemoryInfo | null,
swapfileExists: fs.existsSync("/swapfile"),
dryRun: false,
interactive: process.stdout.isTTY && !process.env.NEMOCLAW_NON_INTERACTIVE,
getMemoryInfoImpl: getMemoryInfo,
...opts,
};
const threshold = minTotalMB ?? 12000;

if (o.platform !== "linux") {
return { ok: true, totalMB: 0, swapCreated: false };
}

const mem = o.memoryInfo ?? o.getMemoryInfoImpl({ platform: o.platform });
if (!mem) {
return { ok: false, reason: "could not read memory info" };
}

if (mem.totalMB >= threshold) {
return { ok: true, totalMB: mem.totalMB, swapCreated: false };
}

if (o.dryRun) {
if (o.swapfileExists) {
return {
ok: true,
totalMB: mem.totalMB,
swapCreated: false,
reason: "/swapfile already exists",
};
}
return { ok: true, totalMB: mem.totalMB, swapCreated: true };
}

const existingSwapResult = getExistingSwapResult(mem);
if (existingSwapResult) {
return existingSwapResult;
}

const diskSpaceResult = checkSwapDiskSpace();
if (diskSpaceResult) {
return diskSpaceResult;
}

return createSwapfile(mem);
}
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟡 Minor

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
# Check if the original onboard.js had interactive checks for swap creation
rg -n -B3 -A3 'interactive.*swap|swap.*interactive' bin/lib/onboard.js

Repository: NVIDIA/NemoClaw

Length of output: 41


🏁 Script executed:

# Find ensureSwap function and check if interactive is used
rg -n -B5 -A50 'ensureSwap.*interactive|interactive.*used|interactive.*passed' src/lib/preflight.ts

Repository: NVIDIA/NemoClaw

Length of output: 41


🏁 Script executed:

# Check the actual implementation of createSwapfile to see if it uses interactive
rg -n -B3 -A20 'function createSwapfile|const createSwapfile' src/lib/preflight.ts

Repository: NVIDIA/NemoClaw

Length of output: 965


🏁 Script executed:

# Check how ensureSwap is called in the codebase
rg -n -B3 -A3 'ensureSwap\(' --type ts --type js

Repository: NVIDIA/NemoClaw

Length of output: 4073


🏁 Script executed:

# Check if there's any pattern of interactive mode checks in preflight.ts
rg -n 'interactive' src/lib/preflight.ts

Repository: NVIDIA/NemoClaw

Length of output: 218


The interactive option is computed but never used to gate swap creation.

Line 365 computes interactive: process.stdout.isTTY && !process.env.NEMOCLAW_NON_INTERACTIVE, but this value is never checked in ensureSwap before calling createSwapfile. If non-interactive mode (e.g., CI, headless environments) should skip swap creation, the check is missing.

Suggested fix
   if (mem.totalMB >= threshold) {
     return { ok: true, totalMB: mem.totalMB, swapCreated: false };
   }

+  if (!o.interactive) {
+    return {
+      ok: true,
+      totalMB: mem.totalMB,
+      swapCreated: false,
+      reason: "running in non-interactive mode, not creating swap",
+    };
+  }
+
   if (o.dryRun) {
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/lib/preflight.ts` around lines 359 - 407, The code computes o.interactive
but never uses it; before attempting to create swap, check o.interactive (the
interactive flag on the local options object passed into ensureSwap) and if it's
false return a SwapResult that skips creation (e.g., { ok: true, totalMB:
mem.totalMB, swapCreated: false, reason: "non-interactive mode, skipping swap
creation" }) instead of calling createSwapfile; add this check after
diskSpaceResult is computed (before calling createSwapfile(mem)) so
non-interactive environments do not try to create a swapfile.

Copy link
Copy Markdown
Collaborator

@brandonpelfrey brandonpelfrey left a comment

Choose a reason for hiding this comment

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

I don't see any new issues here. Definitely a positive move towards TS.

@cv cv merged commit 3f923a4 into main Apr 1, 2026
10 checks passed
@jyaunches
Copy link
Copy Markdown
Contributor

PR Review: cv/extract-onboard-pure-fns

Nice extraction — scoping this to pure/near-pure functions and leaving the stateful parts of onboard.js alone is the right sequencing. The CJS shim pattern, DI via opts objects, and CI alignment are all clean. A few things to address:


🔴 Blockers

1. Duplicated GATEWAY_NAME constantgateway-state.ts:10 hardcodes const GATEWAY_NAME = "nemoclaw", and onboard.js:54 defines the same. Two sources of truth for one magic string. If someone changes it in one place, the other silently diverges.

Suggestion: export it from gateway-state.ts and have onboard.js import from the compiled output, or extract to a shared constants module.

2. preflight.ts:17require("../../bin/lib/runner") crosses the layer boundary — The TS module reaches back into bin/lib/ for runCapture, creating bin/lib/preflight.jsdist/lib/preflight.jsbin/lib/runner.js. This couples the "pure" TS layer to a CJS module that shells out, and breaks the no-I/O contract for preflight.ts.

The module already does DI extensively (probeImpl, lsofOutput, meminfoContent, etc.) — the runCapture calls are the remaining un-injectable I/O paths. Could inject runCapture via opts too, or extract it to its own TS module as a prerequisite. At minimum a TODO comment explaining the migration path would be good.


🟡 Warnings

3. No tests for gateway-state.ts or build-context.ts — 10 exported functions in gateway-state.ts (including the 5-state getGatewayReuseState state machine) and 3 in build-context.ts, all with zero tests. These are pure classifiers — easiest things to test. Would be good to add at least happy-path coverage before merge.

4. preflight.test.ts:127 — mixed import/require() — Top of file uses ESM import, then one describe block uses require("../../dist/lib/preflight"). Will break under strict ESM. Should use top-level import or dynamic import().

5. vitest.config.tsbuild:cli is now a silent prerequisite — Adding src/**/*.test.ts to the cli project means anyone who forgets npm run build:cli gets cryptic "module not found" errors. CI handles it, but local dev friction goes up. Consider a pretest script in package.json.

6. build-context.tsprintSandboxCreateRecoveryHints does I/O — Calls console.error(), making this a mixed-purity module despite the "pure functions" framing. Worth a comment or split.


🔵 Suggestions

  • Barrel export (src/lib/index.ts) would give onboard.js one import instead of five.
  • The EndpointFlavor type in url-utils.ts overlaps with PR refactor(cli): migrate inference-config.js to TypeScript #1265 (inference-config migration) — worth coordinating to avoid duplicate definitions.
  • The ANSI regex extraction in gateway-state.ts is good — consider exporting it since onboard.js still has inline copies.

Overall this is solid groundwork. The tsconfig.src.json pattern sets a clean template for #1265 and future migrations. Tests are behavioral and well-structured. Merge after fixing the GATEWAY_NAME duplication and ideally adding gateway-state tests. 👍

@cv cv deleted the cv/extract-onboard-pure-fns branch April 1, 2026 19:51
@cv cv restored the cv/extract-onboard-pure-fns branch April 1, 2026 20:04
@wscurran wscurran added NemoClaw CLI Use this label to identify issues with the NemoClaw command-line interface (CLI). refactor This is a refactor of the code and/or architecture. labels Apr 1, 2026
ericksoa added a commit that referenced this pull request Apr 1, 2026
PR #1240 replaced bin/lib/ CJS modules with thin shims that re-export
from dist/lib/, but install.sh was never updated to run `npm run
build:cli`. Every fresh install now crashes on `nemoclaw onboard` with
"Cannot find module '../../dist/lib/preflight'".

Add the missing build:cli step to both install paths (local source and
GitHub clone).

Signed-off-by: Aaron Erickson <aerickson@nvidia.com>
cv pushed a commit that referenced this pull request Apr 1, 2026
## Summary
- **install.sh missing build:cli**: PR #1240 replaced `bin/lib/` CJS
modules with shims that re-export from `dist/lib/`, but `install.sh`
never ran `npm run build:cli`. Every fresh install crashed on `nemoclaw
onboard` with `Cannot find module '../../dist/lib/preflight'`
- **E2E destroy verification**: The cleanup phase used `nemoclaw list`
to verify a sandbox was gone, but `list` triggers gateway recovery which
can restart a destroyed gateway and re-import the sandbox. Now checks
the registry file directly instead
- Uses `--if-present` so installs targeting older tags without
`build:cli` don't break

## Test plan
- [ ] Run `install.sh` on a clean VM and verify `nemoclaw onboard` no
longer crashes
- [ ] Verify `dist/lib/preflight.js` exists after install completes
- [ ] Nightly E2E cloud-e2e passes the destroy verification phase

---------

Signed-off-by: Aaron Erickson <aerickson@nvidia.com>
cv added a commit that referenced this pull request Apr 2, 2026
## Summary
- Convert `bin/lib/runtime-recovery.js` (84 lines) to
`src/lib/runtime-recovery.ts`
- Typed `StateClassification` interface for sandbox/gateway state
- 5 pure classifiers/parsers + 1 I/O function (`getRecoveryCommand`)
- Co-locate tests with additional edge cases

Stacked on #1240. 617 CLI tests pass. Coverage ratchet passes.

Relates to #924 (shell consolidation).

🤖 Generated with [Claude Code](https://claude.com/claude-code)

<!-- This is an auto-generated comment: release notes by coderabbit.ai
-->
## Summary by CodeRabbit

* **New Features**
* Improved runtime recovery logic to better parse CLI output and
determine recovery actions, including more reliable sandbox/gateway
state classification and recovery command selection.

* **Bug Fixes**
* Correctly treats "Disconnected" gateway status as inactive and handles
error/empty lines when listing sandboxes.

* **Tests**
* Expanded test coverage for edge cases, malformed input, and empty
states.

* **Refactor**
  * Runtime recovery helpers consolidated to a shared compiled module.
<!-- end of auto-generated comment: release notes by coderabbit.ai -->

---------

Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
cv added a commit that referenced this pull request Apr 2, 2026
## Summary
- Convert `bin/lib/local-inference.js` (228 lines) to
`src/lib/local-inference.ts`
- Typed interfaces: `ValidationResult`, `GpuInfo`, `RunCaptureFn`
- 9 pure functions + 4 with DI-injected `runCapture`
- Co-locate tests (30 tests)

Stacked on #1240. 616 CLI tests pass. Coverage ratchet passes.

Relates to #924 (shell consolidation).

🤖 Generated with [Claude Code](https://claude.com/claude-code)

<!-- This is an auto-generated comment: release notes by coderabbit.ai
-->
## Summary by CodeRabbit

* **New Features**
* Added local inference provider validation and connectivity diagnostics
for vllm-local and ollama-local.
* Implemented Ollama model discovery, warmup/probe commands, and
intelligent model selection based on GPU memory.
* Added health-check and model readiness checks to surface configuration
or networking issues earlier.

* **Tests**
* Enhanced test coverage for provider validation, model detection, and
unknown-provider handling; updated test targets accordingly.
<!-- end of auto-generated comment: release notes by coderabbit.ai -->

---------

Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
cv added a commit that referenced this pull request Apr 2, 2026
## Summary
- Convert `bin/lib/nim.js` (259 lines) to `src/lib/nim.ts`
- Typed interfaces: `NimModel`, `GpuDetection`, `NimStatus`
- 4 pure functions + subprocess-heavy I/O functions (docker, nvidia-smi)
- Co-locate tests (16 tests) with updated mock pattern for dist/ paths

Stacked on #1240. 615 CLI tests pass. Coverage ratchet passes.

Relates to #924 (shell consolidation).

🤖 Generated with [Claude Code](https://claude.com/claude-code)

<!-- This is an auto-generated comment: release notes by coderabbit.ai
-->
## Summary by CodeRabbit

* **New Features**
  * GPU detection with capability assessment for NIM containers
* Container lifecycle management (start, stop, status monitoring) with
automatic health checks
  * Model enumeration and image selection for launching containers

* **Refactor**
* Simplified module surface and improved internal architecture for
maintainability

* **Tests**
* Updated test suite to align with the refactored module behavior and
stronger typings
<!-- end of auto-generated comment: release notes by coderabbit.ai -->

---------

Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
cv added a commit that referenced this pull request Apr 2, 2026
## Summary
- Convert `bin/lib/onboard-session.js` (440 lines) to
`src/lib/onboard-session.ts`
- Typed interfaces: `Session`, `StepState`, `SessionFailure`,
`LockResult`, `SessionUpdates`, `LockInfo`, `SessionMetadata`
- Full migration including fs I/O (session persistence, file locking)
and pure helpers (redaction, validation, normalization)
- Co-locate tests (14 tests) with updated cache-clearing for dist/ path

Stacked on #1240. 615 CLI tests pass. Coverage ratchet passes.

Relates to #924 (shell consolidation).

🤖 Generated with [Claude Code](https://claude.com/claude-code)

<!-- This is an auto-generated comment: release notes by coderabbit.ai
-->

## Summary by CodeRabbit

* **Refactor**
* Reorganized onboarding session management module structure and
improved distribution of the codebase. Session tracking, step-level
state management, lock handling, and sensitive data redaction
functionality have been consolidated and optimized for better
maintainability.

<!-- end of auto-generated comment: release notes by coderabbit.ai -->

---------

Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
cv added a commit that referenced this pull request Apr 2, 2026
## Summary
- Convert `bin/lib/inference-config.js` (143 lines) to
`src/lib/inference-config.ts`
- Typed interfaces: `ProviderSelectionConfig`, `GatewayInference`
- All 3 exports are pure — straightforward full conversion
- Co-locate tests: `test/inference-config.test.js` →
`src/lib/inference-config.test.ts`

Stacked on #1240. 614 CLI tests pass. Coverage ratchet passes.

Relates to #924 (shell consolidation).

🤖 Generated with [Claude Code](https://claude.com/claude-code)

<!-- This is an auto-generated comment: release notes by coderabbit.ai
-->
## Summary by CodeRabbit

* **Refactor**
* Centralized inference configuration, provider selection, and gateway
output parsing into a single consolidated module for more consistent
behavior.

* **Tests**
* Updated tests to match the consolidated module and improved assertion
patterns.

* **Chores**
* Install/prep process now includes a CLI build step during project
setup (silently handled).
<!-- end of auto-generated comment: release notes by coderabbit.ai -->

---------

Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
laitingsheng pushed a commit that referenced this pull request Apr 2, 2026
…ript modules (#1240)

## Summary

- Extract ~210 lines of pure, side-effect-free functions from the
3,800-line `onboard.js` into **5 typed TypeScript modules** under
`src/lib/`:
- `gateway-state.ts` — gateway/sandbox state classification from
openshell output
- `validation.ts` — failure classification, API key validation, model ID
checks
  - `url-utils.ts` — URL normalization, text compaction, env formatting
  - `build-context.ts` — Docker build context filtering, recovery hints
  - `dashboard.ts` — dashboard URL resolution and construction
- Add **56 co-located unit tests** (`src/lib/*.test.ts`) for the
extracted modules
- Set up CLI TypeScript compilation: `tsconfig.src.json` compiles `src/`
→ `dist/` as CJS
- `onboard.js` imports from compiled `dist/lib/` output — transparent to
callers
- Pre-commit hook updated to build TS and include `dist/lib/` in
coverage

These functions are **not touched by any #924 blocker PR** (#781, #782,
#819, #672, #634, #890), so this extraction is safe to land immediately.

## Test plan

- [x] 598 CLI tests pass (542 existing + 56 new)
- [x] `tsc -p tsconfig.src.json` compiles cleanly
- [x] `tsc -p tsconfig.cli.json` type-checks cleanly
- [x] `tsc -p jsconfig.json` type-checks cleanly
- [x] Coverage ratchet passes with `dist/lib/` included

Closes #1237. Relates to #924 (shell consolidation).

🤖 Generated with [Claude Code](https://claude.com/claude-code)

<!-- This is an auto-generated comment: release notes by coderabbit.ai
-->
## Summary by CodeRabbit

* **New Features**
* Improved sandbox-creation recovery hints and targeted remediation
commands.
  * Smarter dashboard URL resolution and control-UI URL construction.

* **Bug Fixes**
  * More accurate gateway and sandbox state detection.
* Enhanced classification of validation/apply failures and safer
model/key validation.
  * Better provider URL normalization and loopback handling.

* **Tests**
  * Added comprehensive tests covering new utilities.

* **Chores**
* CLI now builds before CLI tests; CI/commit hooks updated to run the
CLI build.
<!-- end of auto-generated comment: release notes by coderabbit.ai -->

---------

Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
laitingsheng pushed a commit that referenced this pull request Apr 2, 2026
## Summary
- **install.sh missing build:cli**: PR #1240 replaced `bin/lib/` CJS
modules with shims that re-export from `dist/lib/`, but `install.sh`
never ran `npm run build:cli`. Every fresh install crashed on `nemoclaw
onboard` with `Cannot find module '../../dist/lib/preflight'`
- **E2E destroy verification**: The cleanup phase used `nemoclaw list`
to verify a sandbox was gone, but `list` triggers gateway recovery which
can restart a destroyed gateway and re-import the sandbox. Now checks
the registry file directly instead
- Uses `--if-present` so installs targeting older tags without
`build:cli` don't break

## Test plan
- [ ] Run `install.sh` on a clean VM and verify `nemoclaw onboard` no
longer crashes
- [ ] Verify `dist/lib/preflight.js` exists after install completes
- [ ] Nightly E2E cloud-e2e passes the destroy verification phase

---------

Signed-off-by: Aaron Erickson <aerickson@nvidia.com>
laitingsheng pushed a commit that referenced this pull request Apr 2, 2026
## Summary
- Convert `bin/lib/runtime-recovery.js` (84 lines) to
`src/lib/runtime-recovery.ts`
- Typed `StateClassification` interface for sandbox/gateway state
- 5 pure classifiers/parsers + 1 I/O function (`getRecoveryCommand`)
- Co-locate tests with additional edge cases

Stacked on #1240. 617 CLI tests pass. Coverage ratchet passes.

Relates to #924 (shell consolidation).

🤖 Generated with [Claude Code](https://claude.com/claude-code)

<!-- This is an auto-generated comment: release notes by coderabbit.ai
-->
## Summary by CodeRabbit

* **New Features**
* Improved runtime recovery logic to better parse CLI output and
determine recovery actions, including more reliable sandbox/gateway
state classification and recovery command selection.

* **Bug Fixes**
* Correctly treats "Disconnected" gateway status as inactive and handles
error/empty lines when listing sandboxes.

* **Tests**
* Expanded test coverage for edge cases, malformed input, and empty
states.

* **Refactor**
  * Runtime recovery helpers consolidated to a shared compiled module.
<!-- end of auto-generated comment: release notes by coderabbit.ai -->

---------

Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
laitingsheng pushed a commit that referenced this pull request Apr 2, 2026
## Summary
- Convert `bin/lib/local-inference.js` (228 lines) to
`src/lib/local-inference.ts`
- Typed interfaces: `ValidationResult`, `GpuInfo`, `RunCaptureFn`
- 9 pure functions + 4 with DI-injected `runCapture`
- Co-locate tests (30 tests)

Stacked on #1240. 616 CLI tests pass. Coverage ratchet passes.

Relates to #924 (shell consolidation).

🤖 Generated with [Claude Code](https://claude.com/claude-code)

<!-- This is an auto-generated comment: release notes by coderabbit.ai
-->
## Summary by CodeRabbit

* **New Features**
* Added local inference provider validation and connectivity diagnostics
for vllm-local and ollama-local.
* Implemented Ollama model discovery, warmup/probe commands, and
intelligent model selection based on GPU memory.
* Added health-check and model readiness checks to surface configuration
or networking issues earlier.

* **Tests**
* Enhanced test coverage for provider validation, model detection, and
unknown-provider handling; updated test targets accordingly.
<!-- end of auto-generated comment: release notes by coderabbit.ai -->

---------

Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
laitingsheng pushed a commit that referenced this pull request Apr 2, 2026
## Summary
- Convert `bin/lib/nim.js` (259 lines) to `src/lib/nim.ts`
- Typed interfaces: `NimModel`, `GpuDetection`, `NimStatus`
- 4 pure functions + subprocess-heavy I/O functions (docker, nvidia-smi)
- Co-locate tests (16 tests) with updated mock pattern for dist/ paths

Stacked on #1240. 615 CLI tests pass. Coverage ratchet passes.

Relates to #924 (shell consolidation).

🤖 Generated with [Claude Code](https://claude.com/claude-code)

<!-- This is an auto-generated comment: release notes by coderabbit.ai
-->
## Summary by CodeRabbit

* **New Features**
  * GPU detection with capability assessment for NIM containers
* Container lifecycle management (start, stop, status monitoring) with
automatic health checks
  * Model enumeration and image selection for launching containers

* **Refactor**
* Simplified module surface and improved internal architecture for
maintainability

* **Tests**
* Updated test suite to align with the refactored module behavior and
stronger typings
<!-- end of auto-generated comment: release notes by coderabbit.ai -->

---------

Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
laitingsheng pushed a commit that referenced this pull request Apr 2, 2026
## Summary
- Convert `bin/lib/onboard-session.js` (440 lines) to
`src/lib/onboard-session.ts`
- Typed interfaces: `Session`, `StepState`, `SessionFailure`,
`LockResult`, `SessionUpdates`, `LockInfo`, `SessionMetadata`
- Full migration including fs I/O (session persistence, file locking)
and pure helpers (redaction, validation, normalization)
- Co-locate tests (14 tests) with updated cache-clearing for dist/ path

Stacked on #1240. 615 CLI tests pass. Coverage ratchet passes.

Relates to #924 (shell consolidation).

🤖 Generated with [Claude Code](https://claude.com/claude-code)

<!-- This is an auto-generated comment: release notes by coderabbit.ai
-->

## Summary by CodeRabbit

* **Refactor**
* Reorganized onboarding session management module structure and
improved distribution of the codebase. Session tracking, step-level
state management, lock handling, and sensitive data redaction
functionality have been consolidated and optimized for better
maintainability.

<!-- end of auto-generated comment: release notes by coderabbit.ai -->

---------

Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
laitingsheng pushed a commit that referenced this pull request Apr 2, 2026
## Summary
- Convert `bin/lib/inference-config.js` (143 lines) to
`src/lib/inference-config.ts`
- Typed interfaces: `ProviderSelectionConfig`, `GatewayInference`
- All 3 exports are pure — straightforward full conversion
- Co-locate tests: `test/inference-config.test.js` →
`src/lib/inference-config.test.ts`

Stacked on #1240. 614 CLI tests pass. Coverage ratchet passes.

Relates to #924 (shell consolidation).

🤖 Generated with [Claude Code](https://claude.com/claude-code)

<!-- This is an auto-generated comment: release notes by coderabbit.ai
-->
## Summary by CodeRabbit

* **Refactor**
* Centralized inference configuration, provider selection, and gateway
output parsing into a single consolidated module for more consistent
behavior.

* **Tests**
* Updated tests to match the consolidated module and improved assertion
patterns.

* **Chores**
* Install/prep process now includes a CLI build step during project
setup (silently handled).
<!-- end of auto-generated comment: release notes by coderabbit.ai -->

---------

Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

NemoClaw CLI Use this label to identify issues with the NemoClaw command-line interface (CLI). refactor This is a refactor of the code and/or architecture.

Projects

None yet

Development

Successfully merging this pull request may close these issues.

refactor(cli): extract pure functions from onboard.js to typed TypeScript modules

4 participants