Skip to content

Simplify CLI surface and keep nun uninstall - #9

Merged
spa5k merged 4 commits into
mainfrom
codex/cli-surface-cleanup-nun
Jun 3, 2026
Merged

spa5k merged 4 commits into
mainfrom
codex/cli-surface-cleanup-nun

Conversation

@spa5k

@spa5k spa5k commented Jun 3, 2026 •

Copy link
Copy Markdown
Collaborator

Summary

  • remove legacy interactive flows and old aliases while keeping the node shim
  • replace custom parsing/config/system helpers with standard crates and clap-generated public help/completions
  • keep nun as the uninstall command across aliases, hni uninstall, node uninstall/remove, JSR, docs, and tests

Verification

  • cargo test
  • cargo clippy --all-targets -- -D warnings

Summary by CodeRabbit

  • Breaking Changes

    • Config format/location changed to TOML at the new config path; keys now use snake_case
    • Env var renamed: HNI_FAST → HNI_FAST_MODE
    • Removed commands/aliases: nru and na; interactive selection and script-completion flows removed
  • New Features

    • New canonical aliases added (nun, nci) and updated alias set
  • Documentation

    • README updated with revised aliases, canonical command examples, and config/env docs
  • Chores

    • Updated CLI/dependency tooling and completion/help outputs

Replace legacy parsing/config helpers with standard crates, remove interactive flows and unused aliases, keep the node shim, and retain nun as the uninstall command.
@coderabbitai

coderabbitai Bot commented Jun 3, 2026 •

Copy link
Copy Markdown

Review Change Stack

Warning

Review limit reached

@spa5k, we couldn't start this review because you've reached your PR review rate limit.

More reviews will be available in 18 minutes and 55 seconds. Learn how PR review limits work.

Your organization has run out of usage credits. Purchase more in the billing tab.

⌛ How to resolve this issue?

After more reviews become available, a review can be triggered using the @coderabbitai review command as a PR comment. Alternatively, push new commits to this PR.

We recommend that you space out your commits to avoid hitting the rate limit.

🚦 How do rate limits work?

CodeRabbit enforces hourly rate limits for each developer per organization.

Our paid plans include higher PR review limits than trial, open-source, and free plans. In all cases, reviews become available again over time. During sustained high-volume PR review activity, CodeRabbit may temporarily slow when the next review becomes available.

Please see our Fair Usage Limits Policy for further information.

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: 48fad7d2-eab2-4586-8051-0357e5450e9d

📥 Commits

Reviewing files that changed from the base of the PR and between 849da87 and 4af0dd7.

⛔ Files ignored due to path filters (1)
  • .github/og-image.svg is excluded by !**/*.svg
📒 Files selected for processing (3)
  • README.md
  • dist-workspace.toml
  • docs/fast-compat.md
📝 Walkthrough

Walkthrough

This PR refactors CLI parsing to derive-based clap, migrates config loading from INI to TOML via Figment, replaces ad-hoc error strings with thiserror types, removes deprecated aliases/interactive features, updates native execution helpers and dependency list, renames HNI_FAST → HNI_FAST_MODE, and updates tests and docs accordingly.

Changes

CLI and Core System Refactoring

Layer / File(s) Summary
Configuration System Migration to TOML/Figment
src/core/config.rs, src/app/doctor.rs, tests/config_detect.rs, tests/error_contract.rs
Configuration loading switches from .hnirc INI format to config.toml TOML format via Figment providers; HNI_CONFIG_FILE remains explicit-path env var; fallback is .../hni/config.toml. Config fields use snake_case (default_package_manager, global_package_manager, fast_mode).
CLI Refactoring to Derive-based Clap
src/app/cli.rs, src/app/help.rs, src/app/completion.rs, src/app/dispatch.rs, src/app/command_registry.rs
Replaces manual clap::Command construction with derive-based structs (HniCli, HniPublicCli); centralizes help/completion via exported hni_command(); parsing maps into typed ParsedCommand variants and removes command_subcommands() helper.
Typed Error Handling with thiserror
src/core/detect.rs, src/core/resolve/detect.rs, src/core/native/plan.rs, src/app/error_report.rs
Introduces PackageManagerAvailabilityError, ResolveDetectionError, and derives FallbackReason with thiserror::Error for structured error formatting instead of inline anyhow! messages.
Remove nru (Upgrade) and na (Alias) Commands
src/app/command_registry.rs, src/core/resolve/build.rs, src/core/resolve/map.rs, src/core/resolve/mod.rs, src/features/node_shim.rs, jsr.json, jsr/shared.ts
Removes deprecated nru and na command specs, resolve handlers, and JSR subpath exports; introduces nun and nci exports and updates invocation lists.
Remove Interactive Features
src/app/commands.rs, src/features/mod.rs, src/features/interactive/*
Deletes interactive modules and their helpers (ni_search, nr_scripts, nun_select, completion) and simplifies handlers to delegate directly to resolve functions without prompts.
Library Improvements and Dependency Updates
Cargo.toml, src/core/native/bin_resolver.rs, src/core/native/deno.rs, src/core/native/exec.rs, src/core/pkg_json.rs, src/core/project.rs, src/core/util.rs, src/platform/node.rs
Updates Cargo.toml to add clap_complete, wildmatch, is_executable, thiserror, figment, semver and removes some legacy crates; switches to cross-platform executable checks, uses WildMatch for pattern matching, reuses a Tokio runtime for Deno execution, and adopts semver comparator parsing for version hints.
Environment Variable Rename (HNI_FAST → HNI_FAST_MODE)
justfile, benchmark/run.mjs, tests/support.rs, CI workflow
Renames the fast-mode environment variable across benchmarks, test targets, CI, and test helpers; updates test expectations and environment cleanup.
Comprehensive Test Updates
tests/*, README.md, aliases.json
Updates tests to invoke canonical subcommands (install, run, exec) instead of aliases, replace HNI_FAST with HNI_FAST_MODE, reflect TOML config format, remove interactive-related tests, and add coverage for nun uninstall routing and node-shim uninstall/remove verbs.

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~60 minutes

Possibly related PRs

  • happytoolin/hni#7: Modifies executable-detection logic in native resolution (related areas).
  • happytoolin/hni#6: Changes node shim routing logic (decide/intent mapping).
  • happytoolin/hni#3: Related CLI/command registry refactor touching command specs and help wiring.

Poem

🐰 From INI to TOML I hop with a grin,
clap derives parse the flags in a spin,
Errors now tidy, interactive shed,
HNI_FAST_MODE leads the fast-path ahead.

✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch codex/cli-surface-cleanup-nun

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

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 (2)
tests/parity_against_antfu.rs (1)

44-48: 🛠️ Refactor suggestion | 🟠 Major | ⚡ Quick win

Keep nun in the parity matrix.

This harness no longer creates or resolves nun, and none of the fixture cases exercise uninstall parity. That leaves the PR’s required public uninstall alias unverified against the antfu reference, so a regression in nun wiring or uninstall rendering can still pass parity.

♻️ Suggested parity coverage update
         create_alias(&our_bin, &our_alias_dir, "ni");
         create_alias(&our_bin, &our_alias_dir, "nr");
         create_alias(&our_bin, &our_alias_dir, "nlx");
+        create_alias(&our_bin, &our_alias_dir, "nun");
         create_alias(&our_bin, &our_alias_dir, "nci");
@@
         Case {
+            antfu_bin: cmds.nun.clone(),
+            our_bin: "nun".into(),
+            args: vec!["lodash".into()],
+        },
+        Case {
             antfu_bin: cmds.nci.clone(),
             our_bin: "nci".into(),
             args: vec![],
         },
@@
 struct AntfuBins {
     ni: PathBuf,
     nr: PathBuf,
     nlx: PathBuf,
+    nun: PathBuf,
     nci: PathBuf,
 }
@@
     Some(AntfuBins {
         ni: which::which("ni").ok()?,
         nr: which::which("nr").ok()?,
         nlx: which::which("nlx").ok()?,
+        nun: which::which("nun").ok()?,
         nci: which::which("nci").ok()?,
     })
 }

Also applies to: 127-188, 289-303

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@tests/parity_against_antfu.rs` around lines 44 - 48, The test harness removed
the uninstall alias "nun" from the parity matrix so uninstall parity isn't
verified; restore coverage by adding create_alias(&our_bin, &our_alias_dir,
"nun") where other aliases are created (see create_alias calls for
"ni","nr","nlx","nci") and ensure the same "nun" alias is present in the
corresponding fixture blocks referenced (around the other alias creation ranges)
so uninstall-related test cases exercise the "nun" path against the antfu
reference.
src/core/resolve/build.rs (1)

152-166: ⚠️ Potential issue | 🟡 Minor | ⚡ Quick win

Validate the uninstall target before package-manager detection.

Line 153 resolves the package manager before we know whether nun has anything to remove. In an empty project, nun/nun -g can currently fail with a detection error instead of the intended “requires a dependency” usage error, and nun -g can surface the Yarn Berry global-operation error even when no package name was provided. Strip -g, check for remaining args, then detect.

Suggested fix
 pub fn resolve_nun(args: Vec<String>, ctx: &ResolveContext) -> Result<ResolvedExecution> {
     let use_global = args.iter().any(|arg| arg == "-g");
-    let detected = detect_for_action(ctx, use_global)?;
     let args = if use_global {
         exclude_flag(args, "-g")
     } else {
         args
     };
 
     if args.is_empty() {
         return Err(anyhow!(
             "execution error: nun requires a dependency to uninstall.\nTry: nun lodash"
         ));
     }
 
+    let detected = detect_for_action(ctx, use_global)?;
     ensure_detected_available(&detected, ctx)?;
     Ok(build_uninstall_exec(
         detected.pm,
         args,
         ctx.cwd(),
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/core/resolve/build.rs` around lines 152 - 166, The code calls
detect_for_action(ctx, use_global) before validating that a package name was
provided, causing package-manager detection errors to surface when args are
empty; change the order so you strip the "-g" flag first (use exclude_flag on
args when use_global is true), then check args.is_empty() and return the
"requires a dependency" error if empty, and only after that call
detect_for_action(ctx, use_global) and ensure_detected_available(&detected,
ctx). Update references: move the existing
detect_for_action/ensure_detected_available calls to after the args.is_empty()
check and keep use_global, exclude_flag, detect_for_action, args.is_empty(), and
ensure_detected_available logic intact.
🧹 Nitpick comments (2)
src/core/project.rs (1)

357-373: 💤 Low value

Update: Comparator::parse supports bare versions; consider build-metadata handling

  • semver::Comparator::parse("1.2.3") succeeds without an operator (treated as an exact match), so extracting major/minor/patch from comparator is appropriate for bare packageManager version strings.
  • In src/core/project.rs (parse_version_hint, lines 357-373), VersionHint.value is rebuilt from major/minor/patch/pre only; if inputs can include build metadata like 1.2.3+sha, the current normalization drops the +... portion—add a test/fixture if that metadata is expected to be preserved.
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/core/project.rs` around lines 357 - 373, The normalization currently
reconstructs VersionHint.value from Comparator::parse output (in
parse_version_hint) but drops any build metadata (the "+..." suffix) present in
the original trimmed input; update parse_version_hint to detect and preserve
build metadata when present (e.g., parse or split the original trimmed string on
'+' and append the "+..." portion to normalized after adding pre), so
VersionHint.value retains the complete input (major/minor/patch/pre plus build
metadata) while still using comparator.major/minor/patch and comparator.pre for
the normalized core; refer to Comparator::parse, comparator.pre, and the
VersionHint struct/value when implementing this.
src/app/dispatch.rs (1)

31-94: 💤 Low value

The parsed.command.clone() is unnecessary.

The match consumes ownership of parsed.command, but parsed is still borrowed later in some arms for resolve_context(&parsed, ...). However, since resolve_context only uses parsed.cwd, parsed.fast_override, the clone could be avoided by extracting these values first or restructuring slightly. That said, ParsedCommand is a lightweight enum and the clone cost is negligible.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/app/dispatch.rs` around lines 31 - 94, The match currently does
parsed.command.clone() in run_from_env which is unnecessary; change to match on
&parsed.command (or destructure parsed first) so you don't clone the whole enum,
update each arm to pattern-match by reference (e.g.,
ParsedCommand::PrintHelp(ref topic)) and only clone specific owned pieces when
needed (for example clone invocation/args inside the ParsedCommand::Execute arm
before calling dispatch_invocation or refactor dispatch_invocation to accept
references), while keeping calls to resolve_context(&parsed, ...) and
runner::run(&resolved) unchanged.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@README.md`:
- Around line 244-248: README.md omits the Windows default config location;
update the "Config file" section to document the Windows default path used by
the code by referencing how src/core/config.rs resolves it via
dirs::config_dir()/hni/config.toml (i.e., add a line for Windows alongside the
XDG and macOS entries). Make the wording consistent with the existing entries
and mention that the application uses dirs::config_dir() on Windows to determine
the default config location.

In `@src/core/resolve/detect.rs`:
- Around line 48-50: The check in detect.rs that returns
ResolveDetectionError::UnsupportedGlobalYarnBerry when use_global && pm ==
PackageManager::YarnBerry is too install-specific and is used by both resolve_ni
and resolve_nun; update the error to be action-neutral by changing the message
for ResolveDetectionError::UnsupportedGlobalYarnBerry to refer to "global
operation" (or "global install/uninstall") instead of "global install", or
alternatively add an explicit intent parameter to the detection helper and
include that intent in the error; adjust calls from resolve_ni and resolve_nun
accordingly so the error text correctly reflects install or uninstall intent
while still triggered by PackageManager::YarnBerry and use_global.

---

Outside diff comments:
In `@src/core/resolve/build.rs`:
- Around line 152-166: The code calls detect_for_action(ctx, use_global) before
validating that a package name was provided, causing package-manager detection
errors to surface when args are empty; change the order so you strip the "-g"
flag first (use exclude_flag on args when use_global is true), then check
args.is_empty() and return the "requires a dependency" error if empty, and only
after that call detect_for_action(ctx, use_global) and
ensure_detected_available(&detected, ctx). Update references: move the existing
detect_for_action/ensure_detected_available calls to after the args.is_empty()
check and keep use_global, exclude_flag, detect_for_action, args.is_empty(), and
ensure_detected_available logic intact.

In `@tests/parity_against_antfu.rs`:
- Around line 44-48: The test harness removed the uninstall alias "nun" from the
parity matrix so uninstall parity isn't verified; restore coverage by adding
create_alias(&our_bin, &our_alias_dir, "nun") where other aliases are created
(see create_alias calls for "ni","nr","nlx","nci") and ensure the same "nun"
alias is present in the corresponding fixture blocks referenced (around the
other alias creation ranges) so uninstall-related test cases exercise the "nun"
path against the antfu reference.

---

Nitpick comments:
In `@src/app/dispatch.rs`:
- Around line 31-94: The match currently does parsed.command.clone() in
run_from_env which is unnecessary; change to match on &parsed.command (or
destructure parsed first) so you don't clone the whole enum, update each arm to
pattern-match by reference (e.g., ParsedCommand::PrintHelp(ref topic)) and only
clone specific owned pieces when needed (for example clone invocation/args
inside the ParsedCommand::Execute arm before calling dispatch_invocation or
refactor dispatch_invocation to accept references), while keeping calls to
resolve_context(&parsed, ...) and runner::run(&resolved) unchanged.

In `@src/core/project.rs`:
- Around line 357-373: The normalization currently reconstructs
VersionHint.value from Comparator::parse output (in parse_version_hint) but
drops any build metadata (the "+..." suffix) present in the original trimmed
input; update parse_version_hint to detect and preserve build metadata when
present (e.g., parse or split the original trimmed string on '+' and append the
"+..." portion to normalized after adding pre), so VersionHint.value retains the
complete input (major/minor/patch/pre plus build metadata) while still using
comparator.major/minor/patch and comparator.pre for the normalized core; refer
to Comparator::parse, comparator.pre, and the VersionHint struct/value when
implementing this.
🪄 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: defaults

Review profile: CHILL

Plan: Pro

Run ID: 9efa2e0f-622e-476e-83f7-b22738cf8653

📥 Commits

Reviewing files that changed from the base of the PR and between 4c1d3ed and a69d43f.

⛔ Files ignored due to path filters (1)
  • Cargo.lock is excluded by !**/*.lock
📒 Files selected for processing (56)
  • Cargo.toml
  • README.md
  • aliases.json
  • benchmark/run.mjs
  • jsr.json
  • jsr/na.ts
  • jsr/nru.ts
  • jsr/shared.ts
  • justfile
  • src/app/cli.rs
  • src/app/command_registry.rs
  • src/app/commands.rs
  • src/app/completion.rs
  • src/app/dispatch.rs
  • src/app/doctor.rs
  • src/app/error_report.rs
  • src/app/help.rs
  • src/core/config.rs
  • src/core/detect.rs
  • src/core/native/bin_resolver.rs
  • src/core/native/deno.rs
  • src/core/native/exec.rs
  • src/core/native/plan.rs
  • src/core/pkg_json.rs
  • src/core/project.rs
  • src/core/resolve/build.rs
  • src/core/resolve/detect.rs
  • src/core/resolve/map.rs
  • src/core/resolve/mod.rs
  • src/core/types.rs
  • src/core/util.rs
  • src/features/interactive/completion.rs
  • src/features/interactive/mod.rs
  • src/features/interactive/ni_search.rs
  • src/features/interactive/nr_scripts.rs
  • src/features/interactive/nun_select.rs
  • src/features/mod.rs
  • src/features/node_shim.rs
  • src/features/nr.rs
  • src/platform/node.rs
  • tests/cli_contract.rs
  • tests/config_detect.rs
  • tests/dispatch_multicall.rs
  • tests/error_contract.rs
  • tests/fixture_modes.rs
  • tests/hni_meta.rs
  • tests/init_contract.rs
  • tests/native_deno_fast.rs
  • tests/native_execution.rs
  • tests/native_package_managers.rs
  • tests/native_regression.rs
  • tests/node_shim.rs
  • tests/nr_interactive_behavior.rs
  • tests/parity_against_antfu.rs
  • tests/resolve_matrix.rs
  • tests/support.rs
💤 Files with no reviewable changes (13)
  • src/features/mod.rs
  • src/features/interactive/nr_scripts.rs
  • src/features/interactive/ni_search.rs
  • src/features/interactive/mod.rs
  • src/features/nr.rs
  • src/features/interactive/completion.rs
  • jsr.json
  • src/core/resolve/map.rs
  • jsr/nru.ts
  • jsr/na.ts
  • jsr/shared.ts
  • src/features/interactive/nun_select.rs
  • src/core/pkg_json.rs

Comment thread README.md
Comment thread src/core/resolve/detect.rs
@spa5k
spa5k merged commit 6a2e0a1 into main Jun 3, 2026
1 check was pending
@spa5k
spa5k deleted the codex/cli-surface-cleanup-nun branch June 3, 2026 17:33
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant