Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
37 commits
Select commit Hold shift + click to select a range
b88dc88
Fix: plugin_root should be a property
Garulf May 10, 2026
4c4b0b9
Refactor: use cached_property for root_dir and manifest_path in Plugi…
Garulf May 10, 2026
b362db3
Refactor: replace PluginManifestSchema with PluginMetadata and add pr…
Garulf May 10, 2026
a0a8771
Add models
Garulf May 10, 2026
86aef60
Fix: use correct typing
Garulf May 10, 2026
e800c40
Fix: update type hints for action method and method signature
Garulf May 10, 2026
ba896d3
Refactor: remove unused JsonRPCAction and ResultResponse, update Resu…
Garulf May 11, 2026
5872fd4
Refactor: Normalize fields for Result
Garulf May 11, 2026
d9cc617
Refactor: Update Result class to use json_rpc_action and add action m…
Garulf May 11, 2026
e1ed0a2
Add from_json method to Result class for JsonRPCResult conversion
Garulf May 11, 2026
46d4df3
Refactor: Enhance method registration in Plugin class and validate ac…
Garulf May 11, 2026
c0f07e9
Refactor: Remove unnecessary sys.version_info check and clean up impo…
Garulf May 27, 2026
42f35ca
Fix: Ensure newline at end of file in result.py
Garulf May 27, 2026
4c8f6b4
Refactor: Introduce MethodNotRegisteredError for better error handlin…
Garulf May 27, 2026
0c09fe6
Fix: Remove unnecessary blank lines in plugin.py for cleaner code
Garulf May 27, 2026
8f0de99
Refactor: Move logger setup to base.py and update Plugin class to inh…
Garulf May 27, 2026
f304fb8
Fix: Remove unnecessary blank lines and ensure newline at end of file…
Garulf May 27, 2026
da855f8
Add: Create __init__.py file for models package initialization
Garulf May 27, 2026
4c37fc5
Refactor: Update plugin manifest handling and introduce Manifest clas…
Garulf May 27, 2026
cd88b95
Fix: Remove unused import of json in plugin.py for cleaner code
Garulf May 27, 2026
86cd0fc
Refactor: Update Method class to inherit from pyFlowLauncherObject an…
Garulf May 27, 2026
f257b64
Refactor: Update add_action method to use Method type for better type…
Garulf May 27, 2026
94761ef
Refactor: Remove unnecessary blank lines in method.py for cleaner code
Garulf May 27, 2026
e7307b3
Refactor: make logger public
Garulf May 27, 2026
8f1c41a
Fix: Update run_dir method to resolve the path for accurate directory…
Garulf May 27, 2026
a2065be
Refactor: Enhance from_json method to create Manifest instance from J…
Garulf May 27, 2026
d7c7173
Refactor: Update to_json method to return a dictionary instead of Plu…
Garulf May 27, 2026
119fc64
Refactor: Update PluginMetadata to make ActionKeywords field optional
Garulf May 27, 2026
e30851d
Refactor: Remove unnecessary blank line in plugin_manifest.py
Garulf May 27, 2026
d020ff3
Refactor: Update examples with new Result
Garulf May 30, 2026
8b83fe6
Refactor: Update return type of action functions to JsonRPCRequest
Garulf May 31, 2026
e4bc612
Refactor: Include "pyflowlauncher.models" in setuptools packages
Garulf May 31, 2026
ce535ee
Refactor: Update JsonRPCRequest structure and improve method signatur…
Garulf May 31, 2026
daa8431
Refactor: Update API response keys to follow consistent casing and im…
Garulf May 31, 2026
25097ae
Fix: Update imports for compat
Garulf May 31, 2026
9588a58
Refactor: Import sys and conditionally import Self based on Python ve…
Garulf May 31, 2026
dc3b5ec
Refactor: Add future annotations import and adjust typing imports for…
Garulf May 31, 2026
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
12 changes: 6 additions & 6 deletions docs/examples/guide/api_requests/example1.py
Original file line number Diff line number Diff line change
@@ -1,16 +1,16 @@
from pyflowlauncher import Plugin, Result, send_results, api
from pyflowlauncher.result import ResultResponse
from pyflowlauncher.models.json_rpc import JsonRPCResponse

plugin = Plugin()


@plugin.on_method
def query(query: str) -> ResultResponse:
def query(query: str) -> JsonRPCResponse:
r = Result(
Title="This is a title!",
SubTitle="This is the subtitle!",
IcoPath="icon.png",
JsonRPCAction=api.change_query("This is a new query!"),
title="This is a title!",
subtitle="This is the subtitle!",
icon="icon.png",
json_rpc_action=api.change_query("This is a new query!"),
)
return send_results([r])

Expand Down
12 changes: 6 additions & 6 deletions docs/examples/guide/api_requests/example2.py
Original file line number Diff line number Diff line change
@@ -1,21 +1,21 @@
from pyflowlauncher import Plugin, Result, send_results, api
from pyflowlauncher.result import JsonRPCAction, ResultResponse
from pyflowlauncher.models.json_rpc import JsonRPCResponse

plugin = Plugin()


@plugin.on_method
def example_method() -> JsonRPCAction:
def example_method() -> JsonRPCResponse:
# Do stuff here
return api.change_query("This is also a new query!")


@plugin.on_method
def query(query: str) -> ResultResponse:
def query(query: str) -> JsonRPCResponse:
r = Result(
Title="This is a title!",
SubTitle="This is the subtitle!",
IcoPath="icon.png",
title="This is a title!",
subtitle="This is the subtitle!",
icon="icon.png",
)
r.add_action(example_method)
return send_results([r])
Expand Down
10 changes: 5 additions & 5 deletions docs/examples/guide/launcher_icons/example1.py
Original file line number Diff line number Diff line change
@@ -1,16 +1,16 @@
from pyflowlauncher import Plugin, Result, send_results
from pyflowlauncher.result import ResultResponse
from pyflowlauncher.models.json_rpc import JsonRPCResponse
from pyflowlauncher.icons import ADMIN

plugin = Plugin()


@plugin.on_method
def query(query: str) -> ResultResponse:
def query(query: str) -> JsonRPCResponse:
r = Result(
Title="This is a title!",
SubTitle="This is the subtitle!",
IcoPath=ADMIN
title="This is a title!",
subtitle="This is the subtitle!",
icon=ADMIN
)
return send_results([r])

Expand Down
10 changes: 5 additions & 5 deletions docs/examples/guide/plugin_methods/example1.py
Original file line number Diff line number Diff line change
@@ -1,15 +1,15 @@
from pyflowlauncher import Plugin, Result, send_results
from pyflowlauncher.result import ResultResponse
from pyflowlauncher.models.json_rpc import JsonRPCResponse

plugin = Plugin()


@plugin.on_method
def query(query: str) -> ResultResponse:
def query(query: str) -> JsonRPCResponse:
r = Result(
Title="This is a title!",
SubTitle="This is the subtitle!",
JsonRPCAction={"method": "action", "parameters": []}
title="This is a title!",
subtitle="This is the subtitle!",
json_rpc_action={"Method": "action", "Parameters": []}
)
return send_results([r])

Expand Down
10 changes: 5 additions & 5 deletions docs/examples/guide/plugin_methods/example2.py
Original file line number Diff line number Diff line change
@@ -1,16 +1,16 @@
from pyflowlauncher import Plugin, Result, send_results
from pyflowlauncher.result import ResultResponse
from pyflowlauncher.models.json_rpc import JsonRPCResponse

plugin = Plugin()


@plugin.on_method
def query(query: str) -> ResultResponse:
def query(query: str) -> JsonRPCResponse:
r = Result(
Title="This is a title!",
SubTitle="This is the subtitle!",
JsonRPCAction=plugin.action(action, ["stuff"])
title="This is a title!",
subtitle="This is the subtitle!",
)
r.add_action(action, ["stuff"])
return send_results([r])


Expand Down
8 changes: 4 additions & 4 deletions docs/examples/guide/scoring_results/example1.py
Original file line number Diff line number Diff line change
@@ -1,17 +1,17 @@
from pyflowlauncher import Plugin, Result, send_results
from pyflowlauncher.result import ResultResponse
from pyflowlauncher.models.json_rpc import JsonRPCResponse
from pyflowlauncher.utils import score_results

plugin = Plugin()


@plugin.on_method
def query(query: str) -> ResultResponse:
def query(query: str) -> JsonRPCResponse:
results = []
for _ in range(100):
r = Result(
Title="This is a title!",
SubTitle="This is the subtitle!",
title="This is a title!",
subtitle="This is the subtitle!",
)
results.append(r)
return send_results(score_results(query, results))
Expand Down
4 changes: 1 addition & 3 deletions pyflowlauncher/__init__.py
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import logging

from .plugin import Plugin
from .result import JsonRPCAction, Result, send_results, ResultResponse
from .result import Result, send_results
from .method import Method


Expand All @@ -10,9 +10,7 @@

__all__ = [
"Plugin",
"ResultResponse",
"send_results",
"Result",
"JsonRPCAction",
"Method",
]
34 changes: 17 additions & 17 deletions pyflowlauncher/api.py
Original file line number Diff line number Diff line change
@@ -1,79 +1,79 @@
from typing import Optional

from .result import JsonRPCAction
from .models.json_rpc import JsonRPCRequest

NAME_SPACE = 'Flow.Launcher'


def _send_action(method: str, *parameters) -> JsonRPCAction:
return {"method": f"{NAME_SPACE}.{method}", "parameters": parameters}
def _send_action(method: str, *parameters) -> JsonRPCRequest:
return {"Method": f"{NAME_SPACE}.{method}", "Parameters": list(parameters)}


def change_query(query: str, requery: bool = False) -> JsonRPCAction:
def change_query(query: str, requery: bool = False) -> JsonRPCRequest:
"""Change the query in Flow Launcher."""
return _send_action("ChangeQuery", query, requery)


def shell_run(command: str, filename: str = 'cmd.exe') -> JsonRPCAction:
def shell_run(command: str, filename: str = 'cmd.exe') -> JsonRPCRequest:
"""Run a shell command."""
return _send_action("ShellRun", command, filename)


def close_app() -> JsonRPCAction:
def close_app() -> JsonRPCRequest:
"""Close Flow Launcher."""
return _send_action("CloseApp")


def hide_app() -> JsonRPCAction:
def hide_app() -> JsonRPCRequest:
"""Hide Flow Launcher."""
return _send_action("HideApp")


def show_app() -> JsonRPCAction:
def show_app() -> JsonRPCRequest:
"""Show Flow Launcher."""
return _send_action("ShowApp")


def show_msg(title: str, sub_title: str, ico_path: str = "") -> JsonRPCAction:
def show_msg(title: str, sub_title: str, ico_path: str = "") -> JsonRPCRequest:
"""Show a message in Flow Launcher."""
return _send_action("ShowMsg", title, sub_title, ico_path)


def open_setting_dialog() -> JsonRPCAction:
def open_setting_dialog() -> JsonRPCRequest:
"""Open the settings window in Flow Launcher."""
return _send_action("OpenSettingDialog")


def start_loading_bar() -> JsonRPCAction:
def start_loading_bar() -> JsonRPCRequest:
"""Start the loading bar in Flow Launcher."""
return _send_action("StartLoadingBar")


def stop_loading_bar() -> JsonRPCAction:
def stop_loading_bar() -> JsonRPCRequest:
"""Stop the loading bar in Flow Launcher."""
return _send_action("StopLoadingBar")


def reload_plugins() -> JsonRPCAction:
def reload_plugins() -> JsonRPCRequest:
"""Reload the plugins in Flow Launcher."""
return _send_action("ReloadPlugins")


def copy_to_clipboard(text: str, direct_copy: bool = False, show_default_notification=True) -> JsonRPCAction:
def copy_to_clipboard(text: str, direct_copy: bool = False, show_default_notification=True) -> JsonRPCRequest:
"""Copy text to the clipboard."""
return _send_action("CopyToClipboard", text, direct_copy, show_default_notification)


def open_directory(directory_path: str, filename_or_filepath: Optional[str] = None) -> JsonRPCAction:
def open_directory(directory_path: str, filename_or_filepath: Optional[str] = None) -> JsonRPCRequest:
"""Open a directory."""
return _send_action("OpenDirectory", directory_path, filename_or_filepath)


def open_url(url: str, in_private: bool = False) -> JsonRPCAction:
def open_url(url: str, in_private: bool = False) -> JsonRPCRequest:
"""Open a URL."""
return _send_action("OpenUrl", url, in_private)


def open_uri(uri: str) -> JsonRPCAction:
def open_uri(uri: str) -> JsonRPCRequest:
"""Open a URI."""
return _send_action("OpenAppUri", uri)
23 changes: 23 additions & 0 deletions pyflowlauncher/base.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
import logging


def _setup_file_logger() -> logging.Logger:
log = logging.getLogger('pyflowlauncher')
if not log.handlers:
handler = logging.FileHandler('plugin.log')
handler.setLevel(logging.WARNING)
handler.setFormatter(logging.Formatter('%(asctime)s %(name)s %(levelname)s %(message)s'))
log.addHandler(handler)
log.setLevel(logging.WARNING)
log.propagate = False
return log


_setup_file_logger()


class pyFlowLauncherObject:
"""Base class for all pyFlowLauncher objects."""

def __init__(self):
self.logger = logging.getLogger(f'pyflowlauncher.{self.__class__.__name__}')
103 changes: 78 additions & 25 deletions pyflowlauncher/manifest.py
Original file line number Diff line number Diff line change
@@ -1,25 +1,78 @@
from typing import TypedDict, Literal

MANIFEST_FILE = 'plugin.json'

Languages = Literal[
'Python',
'CSharp',
'FSharp',
'Executable',
'TypeScript',
'JavaScript',
]


class PluginManifestSchema(TypedDict):
ID: str
ActionKeyword: str
Name: str
Description: str
Author: str
Version: str
Language: Languages
Website: str
IcoPath: str
ExecuteFileName: str
from __future__ import annotations

from dataclasses import dataclass, field
import json
from pathlib import Path
import sys

if sys.version_info < (3, 11):
from typing_extensions import Self
else:
from typing import Self

from .models.plugin_manifest import FILE_NAME, PluginMetadata


@dataclass
class Manifest:
id: str
name: str
author: str
version: str
language: str
description: str
website: str
execute_file_name: str
ico_path: str
action_keyword: str
action_keywords: list[str] = field(default_factory=list)

@classmethod
def from_json(cls, json_data: PluginMetadata) -> Self:
"""Create a Manifest instance from a JSON dictionary."""
return cls(
id=json_data['ID'],
name=json_data['Name'],
author=json_data['Author'],
version=json_data['Version'],
language=json_data['Language'],
description=json_data['Description'],
website=json_data['Website'],
execute_file_name=json_data['ExecuteFileName'],
ico_path=json_data['IcoPath'],
action_keyword=json_data['ActionKeyword'],
action_keywords=json_data.get('ActionKeywords', []),
)

@classmethod
def from_file(cls, path: Path) -> Self:
"""Load the manifest from a JSON file."""
with open(path, 'r', encoding='utf-8') as f:
data = json.load(f)
return cls.from_json(data)

@classmethod
def from_dir(cls, dir_path: Path) -> Self:
"""Load the manifest from a directory containing a plugin.json file."""
return cls.from_file(Path(dir_path) / FILE_NAME)

def to_json(self) -> PluginMetadata:
"""Convert the manifest to a JSON-serializable dictionary."""
return {
"ID": self.id,
"Name": self.name,
"Author": self.author,
"Version": self.version,
"Language": self.language,
"Description": self.description,
"Website": self.website,
"ExecuteFileName": self.execute_file_name,
"IcoPath": self.ico_path,
"ActionKeyword": self.action_keyword,
"ActionKeywords": self.action_keywords,
}

def save(self, path: Path) -> None:
"""Save the manifest to a JSON file."""
with open(path, 'w', encoding='utf-8') as f:
json.dump(self.to_json(), f, ensure_ascii=False, indent=4)
Loading
Loading