krypton-app 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.
@@ -0,0 +1,2 @@
1
+ # Made with ❤️ by [Vibey](https://the-vibey-project.github.io/vibey/), Developed by [Adam Matthew Steinberger](https://vibewithadam.matthewsteinberger.com/) ([@adammatthewsteinberger](https://github.com/adammatthewsteinberger/)).
2
+ """krypton-app: the apps of vibey, and the `krypton` command that launches them."""
@@ -0,0 +1,5 @@
1
+ """python -m krypton_app"""
2
+
3
+ from krypton_app.cli import main
4
+
5
+ raise SystemExit(main())
krypton_app/cli.py ADDED
@@ -0,0 +1,61 @@
1
+ """The `krypton` command."""
2
+
3
+ from __future__ import annotations
4
+
5
+ import argparse
6
+ import os
7
+ import sys
8
+ from collections.abc import Sequence
9
+ from importlib.metadata import PackageNotFoundError, version
10
+ from pathlib import Path
11
+
12
+ from krypton_app.launcher import KryptonLauncher
13
+
14
+
15
+ def _version() -> str:
16
+ # A module-level function because it is a one-line lookup with no state (ADR-0016).
17
+ try:
18
+ return version("krypton-app")
19
+ except PackageNotFoundError: # pragma: no cover - only in an uninstalled tree
20
+ return "0+unknown"
21
+
22
+
23
+ def _vibey_beside_me() -> str | None:
24
+ # A module-level function because it is a one-line lookup with no state (ADR-0016).
25
+ # The engine installed into the same environment as krypton-app comes first: a `vibey`
26
+ # earlier on PATH may be an older one from another environment.
27
+ candidate = Path(sys.executable).parent / "vibey"
28
+ return str(candidate) if os.access(candidate, os.X_OK) else None
29
+
30
+
31
+ def main(argv: Sequence[str] | None = None) -> int:
32
+ # A module-level function because a console-script entry point must be one (ADR-0016);
33
+ # everything it does is KryptonLauncher's.
34
+ parser = argparse.ArgumentParser(
35
+ prog="krypton",
36
+ description=(
37
+ "krypton, the apps of vibey. Starts the local vibey hub (`vibey serve`) and "
38
+ "opens it in your browser; if the installed vibey cannot serve it yet, says so "
39
+ "and lists what is available instead."
40
+ ),
41
+ )
42
+ parser.add_argument("--version", action="version", version=f"krypton {_version()}")
43
+ parser.add_argument(
44
+ "--host", default="127.0.0.1", help="address to serve on (default: 127.0.0.1)"
45
+ )
46
+ parser.add_argument("--port", type=int, default=8765, help="port to serve on (default: 8765)")
47
+ parser.add_argument(
48
+ "--no-browser", action="store_true", help="start the hub without opening it"
49
+ )
50
+ parser.add_argument(
51
+ "--vibey",
52
+ default=None,
53
+ help="the vibey program to use (default: the one beside krypton, else vibey on PATH)",
54
+ )
55
+ args = parser.parse_args(argv)
56
+ launcher = KryptonLauncher(out=sys.stdout, vibey=args.vibey or _vibey_beside_me())
57
+ return launcher.launch(args.host, args.port, open_browser=not args.no_browser)
58
+
59
+
60
+ if __name__ == "__main__": # pragma: no cover
61
+ raise SystemExit(main())
@@ -0,0 +1 @@
1
+ """Interfaces of krypton-app (ADR-0016)."""
@@ -0,0 +1,13 @@
1
+ """The contract of the krypton launcher (ADR-0016: an interface beside every class)."""
2
+
3
+ from __future__ import annotations
4
+
5
+ from abc import ABC, abstractmethod
6
+
7
+
8
+ class KryptonLauncherInterface(ABC):
9
+ """Starts the local vibey hub and opens it, or says plainly why it cannot."""
10
+
11
+ @abstractmethod
12
+ def launch(self, host: str, port: int, open_browser: bool) -> int:
13
+ """Run the hub until it exits; return the process exit status."""
@@ -0,0 +1,104 @@
1
+ """The krypton launcher: `vibey serve`, then the browser -- or an honest refusal."""
2
+
3
+ from __future__ import annotations
4
+
5
+ import subprocess
6
+ import time
7
+ import urllib.error
8
+ import urllib.request
9
+ import webbrowser
10
+ from collections.abc import Callable, Sequence
11
+ from dataclasses import dataclass, field
12
+ from shutil import which
13
+ from typing import IO, Protocol
14
+
15
+ from krypton_app.interfaces.krypton_launcher_interface import KryptonLauncherInterface
16
+
17
+ AVAILABLE_INSTEAD = (
18
+ "What you can use now:\n"
19
+ " vibey --help the vibey command line\n"
20
+ " vibey doctor what is installed and what is missing\n"
21
+ " the VS Code extension krypton inside your editor (clients/vscode)\n"
22
+ )
23
+
24
+
25
+ class _Process(Protocol):
26
+ def poll(self) -> int | None: ...
27
+ def wait(self) -> int: ...
28
+ def terminate(self) -> None: ...
29
+
30
+
31
+ def _probe(url: str) -> bool:
32
+ """Whether anything answers HTTP at `url`, whatever the status."""
33
+ try:
34
+ with urllib.request.urlopen(url, timeout=2): # nosec B310 - loopback URL we built
35
+ return True
36
+ except urllib.error.HTTPError:
37
+ return True
38
+ except (urllib.error.URLError, OSError):
39
+ return False
40
+
41
+
42
+ @dataclass
43
+ class KryptonLauncher(KryptonLauncherInterface):
44
+ """Launches the hub through the installed `vibey`, with every side effect injectable."""
45
+
46
+ out: IO[str]
47
+ vibey: str | None = None
48
+ ready_timeout: float = 60.0
49
+ find: Callable[[str], str | None] = which
50
+ run: Callable[[Sequence[str]], int] = field(
51
+ default=lambda argv: (
52
+ subprocess.run( # nosec B603 - argv, no shell
53
+ list(argv), capture_output=True, timeout=30, check=False
54
+ ).returncode
55
+ )
56
+ )
57
+ spawn: Callable[[Sequence[str]], _Process] = field(
58
+ default=lambda argv: subprocess.Popen(list(argv)) # nosec B603 - argv, no shell
59
+ )
60
+ probe: Callable[[str], bool] = _probe
61
+ browse: Callable[[str], object] = webbrowser.open
62
+ sleep: Callable[[float], None] = time.sleep
63
+ clock: Callable[[], float] = time.monotonic
64
+
65
+ def launch(self, host: str, port: int, open_browser: bool) -> int:
66
+ vibey = self.vibey or self.find("vibey")
67
+ if vibey is None:
68
+ self.out.write(
69
+ "krypton: the vibey command is not installed, so there is no hub to start.\n"
70
+ "Install the engine with: pip install vibey-engine\n"
71
+ )
72
+ return 1
73
+ if self.run([vibey, "serve", "--help"]) != 0:
74
+ self.out.write(
75
+ "krypton: the local web app is started by `vibey serve`, and the installed\n"
76
+ f"vibey ({vibey}) does not provide it yet. Nothing was started.\n"
77
+ + AVAILABLE_INSTEAD
78
+ )
79
+ return 1
80
+ url = f"http://{host}:{port}/"
81
+ process = self.spawn([vibey, "serve", "--host", host, "--port", str(port)])
82
+ deadline = self.clock() + self.ready_timeout
83
+ while not self.probe(url):
84
+ status = process.poll()
85
+ if status is not None:
86
+ self.out.write(
87
+ f"krypton: vibey serve exited with status {status} before it answered.\n"
88
+ )
89
+ return status or 1
90
+ if self.clock() >= deadline:
91
+ self.out.write(
92
+ f"krypton: nothing answered at {url} within {self.ready_timeout:g}s.\n"
93
+ )
94
+ process.terminate()
95
+ return 1
96
+ self.sleep(0.5)
97
+ self.out.write(f"krypton: the vibey hub is at {url}\n")
98
+ if open_browser:
99
+ self.browse(url)
100
+ try:
101
+ return process.wait()
102
+ except KeyboardInterrupt:
103
+ process.terminate()
104
+ return 130
krypton_app/py.typed ADDED
File without changes
@@ -0,0 +1,46 @@
1
+ Metadata-Version: 2.5
2
+ Name: krypton-app
3
+ Version: 0.1.0
4
+ Summary: krypton, the apps of vibey: launches the local vibey hub and opens it.
5
+ Project-URL: Homepage, https://the-vibey-project.github.io/vibey/
6
+ Project-URL: Source, https://github.com/the-vibey-project/vibey/tree/main/clients/krypton-app
7
+ Project-URL: Issues, https://github.com/the-vibey-project/vibey/issues
8
+ License: MIT
9
+ Requires-Python: >=3.12
10
+ Requires-Dist: vibey-engine
11
+ Provides-Extra: dev
12
+ Requires-Dist: mypy>=1.13; extra == 'dev'
13
+ Requires-Dist: pytest-cov>=6.0; extra == 'dev'
14
+ Requires-Dist: pytest>=8.3; extra == 'dev'
15
+ Requires-Dist: ruff>=0.8; extra == 'dev'
16
+ Description-Content-Type: text/markdown
17
+
18
+ # krypton-app
19
+
20
+ krypton is every app and interface of [vibey](https://the-vibey-project.github.io/vibey/).
21
+ This package carries them and installs one command, `krypton`.
22
+
23
+ ```bash
24
+ pip install krypton-app # brings vibey-engine with it
25
+ krypton # start the local vibey hub and open it in your browser
26
+ krypton --help
27
+ ```
28
+
29
+ ## What `krypton` does today
30
+
31
+ `krypton` is a launcher. It finds the `vibey` command, starts `vibey serve` on
32
+ `127.0.0.1:8765` (change it with `--host` and `--port`), waits until the hub answers, and
33
+ opens it in your browser. `--no-browser` only starts it.
34
+
35
+ When the installed vibey-engine has no `vibey serve` yet, `krypton` says so plainly, lists
36
+ what you can use instead, and exits with status 1. It never pretends a hub is running.
37
+
38
+ ## Releases
39
+
40
+ Published by `.github/workflows/krypton-app.yml` alone, by trusted publishing: a dev build
41
+ to TestPyPI (environment `testpypi`) on each push to `develop` that changes this tree or the
42
+ workflow, and a release to PyPI (environment `pypi`) on each such push to `main`. There is no
43
+ schedule. A push GitHub skips publishes nothing: a squash merge whose message quotes an old
44
+ `[skip ci]` subject skips every push workflow, which is how the 3.0.0 promotion first
45
+ published neither package. Merge a promotion by rebase, or edit the squash message. ADR-0069 records why vibey publishes exactly two packages, `vibey-engine` and
46
+ `krypton-app`.
@@ -0,0 +1,11 @@
1
+ krypton_app/__init__.py,sha256=RDf_IDp2ZN_S_behzNYyFGgBx9nsDQiVOTNHNstss3w,318
2
+ krypton_app/__main__.py,sha256=kXjgx3aDuOwiLYTQTF8o2g0Zg6f69Q-l7Yanf2xYNsk,88
3
+ krypton_app/cli.py,sha256=WCxi74-eBG3qnic0YlxRA4_7_O9j0vkpH5q4MT8UBmY,2354
4
+ krypton_app/launcher.py,sha256=YRqFVRXzTNATuU9S5hu1qq2SELZTGNLSNpH6j9AIuYs,3756
5
+ krypton_app/py.typed,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
6
+ krypton_app/interfaces/__init__.py,sha256=FFuGqyvWl8qYoA51z238K2vrQgRG-qQjLfeTUzSHx1s,44
7
+ krypton_app/interfaces/krypton_launcher_interface.py,sha256=TUzCwsbAw1NCQM3GYbm1nEW0Ixu-lo2vNs1friE9EhQ,448
8
+ krypton_app-0.1.0.dist-info/METADATA,sha256=zY2ksXPpH0u2uJyiZjtNbw64p-ITPUuj2BAppp_9weU,2089
9
+ krypton_app-0.1.0.dist-info/WHEEL,sha256=W3fkpkm7-wf9vBI5Z-7s0eWkeM-spu78I8Neb98DeEg,87
10
+ krypton_app-0.1.0.dist-info/entry_points.txt,sha256=SUniEOzAlnva7bhd4Aqm6RZ6CBQ87en-Pnpw7nY-lwE,49
11
+ krypton_app-0.1.0.dist-info/RECORD,,
@@ -0,0 +1,4 @@
1
+ Wheel-Version: 1.0
2
+ Generator: hatchling 1.32.4
3
+ Root-Is-Purelib: true
4
+ Tag: py3-none-any
@@ -0,0 +1,2 @@
1
+ [console_scripts]
2
+ krypton = krypton_app.cli:main