Skip to content

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

Open
Garulf wants to merge 10 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 10 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

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. Command-line args are normalized to deep links and sent to the running instance via IPC; behavior is unchanged when disabled or with no args.

Summary of changes

  • Changed
    • Second-instance IPC now carries an optional UTF-8 deep-link payload; server read is canceled after 2s, client connect waits up to 3s, and the outer signal wait is 5s to avoid dropping the payload on slow connects; dispatcher activation exceptions are observed.
    • Distinguish pipe read timeouts from genuine read failures; timeouts are treated as plain activations, while real read errors are logged.
    • App.Main(args) normalizes args to a deep link and dispatches after plugin init; OnSecondAppStarted(string) now accepts a payload and routes it (empty payload just focuses the window).
    • CLI supports -q as a shorthand for --query; empty query values are ignored.
    • Deep link protocol setting toggles registry state immediately and reverts the toggle if registration fails.
    • Logging avoids writing full deep-link payloads at Warn; raw payloads are logged at Debug only.
  • Added
    • Deep link parser/dispatcher with verbs: query, settings, and plugin/install (by local .flowplugin path, store id, or https URL).
    • HKCU registration for .flowplugin and the flow-launcher:// scheme, gated by a new Settings toggle “Allow websites to open Flow Launcher”; registration is ensured at startup.
    • PluginInstaller.InstallPluginFromWebAndCheckRestartAsync with unknown-source confirmation and sanitized filenames derived from URLs.
    • UI strings and a General Settings toggle. Unit tests (DeepLinkTest) for arg normalization (--query/-q), .flowplugin paths, scheme-disabled behavior, and URI parsing.
  • Removed
    • Old OnSecondAppStarted() overload; no user-visible features removed.
  • Memory
    • Negligible impact: 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; manually verified cold/warm starts, empty queries, no-arg launches, and plugin install flows.

Release Note
Open Flow Launcher from the browser or scripts to search or install plugins using flow-launcher:// links, or start it with a pre-filled search via: Flow.Launcher.exe --query "your text" (or -q).

Written for commit ab882e6. 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

📝 Walkthrough

Walkthrough

Adds deep-link parsing and dispatch, single-instance payload forwarding, registry registration, a settings toggle, and plugin install 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 plugin 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 general 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 a deep-link payload through single-instance startup and second-instance activation, registers deep-link handling during app startup, and dispatches pending payloads after initialization.
Deep link test coverage
Flow.Launcher.Test/DeepLinkTest.cs
Adds tests for command-line normalization, URI parsing, and invalid payload handling.

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

Suggested reviewers: jjw24, onesounds, VictoriousRaptor

🚥 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 main additions: deep links, .flowplugin association, and the new query flag.
Description check ✅ Passed The description is directly related to the changeset and accurately describes the new deep-link, file association, and query behaviors.
✨ 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
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.

2 issues found across 5 files (changes from recent commits).

Prompt for AI agents (unresolved issues)

Check if these issues are valid — if so, understand the root cause of each and fix them. If appropriate, use sub-agents to investigate and fix each issue separately.


<file name="Flow.Launcher.Core/Plugin/PluginInstaller.cs">

<violation number="1" location="Flow.Launcher.Core/Plugin/PluginInstaller.cs:167">
P2: Installing a plugin from a web URL can produce an empty or misleading plugin name when the URL path contains percent-encoded characters that decode to invalid Windows filename characters (e.g., `%3F` → `?`). After `Path.GetFileName` and the invalid-character stripping loop, a basename like `?.zip` collapses to `.zip`, and removing the extension yields an empty string. That empty value flows into `UserPlugin.Name` and `Version`, so user-facing prompts show a blank plugin name, and the temp download filename becomes `-$'{Guid.NewGuid()}.zip`. Consider validating the sanitized result and falling back to a safe default (for example, `"plugin"` or the last non-empty path segment) before constructing the `UserPlugin`.</violation>
</file>

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

Re-trigger cubic

Comment thread Flow.Launcher/Helper/DeepLink.cs
var filename = Uri.TryCreate(url, UriKind.Absolute, out var uri)
? Path.GetFileName(uri.LocalPath)
: url.Split('/').Last();
foreach (var c in Path.GetInvalidFileNameChars())

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.

P2: Installing a plugin from a web URL can produce an empty or misleading plugin name when the URL path contains percent-encoded characters that decode to invalid Windows filename characters (e.g., %3F?). After Path.GetFileName and the invalid-character stripping loop, a basename like ?.zip collapses to .zip, and removing the extension yields an empty string. That empty value flows into UserPlugin.Name and Version, so user-facing prompts show a blank plugin name, and the temp download filename becomes -$'{Guid.NewGuid()}.zip. Consider validating the sanitized result and falling back to a safe default (for example, "plugin" or the last non-empty path segment) before constructing the UserPlugin.

Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. At Flow.Launcher.Core/Plugin/PluginInstaller.cs, line 167:

<comment>Installing a plugin from a web URL can produce an empty or misleading plugin name when the URL path contains percent-encoded characters that decode to invalid Windows filename characters (e.g., `%3F` → `?`). After `Path.GetFileName` and the invalid-character stripping loop, a basename like `?.zip` collapses to `.zip`, and removing the extension yields an empty string. That empty value flows into `UserPlugin.Name` and `Version`, so user-facing prompts show a blank plugin name, and the temp download filename becomes `-$'{Guid.NewGuid()}.zip`. Consider validating the sanitized result and falling back to a safe default (for example, `"plugin"` or the last non-empty path segment) before constructing the `UserPlugin`.</comment>

<file context>
@@ -159,9 +159,15 @@ public static async Task InstallPluginFromWebAndCheckRestartAsync(string url)
         var filename = Uri.TryCreate(url, UriKind.Absolute, out var uri)
             ? Path.GetFileName(uri.LocalPath)
             : url.Split('/').Last();
+        foreach (var c in Path.GetInvalidFileNameChars())
+        {
+            filename = filename.Replace(c.ToString(), string.Empty);
</file context>

…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
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.

2 participants