aphelion-plugin-sdk 0.1.0__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (39) hide show
  1. aphelion_plugin_sdk-0.1.0.dist-info/METADATA +114 -0
  2. aphelion_plugin_sdk-0.1.0.dist-info/RECORD +39 -0
  3. aphelion_plugin_sdk-0.1.0.dist-info/WHEEL +5 -0
  4. aphelion_plugin_sdk-0.1.0.dist-info/entry_points.txt +2 -0
  5. aphelion_plugin_sdk-0.1.0.dist-info/top_level.txt +1 -0
  6. aphelion_sdk/__init__.py +151 -0
  7. aphelion_sdk/__main__.py +10 -0
  8. aphelion_sdk/audio/__init__.py +9 -0
  9. aphelion_sdk/cli/__init__.py +7 -0
  10. aphelion_sdk/cli/main.py +203 -0
  11. aphelion_sdk/host/__init__.py +16 -0
  12. aphelion_sdk/host/constants.py +17 -0
  13. aphelion_sdk/host/errors.py +20 -0
  14. aphelion_sdk/host/install.py +55 -0
  15. aphelion_sdk/host/locate.py +81 -0
  16. aphelion_sdk/host/models.py +33 -0
  17. aphelion_sdk/host/validate.py +75 -0
  18. aphelion_sdk/host/windows.py +80 -0
  19. aphelion_sdk/packaging/__init__.py +19 -0
  20. aphelion_sdk/packaging/builder.py +222 -0
  21. aphelion_sdk/packaging/discovery.py +250 -0
  22. aphelion_sdk/packaging/errors.py +20 -0
  23. aphelion_sdk/packaging/names.py +174 -0
  24. aphelion_sdk/packaging/project.py +241 -0
  25. aphelion_sdk/plugin.py +51 -0
  26. aphelion_sdk/properties.py +179 -0
  27. aphelion_sdk/py.typed +0 -0
  28. aphelion_sdk/registration.py +85 -0
  29. aphelion_sdk/types.py +19 -0
  30. aphelion_sdk/version.py +6 -0
  31. aphelion_sdk/video/__init__.py +7 -0
  32. aphelion_sdk/video/effect.py +93 -0
  33. aphelion_sdk/widgets/__init__.py +23 -0
  34. aphelion_sdk/widgets/base.py +76 -0
  35. aphelion_sdk/widgets/dialog.py +45 -0
  36. aphelion_sdk/widgets/host.py +105 -0
  37. aphelion_sdk/widgets/kinds.py +14 -0
  38. aphelion_sdk/widgets/panel.py +35 -0
  39. aphelion_sdk/widgets/qt.py +35 -0
@@ -0,0 +1,114 @@
1
+ Metadata-Version: 2.4
2
+ Name: aphelion-plugin-sdk
3
+ Version: 0.1.0
4
+ Summary: Public SDK for Aphelion plugins (video now; audio later).
5
+ Author: youthx
6
+ License-Expression: LicenseRef-Proprietary
7
+ Project-URL: Homepage, https://github.com/aphelion-engine/aphelion-sdk
8
+ Project-URL: Repository, https://github.com/aphelion-engine/aphelion-sdk
9
+ Keywords: aphelion,plugin,sdk,video,audio
10
+ Requires-Python: >=3.11
11
+ Description-Content-Type: text/markdown
12
+ Requires-Dist: numpy>=1.26.0
13
+ Provides-Extra: dev
14
+ Requires-Dist: build>=1.2.0; extra == "dev"
15
+ Requires-Dist: pytest>=8.0.0; extra == "dev"
16
+ Requires-Dist: mypy>=1.8.0; extra == "dev"
17
+
18
+ # Aphelion Plugin SDK
19
+
20
+ Public API for plugins that run **inside Aphelion Editor**. This package sits beside `aphelion-editor`, not inside it.
21
+
22
+ Import **`aphelion_sdk` only**. Never import `core`, `effects`, `render`, or `ui`.
23
+
24
+ Video effects are available now (`VideoEffectPlugin`). Audio bases will land under `aphelion_sdk.audio` later.
25
+
26
+ Version **0.1.0**. Python **3.11+**. Install with **`pip install aphelion-plugin-sdk`**. Import **`aphelion_sdk`**.
27
+
28
+ ## Install
29
+
30
+ ```bash
31
+ pip install aphelion-plugin-sdk
32
+ ```
33
+
34
+ From the `aphelion-engine` root, with a venv active:
35
+
36
+ ```bash
37
+ pip install -e ./aphelion-editor
38
+ pip install -e ./aphelion-sdk
39
+ ```
40
+
41
+ Installing the editor already depends on this package (`aphelion-plugin-sdk @ file:../aphelion-sdk`).
42
+
43
+ ```bash
44
+ aphelion-sdk --version
45
+ python -m aphelion_sdk --help
46
+ ```
47
+
48
+ ## Quick start
49
+
50
+ ```python
51
+ import aphelion_sdk
52
+
53
+
54
+ @aphelion_sdk.register_plugin
55
+ class GrayscaleEffect(aphelion_sdk.VideoEffectPlugin):
56
+ plugin_name = "Grayscale"
57
+ plugin_category = "Plugins"
58
+ plugin_description = "Blend a frame toward grayscale."
59
+ plugin_color = (140, 140, 140)
60
+
61
+ def setup_effect_properties(self) -> None:
62
+ self.set_property(
63
+ "amount",
64
+ aphelion_sdk.slider_property(
65
+ 100, 0, 100,
66
+ label="Amount",
67
+ suffix="%",
68
+ ),
69
+ )
70
+
71
+ def process_frame(
72
+ self,
73
+ frame: aphelion_sdk.Frame,
74
+ _frame_num: int,
75
+ ) -> aphelion_sdk.Frame:
76
+ amount = self.float_value("amount", 100.0) / 100.0
77
+ luma = (
78
+ frame[..., 0] * 0.2126
79
+ + frame[..., 1] * 0.7152
80
+ + frame[..., 2] * 0.0722
81
+ )
82
+ gray = luma[..., None].repeat(3, axis=2)
83
+ return frame * (1.0 - amount) + gray * amount
84
+ ```
85
+
86
+ Drop the file in the editor's `plugins/` or `userdata/plugins/`, or pack a wheel (below). Time-independent effects should name the unused argument `_frame_num`.
87
+
88
+ Examples:
89
+
90
+ - [`examples/grayscale_effect.py`](examples/grayscale_effect.py)
91
+ - [`examples/effect_with_widget.py`](examples/effect_with_widget.py) — dialog + panel via `widgets = (...)`
92
+
93
+ ## Documentation
94
+
95
+ | Guide | Contents |
96
+ |---|---|
97
+ | [Authoring](docs/authoring.md) | Effect class, properties, discovery rules |
98
+ | [Widgets](docs/widgets.md) | Panels, dialogs, primitives, PyQt6 |
99
+ | [API reference](docs/api.md) | Public symbols |
100
+ | [Packaging](docs/packaging.md) | `aphelion-sdk build`, entry points, drop-in install |
101
+ | [Editor plugins](../aphelion-editor/docs/plugins.md) | How the host loads and reloads plugins |
102
+
103
+ ## Package a plugin
104
+
105
+ ```bash
106
+ aphelion-sdk build examples/grayscale_effect.py -o dist
107
+ pip install dist/aphelion_plugin_grayscale-*.whl
108
+ ```
109
+
110
+ Entry point group: `aphelion.plugins`. Widgets are declared on the plugin (`widgets = (MyDialog, MyPanel)`); they are not registered on their own.
111
+
112
+ ## License
113
+
114
+ Proprietary (`LicenseRef-Proprietary` in `pyproject.toml`).
@@ -0,0 +1,39 @@
1
+ aphelion_sdk/__init__.py,sha256=Tq_JL8Zd0VdUWfVByLowWvHbkciAqP_wwOoUMkR1-us,5180
2
+ aphelion_sdk/__main__.py,sha256=W5PU-J3zrWyMLkpkWOo9q8RG0ah2Da2Ua-kMsUZkVKY,215
3
+ aphelion_sdk/plugin.py,sha256=IiVSVH7V_20dr9knFdF_jsKwytGduKNBnUa-iB3FAGk,2053
4
+ aphelion_sdk/properties.py,sha256=Wq_ChTMZaKltNup3OpLtFIeuFEehTyVntGEY3ma1GSQ,4764
5
+ aphelion_sdk/py.typed,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
6
+ aphelion_sdk/registration.py,sha256=ZnKewc1Vd2eZEwSJeG-MIYJiPt3NgDObxPaEZ9wmVQE,2805
7
+ aphelion_sdk/types.py,sha256=fcQrZyIKw51Mh8nNWbKeDZKlGaz7NgO8kqhaRff0j78,602
8
+ aphelion_sdk/version.py,sha256=DRDKPdzt6Ku28r5lgFuYvtmulhod8TmfX2b8UVmtql8,169
9
+ aphelion_sdk/audio/__init__.py,sha256=lQAb4POqnfot3fbg_UZiezJD1cH9CBBZXwFBAJziJxg,185
10
+ aphelion_sdk/cli/__init__.py,sha256=qAkOQZWWPoll4T5pGsVNsfWAsqwMF0hVXn2WQ3bM6mU,161
11
+ aphelion_sdk/cli/main.py,sha256=oxxmG-phVV9tk6TwMoWUIYHSJsB3tqnVNSVnXnjpr9g,6609
12
+ aphelion_sdk/host/__init__.py,sha256=7SYdewYdfYLAYxXqu97JE_KQlAXmWSnyPKbPVT99rPI,511
13
+ aphelion_sdk/host/constants.py,sha256=_M3EsBRX5IlhjrwQKwFAvtROrvIjP-XNv8iAJVfM2e8,704
14
+ aphelion_sdk/host/errors.py,sha256=uRRcUQjWjuwfS-F0sZ-QKj8WD39V7POt0MhdrhNJRQc,419
15
+ aphelion_sdk/host/install.py,sha256=yl-2DKiMx5-4rKCXET5qyZA1cL2RCKpWMwuJuAlG2xY,1973
16
+ aphelion_sdk/host/locate.py,sha256=lTFxnBD6_BKfn8LimDXx8IpqyNJtvnPKfx8IDTBcQRQ,2809
17
+ aphelion_sdk/host/models.py,sha256=XiCHewoaCDpNmOpyuwvkjIW1butlR4AyVODSEem4jmk,1063
18
+ aphelion_sdk/host/validate.py,sha256=gzHHbuUC7LJx0ywO55seWVOghZWrvUpcyaA2RZca0qc,2315
19
+ aphelion_sdk/host/windows.py,sha256=bCJf0-_5pNwDBegNJtvGaeFPgrXrhXP_961QL_Y_DHg,2607
20
+ aphelion_sdk/packaging/__init__.py,sha256=4WH_jAHxnxngsopPXcX3o2Y5bsXSa8oRGWO9XPhwfNg,666
21
+ aphelion_sdk/packaging/builder.py,sha256=qTtDsS9mXMaLyJDZr94jcl0oBvvQXh07_lOnEW_6O2c,7548
22
+ aphelion_sdk/packaging/discovery.py,sha256=mLV19IH2xVIQZcPAeL6YCfqLu6T2RU8s61GQF4x5Y_U,8979
23
+ aphelion_sdk/packaging/errors.py,sha256=ihIpSNHrBu_opkI8Mf-KGCbyiQh-T5TnPH_irV5TqqA,408
24
+ aphelion_sdk/packaging/names.py,sha256=MD7uWXWQSj8QZBU8GkbEPE-f4WAuAnvMLDUxoOIfd2k,4958
25
+ aphelion_sdk/packaging/project.py,sha256=Y0eTQfJhZyWmaXU4kSG-coXfUuyi3SJsgwagsrMN8I8,7822
26
+ aphelion_sdk/video/__init__.py,sha256=XHct9Eu4DixPmYkm6q3Gn4tLiuw1flnQ6rUvebnFeKc,211
27
+ aphelion_sdk/video/effect.py,sha256=b_YkK7zetpoi78RxcVVve9c7N6w2xV5X5_i5UIb88O0,3125
28
+ aphelion_sdk/widgets/__init__.py,sha256=9RDyHzzIgMm9RgNTyVqasuVHa2IblJDP2WiuT4z683c,737
29
+ aphelion_sdk/widgets/base.py,sha256=TtOpx6zfSNeafMH6Hsn9v_R3AvNHA_BD2pY8ztVcUAs,2841
30
+ aphelion_sdk/widgets/dialog.py,sha256=8s1_sw82UobBotm7EWTAjts2NYjPmNNuRxOFvTIWrUI,1669
31
+ aphelion_sdk/widgets/host.py,sha256=xjH6Ksuq97nl-I89IAielZZOQDIH-LdyO7KDOB2rApw,3512
32
+ aphelion_sdk/widgets/kinds.py,sha256=QU1u83-ReEShDk9Je7Di6yPJ4JWJPRu2nbzYueaBKzY,438
33
+ aphelion_sdk/widgets/panel.py,sha256=wPk8tUtfzgsRSY1HNMuP26N2tggX9wZKwSR2GhBGk6I,1237
34
+ aphelion_sdk/widgets/qt.py,sha256=hnXj3OchPzF4lMw15tnT9E0NJK_-0CDI4nPuzte1jIs,950
35
+ aphelion_plugin_sdk-0.1.0.dist-info/METADATA,sha256=IZgE7lNQ0bbGvJUg-i5rDDPcF4HeEfO_JCr5erelnkA,3660
36
+ aphelion_plugin_sdk-0.1.0.dist-info/WHEEL,sha256=YVMoNqKzERt-wjUZwJ33xBGAwnFl-4cqbYkTtWa4itE,91
37
+ aphelion_plugin_sdk-0.1.0.dist-info/entry_points.txt,sha256=st-HkX1WMH1MzN4mqFMZ4lQaAXgMhI-Nw-kh52d_sfI,60
38
+ aphelion_plugin_sdk-0.1.0.dist-info/top_level.txt,sha256=tx-w30xPZbHoZHSQdaIC6GatkPMGdnC1mh6hEixMgRw,13
39
+ aphelion_plugin_sdk-0.1.0.dist-info/RECORD,,
@@ -0,0 +1,5 @@
1
+ Wheel-Version: 1.0
2
+ Generator: setuptools (84.0.0)
3
+ Root-Is-Purelib: true
4
+ Tag: py3-none-any
5
+
@@ -0,0 +1,2 @@
1
+ [console_scripts]
2
+ aphelion-sdk = aphelion_sdk.cli.main:main
@@ -0,0 +1 @@
1
+ aphelion_sdk
@@ -0,0 +1,151 @@
1
+ """Aphelion Plugin SDK.
2
+
3
+ Public, stable surface for custom Aphelion plugins. Authors should
4
+ ``import aphelion_sdk`` and must never import editor internals
5
+ (``core``, ``effects``, ``render``, ``ui``).
6
+
7
+ Video effects subclass ``VideoEffectPlugin``. Attach UI with
8
+ ``widgets = (MyDialog, MyPanel)``. Simple widgets use host primitives;
9
+ advanced widgets may import PyQt6 and return a ``QWidget`` from
10
+ ``build_qt_widget`` or ``embed_native``.
11
+ """
12
+
13
+ from __future__ import annotations
14
+
15
+ from importlib import import_module
16
+ from typing import TYPE_CHECKING, Final
17
+
18
+ if TYPE_CHECKING:
19
+ from aphelion_sdk.plugin import Plugin
20
+ from aphelion_sdk.properties import (
21
+ PluginProperty,
22
+ choice_property,
23
+ color_property,
24
+ custom_property,
25
+ number_property,
26
+ slider_property,
27
+ text_property,
28
+ toggle_property,
29
+ )
30
+ from aphelion_sdk.registration import (
31
+ clear_registered_plugins,
32
+ discover_installed_plugins,
33
+ get_registered_plugins,
34
+ register_plugin,
35
+ )
36
+ from aphelion_sdk.types import ColorRgb, Frame
37
+ from aphelion_sdk.version import __version__
38
+ from aphelion_sdk.video import VideoEffectPlugin
39
+ from aphelion_sdk.widgets import (
40
+ DialogWidget,
41
+ PanelWidget,
42
+ PluginWidget,
43
+ WidgetContext,
44
+ WidgetHost,
45
+ WidgetView,
46
+ coerce_qt_parent,
47
+ is_qt_widget,
48
+ )
49
+
50
+ __all__ = [
51
+ "ColorRgb",
52
+ "DialogWidget",
53
+ "Frame",
54
+ "PanelWidget",
55
+ "Plugin",
56
+ "PluginProperty",
57
+ "PluginWidget",
58
+ "VideoEffectPlugin",
59
+ "WidgetContext",
60
+ "WidgetHost",
61
+ "WidgetView",
62
+ "__version__",
63
+ "choice_property",
64
+ "clear_registered_plugins",
65
+ "coerce_qt_parent",
66
+ "color_property",
67
+ "custom_property",
68
+ "discover_editors",
69
+ "discover_installed_plugins",
70
+ "get_registered_plugins",
71
+ "install_plugins_into_editor",
72
+ "locate_editor",
73
+ "is_qt_widget",
74
+ "number_property",
75
+ "register_plugin",
76
+ "slider_property",
77
+ "text_property",
78
+ "toggle_property",
79
+ ]
80
+
81
+ _EXPORT_MAP: Final[dict[str, tuple[str, str]]] = {
82
+ "ColorRgb": ("aphelion_sdk.types", "ColorRgb"),
83
+ "DialogWidget": ("aphelion_sdk.widgets.dialog", "DialogWidget"),
84
+ "Frame": ("aphelion_sdk.types", "Frame"),
85
+ "PanelWidget": ("aphelion_sdk.widgets.panel", "PanelWidget"),
86
+ "Plugin": ("aphelion_sdk.plugin", "Plugin"),
87
+ "PluginProperty": ("aphelion_sdk.properties", "PluginProperty"),
88
+ "PluginWidget": ("aphelion_sdk.widgets.base", "PluginWidget"),
89
+ "VideoEffectPlugin": ("aphelion_sdk.video", "VideoEffectPlugin"),
90
+ "WidgetContext": ("aphelion_sdk.widgets.host", "WidgetContext"),
91
+ "WidgetHost": ("aphelion_sdk.widgets.host", "WidgetHost"),
92
+ "WidgetView": ("aphelion_sdk.widgets.host", "WidgetView"),
93
+ "__version__": ("aphelion_sdk.version", "__version__"),
94
+ "choice_property": ("aphelion_sdk.properties", "choice_property"),
95
+ "clear_registered_plugins": (
96
+ "aphelion_sdk.registration",
97
+ "clear_registered_plugins",
98
+ ),
99
+ "coerce_qt_parent": ("aphelion_sdk.widgets.qt", "coerce_qt_parent"),
100
+ "color_property": ("aphelion_sdk.properties", "color_property"),
101
+ "custom_property": ("aphelion_sdk.properties", "custom_property"),
102
+ "discover_editors": ("aphelion_sdk.host.locate", "discover_editors"),
103
+ "discover_installed_plugins": (
104
+ "aphelion_sdk.registration",
105
+ "discover_installed_plugins",
106
+ ),
107
+ "install_plugins_into_editor": (
108
+ "aphelion_sdk.host.install",
109
+ "install_plugins_into_editor",
110
+ ),
111
+ "locate_editor": ("aphelion_sdk.host.locate", "locate_editor"),
112
+ "get_registered_plugins": (
113
+ "aphelion_sdk.registration",
114
+ "get_registered_plugins",
115
+ ),
116
+ "is_qt_widget": ("aphelion_sdk.widgets.qt", "is_qt_widget"),
117
+ "number_property": ("aphelion_sdk.properties", "number_property"),
118
+ "register_plugin": ("aphelion_sdk.registration", "register_plugin"),
119
+ "slider_property": ("aphelion_sdk.properties", "slider_property"),
120
+ "text_property": ("aphelion_sdk.properties", "text_property"),
121
+ "toggle_property": ("aphelion_sdk.properties", "toggle_property"),
122
+ }
123
+
124
+
125
+ def __getattr__(name: str) -> object:
126
+ """Resolve a public SDK symbol on first access.
127
+
128
+ Parameters:
129
+ name: Attribute requested on ``aphelion_sdk``.
130
+
131
+ Returns:
132
+ The exported object bound to ``name``.
133
+
134
+ Exceptions:
135
+ AttributeError: If ``name`` is not part of the public surface.
136
+
137
+ Side effects:
138
+ Imports the implementing submodule and caches the attribute.
139
+ """
140
+ location: tuple[str, str] | None = _EXPORT_MAP.get(name)
141
+ if location is None:
142
+ raise AttributeError(f"module {__name__!r} has no attribute {name!r}")
143
+ module_name, attr_name = location
144
+ value: object = getattr(import_module(module_name), attr_name)
145
+ globals()[name] = value
146
+ return value
147
+
148
+
149
+ def __dir__() -> list[str]:
150
+ """Return module attributes including lazy public exports."""
151
+ return sorted(set(globals()) | set(_EXPORT_MAP))
@@ -0,0 +1,10 @@
1
+ """Allow ``python -m aphelion_sdk`` to invoke the packaging CLI."""
2
+
3
+ from __future__ import annotations
4
+
5
+ import sys
6
+
7
+ from aphelion_sdk.cli.main import main
8
+
9
+ if __name__ == "__main__":
10
+ sys.exit(main())
@@ -0,0 +1,9 @@
1
+ """Audio plugin bases.
2
+
3
+ Not implemented in this release. Video effects use
4
+ ``aphelion_sdk.VideoEffectPlugin``.
5
+ """
6
+
7
+ from __future__ import annotations
8
+
9
+ __all__: list[str] = []
@@ -0,0 +1,7 @@
1
+ """Command-line interface for the Aphelion Plugin SDK."""
2
+
3
+ from __future__ import annotations
4
+
5
+ from aphelion_sdk.cli.main import main
6
+
7
+ __all__ = ["main"]
@@ -0,0 +1,203 @@
1
+ """Argparse entry for ``aphelion-sdk`` and ``python -m aphelion_sdk``."""
2
+
3
+ from __future__ import annotations
4
+
5
+ import argparse
6
+ import sys
7
+ from collections.abc import Sequence
8
+ from pathlib import Path
9
+
10
+ from aphelion_sdk.host import EditorHostError, EditorInstall, locate_editor
11
+ from aphelion_sdk.host.install import install_plugins_into_editor
12
+ from aphelion_sdk.packaging import (
13
+ PackageBuildResult,
14
+ PluginPackageError,
15
+ build_plugin_package,
16
+ )
17
+ from aphelion_sdk.version import DISTRIBUTION_NAME, __version__
18
+
19
+ _DESCRIPTION: str = (
20
+ "Build Aphelion plugins, locate the installed editor, and copy "
21
+ "drop-in plugins into that editor. Pass a .py file or plugin "
22
+ "directory to wrap it, or a project root with pyproject.toml "
23
+ "to run a standard PEP 517 build."
24
+ )
25
+
26
+
27
+ def main(argv: Sequence[str] | None = None) -> int:
28
+ """Parse CLI arguments and dispatch the requested command.
29
+
30
+ Parameters:
31
+ argv: Argument vector without the program name. Defaults to
32
+ ``sys.argv[1:]``.
33
+
34
+ Returns:
35
+ Process exit code. ``0`` on success, ``1`` on packaging errors,
36
+ ``2`` on usage errors (argparse).
37
+
38
+ Exceptions:
39
+ None. Packaging failures are printed to stderr.
40
+
41
+ Side effects:
42
+ May write wheel files and print status to stdout/stderr.
43
+ """
44
+ parser: argparse.ArgumentParser = _build_parser()
45
+ args: argparse.Namespace = parser.parse_args(list(argv) if argv is not None else None)
46
+ command: str | None = args.command
47
+ if command == "build":
48
+ return _handle_build(args)
49
+ if command == "locate":
50
+ return _handle_locate()
51
+ if command == "install":
52
+ return _handle_install(args)
53
+ parser.print_help()
54
+ return 2
55
+
56
+
57
+ def _build_parser() -> argparse.ArgumentParser:
58
+ """Return the root parser with the ``build`` subcommand."""
59
+ parser = argparse.ArgumentParser(prog="aphelion-sdk", description=_DESCRIPTION)
60
+ parser.add_argument(
61
+ "--version",
62
+ action="version",
63
+ version=f"{DISTRIBUTION_NAME} {__version__}",
64
+ )
65
+ subparsers = parser.add_subparsers(dest="command", metavar="COMMAND")
66
+ _add_build_parser(subparsers)
67
+ _add_locate_parser(subparsers)
68
+ _add_install_parser(subparsers)
69
+ return parser
70
+
71
+
72
+ def _add_build_parser(
73
+ subparsers: argparse._SubParsersAction[argparse.ArgumentParser],
74
+ ) -> None:
75
+ """Register ``aphelion-sdk build``."""
76
+ parser = subparsers.add_parser(
77
+ "build",
78
+ help="Build a pip wheel from plugin source or an existing project.",
79
+ )
80
+ parser.add_argument(
81
+ "source",
82
+ nargs="?",
83
+ type=Path,
84
+ default=Path.cwd(),
85
+ help="Plugin .py file, plugin directory, or project root (default: cwd).",
86
+ )
87
+ parser.add_argument(
88
+ "-o",
89
+ "--output",
90
+ type=Path,
91
+ default=Path.cwd() / "dist",
92
+ help="Directory for built artifacts (default: ./dist).",
93
+ )
94
+ _add_build_metadata_flags(parser)
95
+
96
+
97
+ def _add_locate_parser(
98
+ subparsers: argparse._SubParsersAction[argparse.ArgumentParser],
99
+ ) -> None:
100
+ """Register ``aphelion-sdk locate``."""
101
+ subparsers.add_parser(
102
+ "locate",
103
+ help="Print the Aphelion Editor install this SDK will target.",
104
+ )
105
+
106
+
107
+ def _add_install_parser(
108
+ subparsers: argparse._SubParsersAction[argparse.ArgumentParser],
109
+ ) -> None:
110
+ """Register ``aphelion-sdk install``."""
111
+ parser = subparsers.add_parser(
112
+ "install",
113
+ help="Copy plugin sources into the located editor's userdata/plugins.",
114
+ )
115
+ parser.add_argument(
116
+ "source",
117
+ type=Path,
118
+ help="Plugin .py file or directory of plugin modules.",
119
+ )
120
+
121
+
122
+ def _handle_locate() -> int:
123
+ """Print the discovered editor install."""
124
+ try:
125
+ install: EditorInstall = locate_editor()
126
+ except EditorHostError as exc:
127
+ print(f"error: {exc}", file=sys.stderr)
128
+ return 1
129
+ kind: str = "frozen" if install.is_frozen else "source"
130
+ print(f"root: {install.root}")
131
+ print(f"executable: {install.executable}")
132
+ print(f"plugins: {install.user_plugin_dir}")
133
+ print(f"version: {install.version or '(unknown)'}")
134
+ print(f"source: {install.source} ({kind})")
135
+ return 0
136
+
137
+
138
+ def _handle_install(args: argparse.Namespace) -> int:
139
+ """Copy plugin files into the located editor."""
140
+ try:
141
+ written: tuple[Path, ...] = install_plugins_into_editor(args.source)
142
+ except (EditorHostError, PluginPackageError) as exc:
143
+ print(f"error: {exc}", file=sys.stderr)
144
+ return 1
145
+ print(f"Installed {len(written)} plugin file(s) into {written[0].parent}")
146
+ for path in written:
147
+ print(f" {path}")
148
+ return 0
149
+
150
+
151
+ def _add_build_metadata_flags(parser: argparse.ArgumentParser) -> None:
152
+ """Attach optional package-identity flags to the build parser."""
153
+ parser.add_argument(
154
+ "-n",
155
+ "--name",
156
+ default=None,
157
+ help="Distribution name (generated packages only).",
158
+ )
159
+ parser.add_argument(
160
+ "--package-version",
161
+ default="0.1.0",
162
+ help="Package version for generated packages (default: 0.1.0).",
163
+ )
164
+ parser.add_argument(
165
+ "--author",
166
+ default=None,
167
+ help="Package author (generated packages only).",
168
+ )
169
+ parser.add_argument(
170
+ "--description",
171
+ default=None,
172
+ help="Package summary (generated packages only).",
173
+ )
174
+
175
+
176
+ def _handle_build(args: argparse.Namespace) -> int:
177
+ """Run the packaging builder and print the resulting artifacts."""
178
+ source: Path = args.source
179
+ output: Path = args.output
180
+ try:
181
+ result: PackageBuildResult = build_plugin_package(
182
+ source,
183
+ output,
184
+ distribution_name=args.name,
185
+ version=args.package_version,
186
+ author=args.author,
187
+ description=args.description,
188
+ )
189
+ except PluginPackageError as exc:
190
+ print(f"error: {exc}", file=sys.stderr)
191
+ return 1
192
+ _print_success(result)
193
+ return 0
194
+
195
+
196
+ def _print_success(result: PackageBuildResult) -> None:
197
+ """Write a short build summary to stdout."""
198
+ kind: str = "project" if result.mode == "project" else "plugin package"
199
+ print(f"Built {kind} {result.distribution_name} {result.version}")
200
+ for plugin in result.plugins:
201
+ print(f" plugin: {plugin.plugin_name} ({plugin.class_name})")
202
+ for artifact in result.artifacts:
203
+ print(f" {artifact}")
@@ -0,0 +1,16 @@
1
+ """Locate the installed Aphelion Editor and install drop-in plugins into it."""
2
+
3
+ from __future__ import annotations
4
+
5
+ from aphelion_sdk.host.errors import EditorHostError
6
+ from aphelion_sdk.host.install import install_plugins_into_editor
7
+ from aphelion_sdk.host.locate import discover_editors, locate_editor
8
+ from aphelion_sdk.host.models import EditorInstall
9
+
10
+ __all__ = [
11
+ "EditorHostError",
12
+ "EditorInstall",
13
+ "discover_editors",
14
+ "install_plugins_into_editor",
15
+ "locate_editor",
16
+ ]
@@ -0,0 +1,17 @@
1
+ """Shared names used to locate an Aphelion Editor install."""
2
+
3
+ from __future__ import annotations
4
+
5
+ from typing import Final
6
+
7
+ ENV_EDITOR_HOME: Final[str] = "APHELION_EDITOR_HOME"
8
+ HOST_MANIFEST_NAME: Final[str] = "aphelion-host.json"
9
+ EDITOR_EXE_NAME: Final[str] = "AphelionEditor.exe"
10
+ SOURCE_ENTRY_NAME: Final[str] = "main.py"
11
+ REGISTRY_KEY: Final[str] = r"Software\Aphelion\Editor"
12
+ REGISTRY_VALUE_PATH: Final[str] = "InstallPath"
13
+ REGISTRY_VALUE_EXE: Final[str] = "Executable"
14
+ USERDATA_DIR_NAME: Final[str] = "userdata"
15
+ PLUGINS_DIR_NAME: Final[str] = "plugins"
16
+ DEFAULT_USER_INSTALL: Final[str] = r"Aphelion\Aphelion Editor"
17
+ DEFAULT_MACHINE_INSTALL: Final[str] = r"Aphelion\Aphelion Editor"
@@ -0,0 +1,20 @@
1
+ """Errors raised while locating the editor or installing drop-in plugins."""
2
+
3
+ from __future__ import annotations
4
+
5
+
6
+ class EditorHostError(RuntimeError):
7
+ """Raised when the SDK cannot find or write into Aphelion Editor.
8
+
9
+ Parameters:
10
+ message: Human-readable explanation of what failed.
11
+
12
+ Returns:
13
+ None.
14
+
15
+ Exceptions:
16
+ None.
17
+
18
+ Side effects:
19
+ None.
20
+ """
@@ -0,0 +1,55 @@
1
+ """Copy plugin sources into the located editor's user plugin folder."""
2
+
3
+ from __future__ import annotations
4
+
5
+ import shutil
6
+ from pathlib import Path
7
+
8
+ from aphelion_sdk.host.errors import EditorHostError
9
+ from aphelion_sdk.host.locate import locate_editor
10
+ from aphelion_sdk.host.models import EditorInstall
11
+ from aphelion_sdk.packaging.discovery import plugin_source_files
12
+ from aphelion_sdk.packaging.errors import PluginPackageError
13
+
14
+
15
+ def install_plugins_into_editor(
16
+ source: Path,
17
+ *,
18
+ editor: EditorInstall | None = None,
19
+ ) -> tuple[Path, ...]:
20
+ """Copy plugin modules into the editor's ``userdata/plugins`` folder.
21
+
22
+ Parameters:
23
+ source: A ``.py`` file or a directory of plugin modules.
24
+ editor: Install to target. Defaults to ``locate_editor()``.
25
+
26
+ Returns:
27
+ Destination paths written on disk.
28
+
29
+ Exceptions:
30
+ EditorHostError: If the editor cannot be found or copy fails.
31
+ PluginPackageError: If ``source`` is not a valid plugin path.
32
+
33
+ Side effects:
34
+ Creates the user plugin directory and overwrites same-named files.
35
+ """
36
+ target: EditorInstall = editor if editor is not None else locate_editor()
37
+ files: tuple[Path, ...] = plugin_source_files(source)
38
+ destination_dir: Path = target.user_plugin_dir
39
+ destination_dir.mkdir(parents=True, exist_ok=True)
40
+ return _copy_plugin_files(files, destination_dir)
41
+
42
+
43
+ def _copy_plugin_files(files: tuple[Path, ...], destination_dir: Path) -> tuple[Path, ...]:
44
+ """Copy each plugin file into ``destination_dir``."""
45
+ written: list[Path] = []
46
+ for file_path in files:
47
+ dest: Path = destination_dir / file_path.name
48
+ try:
49
+ shutil.copy2(file_path, dest)
50
+ except OSError as exc:
51
+ raise EditorHostError(
52
+ f"Failed to install {file_path} -> {dest}: {exc}"
53
+ ) from exc
54
+ written.append(dest)
55
+ return tuple(written)