Skip to content

feat(cli): install packages from soar:// links - #197

Merged
QaidVoid merged 6 commits into
mainfrom
url-handler
Aug 12, 2026
Merged

feat(cli): install packages from soar:// links#197
QaidVoid merged 6 commits into
mainfrom
url-handler

Conversation

@QaidVoid

@QaidVoid QaidVoid commented Aug 12, 2026

Copy link
Copy Markdown
Member

Summary by CodeRabbit

  • New Features
    • Added a CLI command for opening and installing packages from soar:// links.
    • Added an option to register Soar as the handler for soar:// links.
    • Added validation and safety checks for malformed or potentially unsafe links.
    • Added confirmation prompts and trust warnings before browser-initiated installations.
    • Added terminal detection and fallback notifications for requests launched from a browser.
    • Added support for case-insensitive schemes and trailing slashes in supported links.

@cloudflare-workers-and-pages

cloudflare-workers-and-pages Bot commented Aug 12, 2026

Copy link
Copy Markdown

Deploying soar-docs with  Cloudflare Pages  Cloudflare Pages

Latest commit: 92b71e3
Status: ✅  Deploy successful!
Preview URL: https://4d45c1c1.soar-docs.pages.dev
Branch Preview URL: https://url-handler.soar-docs.pages.dev

View logs

@coderabbitai

coderabbitai Bot commented Aug 12, 2026

Copy link
Copy Markdown
Contributor

Review Change Stack

Note

Reviews paused

It looks like this branch is under active development. To avoid overwhelming you with review comments due to an influx of new commits, CodeRabbit has automatically paused this review. You can configure this behavior by changing the reviews.auto_review.auto_pause_after_reviewed_commits setting.

Use the following commands to manage reviews:

  • @coderabbitai resume to resume automatic reviews.
  • @coderabbitai review to trigger a single review.

Use the checkboxes below for quick actions:

  • ▶️ Resume reviews
  • 🔍 Trigger review

No actionable comments were generated in the recent review. 🎉

ℹ️ Recent review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro Plus

Run ID: 65729f0c-5edc-4f96-a63a-f3e83aa9d4ee

📥 Commits

Reviewing files that changed from the base of the PR and between 672268c and dabc84a.

📒 Files selected for processing (1)
  • crates/soar-cli/src/url_handler.rs
🚧 Files skipped from review as they are similar to previous changes (1)
  • crates/soar-cli/src/url_handler.rs

📝 Walkthrough

Walkthrough

The CLI adds a url command for handling soar:// links and registering soar as their desktop handler. The URL handler validates install requests, supports terminal relaunch and trust confirmation, and starts package installation.

Changes

Soar URL handling

Layer / File(s) Summary
CLI URL entrypoint
crates/soar-cli/src/cli.rs, crates/soar-cli/src/main.rs
The CLI adds the url command and forwards its optional URL and --register flag to url_handler::handle.
URL validation and request model
crates/soar-cli/src/url_handler.rs
The handler defines UrlRequest, parses supported soar:// install links, validates package specifications, rejects unsafe input, and tests valid and invalid cases.
Registration and installation flow
crates/soar-cli/src/url_handler.rs
The handler discovers terminals, creates per-user desktop registration, relaunches browser requests, confirms trust, installs packages, and displays errors until dismissal.

Estimated code review effort: 4 (Complex) | ~45 minutes

Sequence Diagram(s)

sequenceDiagram
  participant Browser
  participant SoarCLI
  participant url_handler
  participant Terminal
  participant PackageInstallation
  Browser->>SoarCLI: Launch soar:// install link
  SoarCLI->>url_handler: handle(context, url, register_only)
  url_handler->>url_handler: parse(url)
  url_handler->>Terminal: Relaunch browser request
  Terminal->>SoarCLI: Start soar in terminal
  url_handler->>PackageInstallation: Install confirmed package
Loading
🚥 Pre-merge checks | ✅ 5
✅ Passed checks (5 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title clearly and concisely describes the main change: installing packages from soar:// links through the CLI.
Docstring Coverage ✅ Passed Docstring coverage is 100.00% which is sufficient. The required threshold is 80.00%.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.
✨ 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 url-handler

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.

@coderabbitai coderabbitai Bot left a comment

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.

Actionable comments posted: 2

🧹 Nitpick comments (5)
crates/soar-cli/src/cli.rs (1)

498-508: 📐 Maintainability & Code Quality | 🔵 Trivial | 💤 Low value

Consider arg_required_else_help for the url subcommand.

soar url with no argument parses successfully. The failure then comes from url_handler::handle as a runtime error. Sibling subcommands such as install and search use #[command(arg_required_else_help = true)] and print help instead. This keeps the CLI behavior consistent.

Note that clap conflicts_with = "register" is symmetric, so the mutual exclusion is already enforced in both directions.

♻️ Proposed change
     /// Act on a soar:// link, or register soar as its handler
+    #[command(arg_required_else_help = true)]
     #[clap(name = "url")]
     Url {
🤖 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 `@crates/soar-cli/src/cli.rs` around lines 498 - 508, Update the Url subcommand
declaration to enable arg_required_else_help, so invoking soar url without a URL
or registration option displays clap help instead of reaching
url_handler::handle; keep the existing URL/register conflict configuration
unchanged.
crates/soar-cli/src/url_handler.rs (4)

180-190: 🩺 Stability & Availability | 🔵 Trivial | 💤 Low value

which accepts a non-executable file.

which selects the first PATH entry where candidate.is_file() is true. It does not check the executable bit. The same applies to the fs::metadata(name).is_ok() branch for a path that contains /. A non-executable file that shares a terminal name shadows a real terminal later on PATH, and the spawn then fails.

♻️ Proposed change
+use std::os::unix::fs::PermissionsExt;
+
+fn is_executable(path: &std::path::Path) -> bool {
+    fs::metadata(path)
+        .map(|m| m.is_file() && m.permissions().mode() & 0o111 != 0)
+        .unwrap_or(false)
+}
+
 fn which(name: &str) -> Option<String> {
     if name.contains('/') {
-        return fs::metadata(name).is_ok().then(|| name.to_string());
+        return is_executable(std::path::Path::new(name)).then(|| name.to_string());
     }
     env::var_os("PATH").and_then(|paths| {
         env::split_paths(&paths)
             .map(|dir| dir.join(name))
-            .find(|candidate| candidate.is_file())
+            .find(|candidate| is_executable(candidate))
             .map(|candidate| candidate.to_string_lossy().into_owned())
     })
 }
🤖 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 `@crates/soar-cli/src/url_handler.rs` around lines 180 - 190, Update which to
require executability in both branches: for names containing “/”, validate the
resolved path is an executable file rather than merely existing; for PATH
candidates, replace the is_file check with an executable-file check. Preserve
the existing first-match and Option<String> behavior.

307-366: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

Add tests for the non-parse helpers.

The parse tests are thorough. The untested code is where the remaining risk sits.

Please add tests for:

  • desktop_entry with an executable path that contains %, a space, and ". This covers the escaping issue raised on lines 129 to 150.
  • validate_spec boundary cases, for example a spec that ends with . or -, and a spec with an empty version after @.
  • find_terminal with TERMINAL set to an absolute path and to an unknown name.
🤖 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 `@crates/soar-cli/src/url_handler.rs` around lines 307 - 366, Add focused tests
in the existing tests module for desktop_entry escaping executable paths
containing %, spaces, and quotes; validate_spec rejecting specs ending in . or -
and specs with an empty version after @; and find_terminal handling TERMINAL as
both an absolute path and an unknown name. Assert the expected escaped output or
validation/lookup results while isolating environment-variable changes between
tests.

293-299: 🎯 Functional Correctness | 🔵 Trivial | 💤 Low value

wait_for_close()? can mask the install error.

Line 298 uses ?. If interactive_ask fails, for example when stdin reaches EOF, handle returns the stdin error and drops result. The caller then reports a stdin failure instead of the install failure.

♻️ Proposed change
     if let Err(ref err) = result {
         info!("{err}");
     }
-    wait_for_close()?;
+    let _ = wait_for_close();
     result
🤖 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 `@crates/soar-cli/src/url_handler.rs` around lines 293 - 299, Update the
result-handling flow in handle so wait_for_close() cannot replace an existing
interactive_ask install error: preserve and return result when it is already
Err, while still propagating wait_for_close() failures when the operation
succeeded.

194-217: 🎯 Functional Correctness | 🔵 Trivial | 💤 Low value

Consider a case-insensitive scheme check and a wait on the spawned terminal.

Two points on the relaunch path.

  1. parse at lines 76 to 77 accepts only the exact strings soar:// and SOAR://. RFC 3986 defines the scheme as case-insensitive. Most browsers normalize the scheme to lowercase, so the risk is low. A mixed-case form such as Soar://install/ripgrep is still rejected. Use url.get(..7).is_some_and(|p| p.eq_ignore_ascii_case("soar://")) instead.
  2. spawn returns immediately and handle then returns Ok(()). The parent exits while the terminal starts. That is the intended behavior for a browser launch. Confirm that no SoarContext lock or SQLite handle held by the parent is still needed by the child, because the child runs the real install.
🤖 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 `@crates/soar-cli/src/url_handler.rs` around lines 194 - 217, Update the URL
scheme validation in parse to accept any casing of the seven-character “soar://”
prefix using a case-insensitive comparison. Keep relaunch_in_terminal’s
non-blocking spawn behavior for browser launches, and verify the parent does not
retain any SoarContext lock or SQLite handle required by the child process.
🤖 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 `@crates/soar-cli/src/url_handler.rs`:
- Around line 258-269: Update the warning block in the URL-handling flow before
interactive_ask to use the same direct stdout output path as the prompt, rather
than tracing::info!, so it remains visible regardless of log-level, quiet, or
JSON filtering. Preserve the existing warning text and confirmation behavior.
- Around line 129-150: Update desktop_entry to encode exe according to
desktop-entry Exec rules: escape every literal % as %% and quote paths
containing any reserved Exec character, escaping embedded ", `, $, and \ with
the required desktop-entry string layer. Ensure literal backslashes and dollar
signs produce the specified file-level escaping, and add unit tests covering %,
", \, $, and another reserved character.

---

Nitpick comments:
In `@crates/soar-cli/src/cli.rs`:
- Around line 498-508: Update the Url subcommand declaration to enable
arg_required_else_help, so invoking soar url without a URL or registration
option displays clap help instead of reaching url_handler::handle; keep the
existing URL/register conflict configuration unchanged.

In `@crates/soar-cli/src/url_handler.rs`:
- Around line 180-190: Update which to require executability in both branches:
for names containing “/”, validate the resolved path is an executable file
rather than merely existing; for PATH candidates, replace the is_file check with
an executable-file check. Preserve the existing first-match and Option<String>
behavior.
- Around line 307-366: Add focused tests in the existing tests module for
desktop_entry escaping executable paths containing %, spaces, and quotes;
validate_spec rejecting specs ending in . or - and specs with an empty version
after @; and find_terminal handling TERMINAL as both an absolute path and an
unknown name. Assert the expected escaped output or validation/lookup results
while isolating environment-variable changes between tests.
- Around line 293-299: Update the result-handling flow in handle so
wait_for_close() cannot replace an existing interactive_ask install error:
preserve and return result when it is already Err, while still propagating
wait_for_close() failures when the operation succeeded.
- Around line 194-217: Update the URL scheme validation in parse to accept any
casing of the seven-character “soar://” prefix using a case-insensitive
comparison. Keep relaunch_in_terminal’s non-blocking spawn behavior for browser
launches, and verify the parent does not retain any SoarContext lock or SQLite
handle required by the child process.
🪄 Autofix

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 Plus

Run ID: 70f3f5b6-2643-4c33-9f6f-474bc4138ebc

📥 Commits

Reviewing files that changed from the base of the PR and between 2772841 and 4489d0d.

📒 Files selected for processing (3)
  • crates/soar-cli/src/cli.rs
  • crates/soar-cli/src/main.rs
  • crates/soar-cli/src/url_handler.rs

Comment thread crates/soar-cli/src/url_handler.rs Outdated
Comment thread crates/soar-cli/src/url_handler.rs Outdated
@QaidVoid
QaidVoid merged commit e447e7c into main Aug 12, 2026
10 checks passed
@QaidVoid QaidVoid mentioned this pull request Aug 12, 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