Skip to content

refactor(cli): use operations from shared crate#158

Merged
QaidVoid merged 2 commits intomainfrom
refactor/cli
Feb 14, 2026
Merged

refactor(cli): use operations from shared crate#158
QaidVoid merged 2 commits intomainfrom
refactor/cli

Conversation

@QaidVoid
Copy link
Member

@QaidVoid QaidVoid commented Feb 14, 2026

Summary by CodeRabbit

  • New Features

    • Per-command context and event-driven progress lifecycle for clearer, managed progress output
    • Expanded download options (pattern matching, extraction, overwrite/skip controls) and improved download progress display
  • Improvements

    • Unified, richer progress UI with colored indicators and concise operation summaries
    • Consistent byte-size formatting across commands
    • Simplified interactive prompts and clearer install/update/remove/run reports

@coderabbitai
Copy link
Contributor

coderabbitai bot commented Feb 14, 2026

📝 Walkthrough

Walkthrough

This PR shifts the CLI from a global AppState to a context-driven SoarContext, centralizes progress/event handling, delegates package operations to soar-operations, consolidates workspace dependency declarations, and removes state.rs and many in-file orchestration helpers.

Changes

Cohort / File(s) Summary
Workspace dependencies
Cargo.toml, crates/soar-cli/Cargo.toml
Added workspace deps (clap, indicatif, nu-ansi-term, tabled, terminal_size, tracing-subscriber); migrated many soar-cli deps to workspace = true; added soar-events/soar-operations; removed minisign-verify and soar-registry entries.
Context & lifecycle
crates/soar-cli/src/main.rs
Added create_context() -> (SoarContext, Option<ProgressGuard>), propagates SoarContext across commands, manages ProgressGuard lifecycle and event sink.
Removed global state
crates/soar-cli/src/state.rs
Entire file removed — AppState, DB/metadata init, sync and related helpers deleted.
Progress/event system
crates/soar-cli/src/progress.rs, crates/soar-cli/src/logging.rs
Rewrote progress subsystem to centralized MultiProgress, event handler, ProgressGuard, create_*_job helpers and suspend/stop APIs; removed MultiProgress globals from logging and adapted SuspendingWriter to use progress::suspend.
Command refactors (apply/install/remove/update/run/use/list/inspect/download/health)
crates/soar-cli/src/apply.rs, .../install.rs, .../remove.rs, .../update.rs, .../run.rs, .../use.rs, .../list.rs, .../inspect.rs, .../download.rs, .../health.rs
Commands now accept &SoarContext and delegate core flows to soar-operations modules (apply, install, remove, update, run, switch, list, search, health). Signatures changed (many added ctx param); flows simplified to report-driven APIs and Install/Update/Apply/Run reports.
Download/Progress integration
crates/soar-cli/src/download.rs, crates/soar-cli/src/self_actions.rs
Expanded DownloadContext with filtering, output, progress callback and extraction options; replaced progress usage with create_download_job/handle_download_progress.
Formatting utilities
crates/soar-cli/src/inspect.rs, .../list.rs, .../utils.rs, .../download.rs
Replaced indicatif::HumanBytes with centralized format_bytes(..., 2) across displays; removed executable-discovery/symlink helper functions from utils (mangle/find_executable/etc.).
Reporting helpers
crates/soar-cli/src/apply.rs, .../install.rs, .../update.rs
Introduced report-display functions (display_apply_report, display_install_report, display_update_report) and unified Install/Update/Apply/Remove report types from soar-operations.
Soar-operations & registry tweaks
crates/soar-operations/src/install.rs, crates/soar-operations/src/update.rs, crates/soar-registry/src/metadata.rs
Minor behavior changes: checksum error message simplified; perform_update now accepts no_verify flag; metadata fetch logs downgraded to debug.

Sequence Diagram(s)

sequenceDiagram
  participant CLI as CLI (main)
  participant Ctx as SoarContext
  participant Events as EventHandler/Progress
  participant Ops as soar-operations
  participant DB as DB/Metadata

  CLI->>Ctx: create_context()
  Ctx->>Events: spawn_event_handler()
  CLI->>Ctx: call command(ctx, ...)
  CLI->>Ops: delegate operation (e.g., install::resolve/perform)
  Ops->>DB: query/resolve metadata
  Ops->>Events: emit progress events
  Events->>CLI: update progress bars via ProgressGuard
  Ops-->>CLI: return Report (Install/Update/Apply/Run)
  CLI->>CLI: display_*_report(report)
  CLI->>Events: stop/suspend/finish ProgressGuard (on exit)
Loading

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~70 minutes

Possibly related PRs

Poem

🐰 I hopped from AppState to a Context so neat,

Progress bars dance to a new event-beat,
Reports now arrive from modules afar,
Packages move swiftly — hop, hop, hurrah! 🥕

🚥 Pre-merge checks | ✅ 4
✅ Passed checks (4 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title accurately describes the primary change: refactoring the CLI to use operations from a shared crate instead of maintaining local implementations.
Docstring Coverage ✅ Passed Docstring coverage is 100.00% which is sufficient. The required threshold is 80.00%.
Merge Conflict Detection ✅ Passed ✅ No merge conflicts detected when merging into main

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

✨ Finishing touches
  • 📝 Generate docstrings
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Post copyable unit tests in a comment
  • Commit unit tests in branch refactor/cli

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

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

@codecov
Copy link

codecov bot commented Feb 14, 2026

Copy link
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: 5

Caution

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

⚠️ Outside diff range comments (2)
crates/soar-cli/src/use.rs (1)

17-44: ⚠️ Potential issue | 🟡 Minor

Display glitch when pkg_type is None: output renders as (-version).

When package.pkg_type is None, argument 5 becomes an empty string, and the format string ({}-{}) produces (-1.0.0) — the leading dash without a type looks unintended.

Consider conditionally including the type-version group:

Suggested fix
         "[{}] {}#{}:{} ({}{}) ({}){}",
         idx + 1,
         Colored(Blue, &package.pkg_name),
         Colored(Cyan, &package.pkg_id),
         Colored(Cyan, &package.repo_name),
         package
             .pkg_type
             .as_ref()
-            .map(|pkg_type| format!(":{}", Colored(Magenta, &pkg_type)))
+            .map(|pkg_type| format!("{}:", Colored(Magenta, &pkg_type)))
             .unwrap_or_default(),
         Colored(Magenta, &package.version),

Or restructure to omit the dash entirely when pkg_type is absent.

crates/soar-cli/src/inspect.rs (1)

205-211: ⚠️ Potential issue | 🟡 Minor

{inspect_type} is not interpolated in the prompt string.

Line 207 passes a string literal "The {inspect_type} file is too large..." to interactive_ask, but this is not a format! macro — {inspect_type} will appear literally in the output rather than being replaced with "log" or "script".

Proposed fix
-        let response = interactive_ask(
-            "The {inspect_type} file is too large. Do you really want to view it (y/N)?",
-        )?;
+        let response = interactive_ask(
+            &format!("The {inspect_type} file is too large. Do you really want to view it (y/N)?"),
+        )?;
🤖 Fix all issues with AI agents
In `@crates/soar-cli/src/health.rs`:
- Around line 109-111: The log message "Removed all broken packages" is emitted
whenever report.removed is non-empty even if some removals failed; update the
condition around the info! call in the health.rs code handling the report
(checking report.removed and report.failed) so it only logs "Removed all broken
packages" when report.removed is non-empty AND report.failed.is_empty(), and add
an else path that logs or warns about which removals failed (use report.failed)
to accurately reflect partial failures.

In `@crates/soar-cli/src/install.rs`:
- Around line 152-159: The fallback path in install_with_show that calls
install::resolve_packages currently only collects ResolveResult::Resolved and
silently drops NotFound, AlreadyInstalled, and Ambiguous results; update the
loop that handles results from install::resolve_packages(ctx,
std::slice::from_ref(package), options).await to mirror the main
install_packages handling by matching all ResolveResult variants (Resolved,
NotFound, AlreadyInstalled, Ambiguous), pushing targets for Resolved and
emitting the same user-facing messages or warnings for NotFound,
AlreadyInstalled and Ambiguous (using the same logging/print helpers used in
install_packages) so no result is silently ignored and output behavior is
consistent.

In `@crates/soar-cli/src/run.rs`:
- Around line 47-55: The comment about interactive checksum handling is
stale—remove or update it so it no longer claims special checksum handling
around run::execute_binary; instead document that prepare_run handles checksum
failures upstream. Also avoid calling std::process::exit which skips
destructors: replace the direct std::process::exit(run_result.exit_code)
behavior by returning an appropriate exit code from the run function (propagate
run_result.exit_code up to main) so ctx drop and progress_guard.finish() in
main.rs execute; reference run::execute_binary, run_result.exit_code, and remove
the misleading comment lines above the execute_binary call.

In `@crates/soar-cli/src/update.rs`:
- Line 17: The CLI flag is ignored because update_packages declares _no_verify
but doesn't forward it and perform_update constructs InstallOptions with
no_verify: false; fix by renaming _no_verify to no_verify in update_packages and
pass that boolean into the update flow (e.g., into perform_update call), then
update perform_update to accept a no_verify argument (or thread it through its
existing params) and use that value when constructing InstallOptions instead of
hardcoding false; ensure the InstallOptions no_verify field is set from the
propagated no_verify parameter so the CLI flag actually controls checksum
verification.

In `@crates/soar-cli/src/use.rs`:
- Line 38: package.size is an Option<u64> but format_bytes requires u64; update
the call where Colored(Magenta, format_bytes(package.size, 2)) is used to pass a
concrete u64 instead (e.g., package.size.copied().unwrap_or(0) or
package.size.unwrap_or(0)) so format_bytes receives a u64; keep the rest of the
Colored(...) expression unchanged and use the same symbol names (format_bytes
and package.size) when making the fix.
🧹 Nitpick comments (13)
crates/soar-operations/src/install.rs (2)

925-927: Generic error message loses per-package detail within the error string itself.

The old message presumably included package name/id in the error text. The new message is generic. While this is fine because the caller (Line 646-657) wraps it into FailedInfo with pkg_name/pkg_id, and the event at Line 647 carries the same, any code path that only has access to the SoarError (e.g., logging the error independently) will lack package context.

Consider embedding the package identity if the error is ever surfaced in isolation:

Suggested improvement
-                return Err(SoarError::Custom(
-                    "Invalid checksum, skipped installation.".into(),
-                ));
+                return Err(SoarError::Custom(format!(
+                    "{}#{}: Invalid checksum, skipped installation.",
+                    pkg.pkg_name, pkg.pkg_id
+                )));

682-684: Arc::try_unwrap / into_inner unwraps are safe here but fragile.

After all handles are successfully awaited, no other clones remain, so these unwraps won't panic on the happy path. If the early-return on JoinError changes in the future (e.g., collecting all errors instead of returning on first), the try_unwrap would panic. A defensive approach:

Optional: handle failure gracefully
-    let installed = Arc::try_unwrap(installed).unwrap().into_inner().unwrap();
-    let failed = Arc::try_unwrap(failed).unwrap().into_inner().unwrap();
-    let warnings = Arc::try_unwrap(warnings).unwrap().into_inner().unwrap();
+    let installed = Arc::try_unwrap(installed)
+        .map_err(|_| SoarError::Custom("internal: installed Arc still shared".into()))?
+        .into_inner()
+        .unwrap_or_default();
+    let failed = Arc::try_unwrap(failed)
+        .map_err(|_| SoarError::Custom("internal: failed Arc still shared".into()))?
+        .into_inner()
+        .unwrap_or_default();
+    let warnings = Arc::try_unwrap(warnings)
+        .map_err(|_| SoarError::Custom("internal: warnings Arc still shared".into()))?
+        .into_inner()
+        .unwrap_or_default();
crates/soar-cli/src/remove.rs (1)

74-79: Minor formatting inconsistency: success messages use plain text while confirmation uses Colored.

The confirmation display (Lines 32-38) uses Colored(Blue, ...), Colored(Cyan, ...), etc., but the success messages at Lines 74-79 use plain format! with no coloring. Consider aligning for a consistent user experience.

crates/soar-cli/src/utils.rs (1)

159-164: Variable shadowing of size in closures reduces readability.

Both closures shadow the function parameter size with their closure argument. Consider using a distinct name like s or bytes for the closure parameter to avoid confusion.

Suggested clarity improvement
 pub fn pretty_package_size(ghcr_size: Option<u64>, size: Option<u64>) -> String {
     ghcr_size
-        .map(|size| format!("{}", Colored(Magenta, format_bytes(size, 2))))
-        .or_else(|| size.map(|size| format!("{}", Colored(Magenta, format_bytes(size, 2)))))
+        .map(|s| format!("{}", Colored(Magenta, format_bytes(s, 2))))
+        .or_else(|| size.map(|s| format!("{}", Colored(Magenta, format_bytes(s, 2)))))
         .unwrap_or_default()
 }
crates/soar-cli/src/apply.rs (1)

197-207: total_changes includes to_remove.len() regardless of prune flag.

On Line 197, total_changes sums all three categories unconditionally, but on Line 203 the display correctly guards to_remove behind prune. Since total_changes is only used to decide whether to show the summary line at all (Line 198), this is harmless — but it could cause the summary to print when prune=false and only removable packages exist (no installs/updates/in-sync), showing all zeros. Minor cosmetic issue.

crates/soar-cli/src/progress.rs (2)

33-47: ProgressGuard lacks a Drop impl — forgetting finish() silently leaks the handler thread.

If a caller drops the guard without calling finish(), the JoinHandle is dropped (detaching the thread). The thread will continue until the channel disconnects, but won't be joined. For a short-lived CLI this is harmless, but adding a Drop impl would make the contract self-enforcing:

Suggested Drop impl
+impl Drop for ProgressGuard {
+    fn drop(&mut self) {
+        if let Some(handle) = self.handle.take() {
+            handle.join().ok();
+        }
+    }
+}

With this, finish() becomes a convenience alias for explicit cleanup, and accidental drops still join.


414-421: unreachable!() in SyncStage wildcard arm will panic if a new variant is added.

If SyncStage gains a new variant in the future, this will panic at runtime. Consider logging an unknown stage instead:

Safer fallback
-                            _ => unreachable!(),
+                            other => format!("{repo_name}: {other:?}"),

This applies similarly to the VerifyStage wildcard at Line 275, though that one is inside a branch already restricted to Checksum | Signature, making it truly unreachable.

crates/soar-cli/src/inspect.rs (1)

60-62: ProgressGuard is dropped immediately due to _ binding.

let (ctx, _) = crate::create_context(); — the underscore pattern _ drops the ProgressGuard immediately (Rust does not bind _ to a variable). If progress_enabled() is true, the event handler thread is spawned and then immediately joined/dropped. This won't cause a functional issue here since inspect_log uses direct spinner APIs rather than the event system, but it's worth noting the inconsistency with how other commands receive context from main.rs.

Consider either passing ctx from the caller (as other commands do) or using _guard to keep the guard alive:

-    let (ctx, _) = crate::create_context();
+    let (ctx, _guard) = crate::create_context();
crates/soar-cli/src/run.rs (1)

13-13: command[0] will panic if command is empty.

If command is an empty slice, indexing at [0] will panic. While clap likely guarantees at least one argument, a defensive check would prevent a confusing panic.

Proposed defensive access
-    let package_name = &command[0];
+    let package_name = command.first().ok_or_else(|| {
+        soar_core::error::SoarError::Custom("No package name provided".into())
+    })?;
crates/soar-cli/src/download.rs (1)

145-146: Context is created inside a per-link loop iteration — wasteful for multiple non-URL links.

crate::create_context() is called inside the for link in &links loop (line 90). If multiple non-URL links are provided, each iteration creates a fresh SoarContext (re-reading config, potentially spawning/joining an event handler thread). Consider accepting a &SoarContext as a parameter or hoisting context creation outside the loop.

Additionally, _ drops the ProgressGuard immediately (same pattern as in inspect.rs).

crates/soar-cli/src/main.rs (1)

289-294: inspect_log and Log/Inspect commands don't receive ctx — inconsistent with other commands.

All other commands receive &ctx, but Log and Inspect create their own context internally via crate::create_context(). This means they have a separate event sink and progress guard lifecycle. Consider passing &ctx for consistency, which would also avoid the _ ProgressGuard drop issue noted in inspect.rs.

crates/soar-cli/src/install.rs (2)

42-55: Unnecessary .clone() on owned Option<String> values.

The portable* parameters are owned Option<String> values. Since they are not used after being passed into InstallOptions, they can be moved directly instead of cloned. The same applies to name_override, version_override, pkg_type_override, and pkg_id_override — which are already moved without cloning on lines 51–54.

Suggested diff
     let options = InstallOptions {
         force,
-        portable: portable.clone(),
-        portable_home: portable_home.clone(),
-        portable_config: portable_config.clone(),
-        portable_share: portable_share.clone(),
-        portable_cache: portable_cache.clone(),
+        portable,
+        portable_home,
+        portable_config,
+        portable_share,
+        portable_cache,
         binary_only,
         no_verify,
         name_override,

127-135: Unused _yes parameter.

The _yes parameter is accepted but never used in install_with_show. In the main install_packages path, yes controls auto-selection of the first candidate for ambiguous results (line 71). The --show path always presents an interactive selector, so if --yes is passed alongside --show, the user still gets an interactive prompt (line 236), which may be unexpected.

Consider either using yes to auto-select the first match (consistent with the non-show path) or documenting why --yes is intentionally ignored here.

.unlinked
.then(String::new)
.unwrap_or_else(|| format!(" {}", Colored(Red, "*")))
Colored(Magenta, format_bytes(package.size, 2)),
Copy link
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🔴 Critical

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
# Check the type of `size` field in the Package struct
ast-grep --pattern 'struct Package {
  $$$
}'

Repository: pkgforge/soar

Length of output: 39


🏁 Script executed:

#!/bin/bash
# Also search for Package struct definition with rg in case ast-grep doesn't find it
rg 'struct Package' -A 10

Repository: pkgforge/soar

Length of output: 11123


🏁 Script executed:

#!/bin/bash
# Find the format_bytes function to check its parameter types
rg 'fn format_bytes' -A 3

Repository: pkgforge/soar

Length of output: 331


🏁 Script executed:

#!/bin/bash
# Check the imports and context in use.rs around line 38
head -50 crates/soar-cli/src/use.rs

Repository: pkgforge/soar

Length of output: 1641


🏁 Script executed:

#!/bin/bash
# Get the full Package struct from crates/soar-core/src/database/models.rs
sed -n '/^pub struct Package {/,/^}/p' crates/soar-core/src/database/models.rs

Repository: pkgforge/soar

Length of output: 1591


🏁 Script executed:

#!/bin/bash
# Double check the exact line 38 context and verify there's no cast or unwrap we missed
sed -n '35,42p' crates/soar-cli/src/use.rs

Repository: pkgforge/soar

Length of output: 416


Handle Option<u64> type mismatch: package.size cannot be passed directly to format_bytes.

package.size is Option<u64>, but format_bytes expects u64. This will not compile. Use .unwrap_or(0), .copied().unwrap_or(0), or similar to handle the Option.

🤖 Prompt for AI Agents
In `@crates/soar-cli/src/use.rs` at line 38, package.size is an Option<u64> but
format_bytes requires u64; update the call where Colored(Magenta,
format_bytes(package.size, 2)) is used to pass a concrete u64 instead (e.g.,
package.size.copied().unwrap_or(0) or package.size.unwrap_or(0)) so format_bytes
receives a u64; keep the rest of the Colored(...) expression unchanged and use
the same symbol names (format_bytes and package.size) when making the fix.

Copy link
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

🤖 Fix all issues with AI agents
In `@crates/soar-cli/src/install.rs`:
- Around line 78-88: The re-resolution loop only collects
ResolveResult::Resolved and silently drops other variants (notably
ResolveResult::AlreadyInstalled), so when the fully-qualified query resolves to
AlreadyInstalled the package vanishes without feedback; update the loop that
calls install::resolve_packages (the one building install_targets from
re_results for specific_query built from pkg.pkg_name/pkg.pkg_id/pkg.repo_name
and options) to match on all ResolveResult variants: preserve Resolved into
install_targets as before, and explicitly handle AlreadyInstalled (e.g., record
the package into a separate already_installed list or emit a user-facing message
via the same ctx/logger so the user sees that selection is already installed),
and handle/propagate any other variants (Ambiguous/NotFound/Error) appropriately
instead of dropping them silently.
🧹 Nitpick comments (5)
crates/soar-operations/src/update.rs (1)

470-470: Error from remove_old_versions is silently discarded.

let _ = remove_old_versions(...) swallows any cleanup failure. If old version removal fails (e.g., permission error, file in use), the user gets no feedback. Consider at least logging a warning.

Suggested fix
-            let _ = remove_old_versions(pkg, &diesel_db, false);
+            if let Err(e) = remove_old_versions(pkg, &diesel_db, false) {
+                warn!(
+                    "Failed to remove old versions of {}#{}: {}",
+                    pkg.pkg_name, pkg.pkg_id, e
+                );
+            }
crates/soar-cli/src/health.rs (1)

12-13: display_health is async but contains no .await.

health::check_health(ctx) is synchronous. The async qualifier adds unnecessary overhead (future state machine). If it's kept for signature consistency across CLI commands, that's fine — just a nit.

crates/soar-cli/src/run.rs (1)

40-43: Silent Ok(0) on unexpected PrepareRunResult variant after re-resolution.

If prepare_run with a specific repo_name and pkg_id unexpectedly returns Ambiguous again, this silently returns success. Consider logging a warning or debug message for this "shouldn't happen" case.

Suggestion
             match result {
                 PrepareRunResult::Ready(path) => path,
-                _ => return Ok(0),
+                other => {
+                    tracing::warn!("Unexpected result after re-resolution: {other:?}");
+                    return Ok(1);
+                }
             }
crates/soar-cli/src/main.rs (1)

56-56: create_context is pub but appears to be crate-internal.

If this function is not intended for use outside the crate, consider pub(crate).

crates/soar-cli/src/install.rs (1)

65-108: Duplicate resolution-result handling logic across install_packages and install_with_show.

The match on ResolveResult variants (lines 65-108) is duplicated nearly verbatim in install_with_show (lines 154-197). Consider extracting a shared helper to avoid drift between these two paths.

Comment on lines +78 to +88
// Re-resolve with the specific selected package
let specific_query =
format!("{}#{}:{}", pkg.pkg_name, pkg.pkg_id, pkg.repo_name);
let re_results =
install::resolve_packages(ctx, &[specific_query], &options).await?;
for r in re_results {
if let ResolveResult::Resolved(targets) = r {
install_targets.extend(targets);
}
}
}
Copy link
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟡 Minor

Non-Resolved re-resolution results are silently dropped after ambiguous selection.

After the user picks a specific package from the ambiguous set, re-resolution at lines 81-87 only collects Resolved targets. If the fully-qualified query returns AlreadyInstalled, the user gets no feedback — the package silently disappears from the install set.

This is unlikely for NotFound/Ambiguous (query is fully qualified), but AlreadyInstalled is a realistic scenario (user picks a variant that's already installed without --force).

Suggested improvement
                     for r in re_results {
-                        if let ResolveResult::Resolved(targets) = r {
-                            install_targets.extend(targets);
+                        match r {
+                            ResolveResult::Resolved(targets) => {
+                                install_targets.extend(targets);
+                            }
+                            ResolveResult::AlreadyInstalled { pkg_name, pkg_id, repo_name, version } => {
+                                warn!(
+                                    "{}#{}:{} ({}) is already installed - skipping",
+                                    pkg_name, pkg_id, repo_name, version,
+                                );
+                                if !force {
+                                    info!("Hint: Use --force to reinstall");
+                                }
+                            }
+                            ResolveResult::NotFound(name) => {
+                                error!("Package {} not found", name);
+                            }
+                            _ => {}
                         }
                     }
🤖 Prompt for AI Agents
In `@crates/soar-cli/src/install.rs` around lines 78 - 88, The re-resolution loop
only collects ResolveResult::Resolved and silently drops other variants (notably
ResolveResult::AlreadyInstalled), so when the fully-qualified query resolves to
AlreadyInstalled the package vanishes without feedback; update the loop that
calls install::resolve_packages (the one building install_targets from
re_results for specific_query built from pkg.pkg_name/pkg.pkg_id/pkg.repo_name
and options) to match on all ResolveResult variants: preserve Resolved into
install_targets as before, and explicitly handle AlreadyInstalled (e.g., record
the package into a separate already_installed list or emit a user-facing message
via the same ctx/logger so the user sees that selection is already installed),
and handle/propagate any other variants (Ambiguous/NotFound/Error) appropriately
instead of dropping them silently.

@QaidVoid QaidVoid merged commit 2a2f1be into main Feb 14, 2026
6 of 7 checks passed
@QaidVoid QaidVoid mentioned this pull request Feb 14, 2026
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