Skip to content

Add flow-launcher:// deep link protocol, .flowplugin file association, and --query command line argument - #4565

Open
Garulf wants to merge 13 commits into
devfrom
feature/query-command-line-arg
Open

Add flow-launcher:// deep link protocol, .flowplugin file association, and --query command line argument#4565
Garulf wants to merge 13 commits into
devfrom
feature/query-command-line-arg

Conversation

@Garulf

@Garulf Garulf commented Jul 7, 2026

Copy link
Copy Markdown
Member

What

Adds a flow-launcher:// deep link protocol and .flowplugin file association, plus a --query "text" (also -query) command-line flag, so scripts, browsers, and other apps can open/focus Flow Launcher to run a query, open settings, or install a plugin.

  • --query "text" / -q "text": opens/focuses Flow Launcher with the query pre-filled and searched.
  • flow-launcher://query?q=..., flow-launcher://settings, flow-launcher://plugin/install?path=|id=|url=: routed through a general verb-dispatch framework. Plugin installs always show the existing Yes/No confirmation dialog — never silent, whether triggered by a local .flowplugin double-click, a store id, or an https download URL.
  • .flowplugin file association and the flow-launcher:// URI scheme are self-healing HKCU registrations applied at every startup (no admin rights required), with a new Settings toggle, "Allow websites to open Flow Launcher", to opt out.
  • Cold start (no instance running): the deep link is dispatched right after plugin initialization, then the main window is shown.
  • Warm start (instance already running): the second process normalizes its args to a flow-launcher:// URI and sends it to the first instance over the existing single-instance named pipe, then exits; the first instance dispatches it.
  • No args: behavior is unchanged — a second launch just shows/focuses the window.

How

  • Flow.Launcher/Helper/DeepLink.cs: normalizes command-line args (--query, .flowplugin paths, raw flow-launcher:// URIs) into a single URI string, parses a URI into a verb + query parameters, and dispatches to handlers for query, settings, and plugin/install.
  • Flow.Launcher/Helper/DeepLinkRegistration.cs: idempotent HKCU registration for the .flowplugin ProgID and the flow-launcher:// scheme, called on every startup so stale executable paths self-heal after updates.
  • Flow.Launcher.Core/Plugin/PluginInstaller.cs: adds InstallPluginFromWebAndCheckRestartAsync(url), reusing the existing unknown-source warning and the always-prompting InstallPluginAndCheckRestartAsync.
  • App.xaml.cs: Main(string[] args) normalizes args via DeepLink.FromCommandLineArgs; the pending deep link is dispatched after PluginManager.InitializePluginsAsync, before the home-page refresh check; OnSecondAppStarted(string payload) dispatches the warm-path payload (or just shows the window when empty).
  • SingleInstance.cs: the named pipe now carries an optional payload — the normalized deep link URI (or legacy plain query text). The client writes it as a newline-terminated UTF-8 line and blocks (up to 3 s) since the process exits immediately after; the server reads one line guarded by a 2 s timeout and try/catch, so a client that connects but never writes degrades to a plain activation and can never hang the server loop.
  • Settings.EnableDeepLinkProtocol (default true) gates the URI scheme registration; toggling it in Settings registers/unregisters the scheme immediately via DeepLinkRegistration.

Test matrix (manual, Windows)

  1. No args, cold start — unchanged behavior (home page etc.)
  2. --query "foo" / -query "foo", cold and warm — window opens/focuses with "foo" searched
  3. Warm start, no args — window just shows/focuses (regression check)
  4. --query "" — treated as no query
  5. Double-click a .flowplugin (Flow running and not running) — confirmation dialog, install only on Yes
  6. flow-launcher://query?q=foo, flow-launcher://settings, flow-launcher://plugin/install?id=... / ?path=... / ?url=https://...zip from a browser — routed correctly, every install prompts
  7. ?url=http://... (non-https) — rejected with an https-only message
  8. flow-launcher://nope — error notification, no crash
  9. Settings toggle off — HKCU\Software\Classes\flow-launcher removed, browser links no longer open Flow; .flowplugin double-click and --query still work. Toggle on — restored
  10. Corrupt the registered command path manually, relaunch — self-healed
  11. Robustness: a client that connects to the pipe without writing/closing — server loop recovers after the 2 s read timeout and keeps accepting connections

Summary by cubic

Summary of changes
Adds a flow-launcher:// deep link and .flowplugin association plus a --query/-q flag so scripts and websites can open Flow Launcher, run searches, open settings, or install plugins. When the URI scheme is disabled or no args are provided, launch behavior is unchanged; second-instance invocations forward a normalized deep link to the running app over IPC.

  • Changed
    • Single-instance IPC now carries an optional UTF-8 deep-link payload; server cancels the read after 2s, client connect waits up to 3s, and the outer delivery wait is 5s so payloads are not dropped; dispatcher activation exceptions are observed.
    • Pipe read timeouts become plain activations; genuine read failures are logged.
    • App.Main(string[] args) normalizes args to a deep link; dispatch happens after plugins initialize. Links received during startup are queued so none are lost. OnSecondAppStarted(string payload) routes a payload; empty payload focuses the window.
    • CLI supports --query and -q; empty queries are ignored.
    • Deep link protocol toggle updates registry immediately and reverts the toggle if registration fails.
    • Logging keeps full deep-link payloads at Debug; Warn logs only the actionable fact.
  • Added
    • Deep link parser/dispatcher with verbs: query, settings, and plugin/install (by .flowplugin path, store id, or https URL). Helper HasExactlyOneInstallIdentifier validates install links.
    • HKCU registration for .flowplugin and the flow-launcher:// scheme, gated by a new General Settings toggle “Allow websites to open Flow Launcher”; registration is ensured at startup.
    • PluginInstaller.InstallPluginFromWebAndCheckRestartAsync with unknown-source confirmation; DerivePluginNameFromUrl sanitizes filenames and falls back to a safe default; TryPopulateIdFromZip recovers the real plugin ID for URL installs to keep modified-plugin tracking correct.
    • UI strings and a General Settings toggle. Unit tests: DeepLinkTest (arg normalization, scheme-disabled behavior, URI parsing) and PluginInstallerTest (URL filename derivation edge cases).
  • Removed
    • Old OnSecondAppStarted() overload; no user-visible features removed.
  • Memory
    • Negligible: short-lived strings and small stream buffers; no persistent allocations.
  • Security
    • URI scheme is opt-in and per-user (HKCU). Named pipe remains per-user. Deep links are parsed/validated; plugin installs require https URLs or an existing local file; store-id installs validate against the manifest. Payload logging is minimized; IPC timeouts prevent hangs.
  • Tests
    • Added DeepLinkTest and PluginInstallerTest; manual verification of cold/warm starts, empty queries, startup-queued links, and all plugin install paths.

Release Note
Open Flow Launcher from your browser or scripts to search, open settings, or install plugins using special links or the new --query option.

Written for commit cd2a1b9. Summary will update on new commits.

Review in cubic

Support `Flow.Launcher.exe --query "text"` (also `-query`). On cold start
the query is applied once plugin initialization completes; when an
instance is already running, the second process sends the query to it
over the existing single-instance named pipe (which previously carried
no payload) and the first instance shows the window with the query
searched. Launching with no arguments behaves exactly as before.
@github-actions github-actions Bot added this to the 2.2.0 milestone Jul 7, 2026
@coderabbitai

coderabbitai Bot commented Jul 7, 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
📝 Walkthrough

Walkthrough

Adds deep-link parsing and dispatch, single-instance payload forwarding, Windows registry registration, a settings toggle, and plugin installation handling from deep-link URLs.

Changes

Deep link handling and registration

Layer / File(s) Summary
Deep link parsing and dispatch
Flow.Launcher/Helper/DeepLink.cs, Flow.Launcher.Core/Plugin/PluginInstaller.cs
Normalizes command-line inputs into flow-launcher:// payloads, parses verbs and parameters, dispatches query/settings/plugin-install links, and adds direct ZIP URL installation support.
Deep link registration and settings toggle
Flow.Launcher/Helper/DeepLinkRegistration.cs, Flow.Launcher.Infrastructure/UserSettings/Settings.cs, Flow.Launcher/SettingPages/ViewModels/SettingsPaneGeneralViewModel.cs, Flow.Launcher/SettingPages/Views/SettingsPaneGeneral.xaml, Flow.Launcher/Languages/en.xaml
Registers the .flowplugin association and URI scheme, adds a setting to enable or disable the protocol, and wires the settings page and localized strings to that toggle.
Startup and single-instance payload flow
Flow.Launcher/Helper/SingleInstance.cs, Flow.Launcher/App.xaml.cs
Carries deep-link payloads through single-instance startup and second-instance activation, registers handling during startup, and dispatches pending payloads after plugin initialization.
Deep link test coverage
Flow.Launcher.Test/DeepLinkTest.cs
Adds tests for command-line normalization, URI parsing, valid parameters, and invalid payload handling.

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

Suggested reviewers: jjw24, onesounds

🚥 Pre-merge checks | ✅ 5
✅ Passed checks (5 passed)
Check name Status Explanation
Docstring Coverage ✅ Passed No functions found in the changed files to evaluate docstring coverage. Skipping docstring coverage check.
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.
Title check ✅ Passed The title clearly summarizes the deep-link protocol, .flowplugin association, and command-line query changes.
Description check ✅ Passed The description directly explains the deep-link, file association, command-line, IPC, settings, installation, and testing changes.
✨ 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 feature/query-command-line-arg

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: 1

🤖 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 `@Flow.Launcher/Helper/SingleInstance.cs`:
- Around line 113-130: The timeout path in SingleInstance.HandleClient leaves
the pending ReadLineAsync running after Task.WhenAny returns on the delay
branch, which can fault later as an unobserved task exception. Update the pipe
read logic to use cancellation so the read is explicitly canceled when the
2000ms timeout wins, and ensure the code in the try block around
StreamReader.ReadLineAsync and the later
Dispatcher.Invoke(ActivateFirstInstance) only proceeds with a completed read
task. Also confirm the ReadLineAsync(CancellationToken) overload is available
for the project’s target framework before wiring the cancellation token through.
🪄 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: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: b9edbf7c-5932-46a5-9718-29538c9bef9f

📥 Commits

Reviewing files that changed from the base of the PR and between 4a831c6 and 27d911c.

📒 Files selected for processing (2)
  • Flow.Launcher/App.xaml.cs
  • Flow.Launcher/Helper/SingleInstance.cs

Comment thread Flow.Launcher/Helper/SingleInstance.cs Outdated

@cubic-dev-ai cubic-dev-ai 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.

3 issues found across 2 files

Reply with feedback, questions, or to request a fix.

Re-trigger cubic

Comment thread Flow.Launcher/App.xaml.cs Outdated
Comment thread Flow.Launcher/Helper/SingleInstance.cs Outdated
Comment thread Flow.Launcher/Helper/SingleInstance.cs Outdated
Comment thread Flow.Launcher/App.xaml.cs Outdated

@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

🤖 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 `@Flow.Launcher/Helper/DeepLink.cs`:
- Around line 41-44: The deep link builder in DeepLink.Build should treat an
empty `--query` or `-query` value the same as no query at all. Update the
query-handling branch to detect when args[i + 1] is empty or whitespace and skip
creating the `query?q=` deep link, returning the no-payload behavior instead.
Keep the fix localized to the argument parsing logic in DeepLink.

In `@Flow.Launcher/SettingPages/ViewModels/SettingsPaneGeneralViewModel.cs`:
- Around line 108-124: The deep link toggle is being persisted before the
registry update succeeds, so a failure in
SettingsPaneGeneralViewModel.SetEnableDeepLinkProtocol can leave
Settings.EnableDeepLinkProtocol out of sync with Windows. Update the logic
around DeepLinkRegistration.RegisterUriScheme() and
DeepLinkRegistration.UnregisterUriScheme() so the previous setting is restored
in the catch block if the registry call throws, and keep the UI/persisted value
aligned with the actual registration state.
🪄 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: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: beecc1f5-a5de-4af2-b556-e55facbfe387

📥 Commits

Reviewing files that changed from the base of the PR and between 27d911c and eb1c876.

📒 Files selected for processing (10)
  • Flow.Launcher.Core/Plugin/PluginInstaller.cs
  • Flow.Launcher.Infrastructure/UserSettings/Settings.cs
  • Flow.Launcher.Test/DeepLinkTest.cs
  • Flow.Launcher/App.xaml.cs
  • Flow.Launcher/Helper/DeepLink.cs
  • Flow.Launcher/Helper/DeepLinkRegistration.cs
  • Flow.Launcher/Helper/SingleInstance.cs
  • Flow.Launcher/Languages/en.xaml
  • Flow.Launcher/SettingPages/ViewModels/SettingsPaneGeneralViewModel.cs
  • Flow.Launcher/SettingPages/Views/SettingsPaneGeneral.xaml
✅ Files skipped from review due to trivial changes (2)
  • Flow.Launcher/Languages/en.xaml
  • Flow.Launcher.Test/DeepLinkTest.cs
🚧 Files skipped from review as they are similar to previous changes (1)
  • Flow.Launcher/Helper/SingleInstance.cs

Comment thread Flow.Launcher/Helper/DeepLink.cs Outdated
Comment thread Flow.Launcher/SettingPages/ViewModels/SettingsPaneGeneralViewModel.cs Outdated
@Garulf Garulf changed the title Add --query command line argument to send a query to Flow Launcher Add flow-launcher:// deep link protocol, .flowplugin file association, and --query command line argument Jul 7, 2026

@cubic-dev-ai cubic-dev-ai 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.

6 issues found across 10 files (changes from recent commits).

Reply with feedback, questions, or to request a fix.

Re-trigger cubic

Comment thread Flow.Launcher/SettingPages/ViewModels/SettingsPaneGeneralViewModel.cs Outdated
Comment thread Flow.Launcher.Core/Plugin/PluginInstaller.cs Outdated
Comment thread Flow.Launcher/Helper/SingleInstance.cs Outdated
Comment thread Flow.Launcher/Helper/DeepLink.cs Outdated
Comment thread Flow.Launcher/Helper/DeepLink.cs Outdated
Comment thread Flow.Launcher/Helper/DeepLink.cs
- Rename -query to -q to match single-dash single-character convention
  (per review discussion) and treat an empty query value as no query
- Cancel the pending pipe read on timeout instead of abandoning it, to
  avoid an unobserved task exception; widen the client connect timeout
  past the server's read guard so a stalled client can't starve a
  legitimate second launch; observe exceptions from the fire-and-forget
  dispatcher activation
- Revert the deep link protocol setting if registry (un)registration
  fails, instead of persisting a value that disagrees with the OS state
- Sanitize the filename derived from a plugin download URL so a
  percent-decoded path segment can't reintroduce characters that are
  invalid in Windows filenames
- Stop logging full deep link payloads (query text, paths, URLs) at
  Warn level; keep only the actionable fact at Warn and move the raw
  payload to Debug

@cubic-dev-ai cubic-dev-ai 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.

All reported issues were addressed across 5 files (changes from recent commits).

Tip: Review your code locally with the cubic CLI to iterate faster.

Re-trigger cubic

Comment thread Flow.Launcher/Helper/DeepLink.cs
Comment thread Flow.Launcher.Core/Plugin/PluginInstaller.cs Outdated
…ailures

- Widen the outer signal-delivery wait from 3s to 5s so a slow
  ConnectAsync(3000ms) can no longer consume the entire budget and
  starve the subsequent payload write, silently dropping a deep link
- Distinguish the expected read-timeout (client connected but never
  wrote) from a genuine pipe read failure; log the latter instead of
  swallowing it silently
@Garulf

Garulf commented Jul 7, 2026

Copy link
Copy Markdown
Member Author

Personally tested:

  • Plugin install URI by ID flow-launcher://plugin/install?id=B2D2C23B055D411GH66FF0C79D6C2A6H
  • Plugin install URI by URL flow-launcher://plugin/install?url=https://github.com/Garulf/HA-Commander/releases/download/v5.2.1/HA-Commander.zip
  • Plugin install URI by path flow-launcher://plugin/install?path=D:\Users\garulf\Downloads\HA-Commander.zip
  • Query URI flow-launcher://query?q=helloworld
  • Settings URI flow-launcher://settings
  • CLI args ./Flow.Launcher.exe -q "hi"
  • Install plugin zip with .flowplugin extension
  • Disabling "Allow websites to open Flow Launcher" stops URI from working
  • Enabling"Allow websites to open Flow Launcher" allows URI to work

Everything has been tested to work

@Garulf Garulf added the enhancement New feature or request label Jul 9, 2026
@Jack251970

Copy link
Copy Markdown
Member

@codex Review it

@chatgpt-codex-connector chatgpt-codex-connector 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.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 061d53c8de

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment thread Flow.Launcher/App.xaml.cs
return;
}

DeepLink.Dispatch(payload);

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P2 Badge Queue second-instance links until startup completes

When a browser or script invokes a link while the first process is still starting, the mutex and pipe server already exist, but _mainWindow and the plugins may not. This immediately dispatches the payload rather than deferring it like _pendingDeepLink: query activation is suppressed by App.LoadingOrExiting, and plugin-install handlers can run against an uninitialized PluginManager, so the link can be lost or fail. Queue these activations until the same post-plugin-initialization point used for the cold-start payload.

Useful? React with 👍 / 👎.

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

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

Fixed in 568b3f8: OnSecondAppStarted now queues the payload into _pendingDeepLink (behind a lock shared with the startup flush) until plugin initialization completes, and the startup path dispatches whatever is pending at that point, latest link wins.


var plugin = new UserPlugin
{
ID = string.Empty,

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P2 Badge Preserve a unique ID for URL-installed plugins

When AutoRestartAfterChanging is false, successfully installing the first ?url= plugin causes PluginManager.InstallPlugin to add this empty ID to ModifiedPlugins. Every subsequent URL install in the same session is also created with an empty ID, so the initial PluginModified(newPlugin.ID) check rejects it as already modified even when it is a different plugin. Populate the actual ID from the downloaded package before entering the normal install path, or otherwise avoid tracking all URL installs under string.Empty.

Useful? React with 👍 / 👎.

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

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

Fixed in 568b3f8: after the download completes, the real ID is read from the zip's plugin.json before entering the install path, so ModifiedPlugins tracks the actual plugin ID and later URL installs are no longer rejected. If the zip is unreadable the ID stays empty and the existing InstallPlugin error path reports it.

Garulf added 2 commits August 18, 2026 12:12
- Queue deep links received by a second instance until plugin
  initialization completes, so warm links arriving during startup are
  no longer lost or dispatched against an uninitialized PluginManager
- Recover the real plugin ID from the downloaded zip for url installs,
  so ModifiedPlugins no longer tracks every url install under an empty
  ID and rejects subsequent installs
- Fall back to a default plugin name when URL sanitization collapses
  the basename to an empty string
- Extract DerivePluginNameFromUrl and HasExactlyOneInstallIdentifier
  as testable helpers with unit tests
…line-arg

# Conflicts:
#	Flow.Launcher.Test/PluginInstallerTest.cs

@cubic-dev-ai cubic-dev-ai 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.

All reported issues were addressed across 6 files (changes from recent commits).

Tip: Review your code locally with the cubic CLI to iterate faster.

Re-trigger cubic

Comment thread Flow.Launcher.Core/Plugin/PluginInstaller.cs
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

enhancement New feature or request

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants