pybricks-cli 1.0.0b1__tar.gz
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.
- pybricks_cli-1.0.0b1/LICENSE +21 -0
- pybricks_cli-1.0.0b1/PKG-INFO +59 -0
- pybricks_cli-1.0.0b1/README.md +23 -0
- pybricks_cli-1.0.0b1/pyproject.toml +70 -0
- pybricks_cli-1.0.0b1/pyproject.toml.orig +54 -0
- pybricks_cli-1.0.0b1/src/pb_cli/__init__.py +8 -0
- pybricks_cli-1.0.0b1/src/pb_cli/__main__.py +7 -0
- pybricks_cli-1.0.0b1/src/pb_cli/app.py +127 -0
- pybricks_cli-1.0.0b1/src/pb_cli/banner.py +338 -0
- pybricks_cli-1.0.0b1/src/pb_cli/cli.py +123 -0
- pybricks_cli-1.0.0b1/src/pb_cli/commands/__init__.py +16 -0
- pybricks_cli-1.0.0b1/src/pb_cli/commands/hub.py +637 -0
- pybricks_cli-1.0.0b1/src/pb_cli/commands/program.py +374 -0
- pybricks_cli-1.0.0b1/src/pb_cli/commands/repl.py +146 -0
- pybricks_cli-1.0.0b1/src/pb_cli/commands/service.py +369 -0
- pybricks_cli-1.0.0b1/src/pb_cli/config.py +213 -0
- pybricks_cli-1.0.0b1/src/pb_cli/console/__init__.py +117 -0
- pybricks_cli-1.0.0b1/src/pb_cli/console/posix.py +125 -0
- pybricks_cli-1.0.0b1/src/pb_cli/console/windows.py +586 -0
- pybricks_cli-1.0.0b1/src/pb_cli/errors.py +680 -0
- pybricks_cli-1.0.0b1/src/pb_cli/escape.py +258 -0
- pybricks_cli-1.0.0b1/src/pb_cli/keys.py +332 -0
- pybricks_cli-1.0.0b1/src/pb_cli/picker.py +124 -0
- pybricks_cli-1.0.0b1/src/pb_cli/py.typed +0 -0
- pybricks_cli-1.0.0b1/src/pb_cli/session.py +813 -0
- pybricks_cli-1.0.0b1/src/pb_cli/ui.py +454 -0
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 The Pybricks Authors
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
|
@@ -0,0 +1,59 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: pybricks-cli
|
|
3
|
+
Version: 1.0.0b1
|
|
4
|
+
Summary: The Pybricks command line interface
|
|
5
|
+
Keywords: pybricks,lego,micropython,bluetooth,robotics
|
|
6
|
+
Author: The Pybricks Authors
|
|
7
|
+
Author-email: The Pybricks Authors <dev@pybricks.com>
|
|
8
|
+
License-Expression: MIT
|
|
9
|
+
License-File: LICENSE
|
|
10
|
+
Classifier: Development Status :: 4 - Beta
|
|
11
|
+
Classifier: Environment :: Console
|
|
12
|
+
Classifier: Intended Audience :: Developers
|
|
13
|
+
Classifier: Intended Audience :: Education
|
|
14
|
+
Classifier: Natural Language :: English
|
|
15
|
+
Classifier: Operating System :: OS Independent
|
|
16
|
+
Classifier: Topic :: Software Development :: Embedded Systems
|
|
17
|
+
Classifier: Typing :: Typed
|
|
18
|
+
Requires-Dist: pybricks-core>=1.0.0b1
|
|
19
|
+
Requires-Dist: pybricks-client>=1.0.0b1
|
|
20
|
+
Requires-Dist: pybricks-service>=1.0.0b1
|
|
21
|
+
Requires-Dist: anyio>=4.14
|
|
22
|
+
Requires-Dist: cyclopts>=4.22
|
|
23
|
+
Requires-Dist: rich>=13.7
|
|
24
|
+
Requires-Dist: typing-extensions>=4.15
|
|
25
|
+
Requires-Dist: watchfiles>=1.2
|
|
26
|
+
Maintainer: David Lechner
|
|
27
|
+
Maintainer-email: David Lechner <david@pybricks.com>
|
|
28
|
+
Requires-Python: >=3.11
|
|
29
|
+
Project-URL: homepage, https://pybricks.com
|
|
30
|
+
Project-URL: repository, https://github.com/pybricks/pybricks-tools
|
|
31
|
+
Project-URL: documentation, https://pybricks.com/pybricks-tools/cli/
|
|
32
|
+
Project-URL: Changelog, https://github.com/pybricks/pybricks-tools/blob/main/packages/pb-cli/CHANGELOG.md
|
|
33
|
+
Project-URL: Support, https://github.com/pybricks/support/discussions
|
|
34
|
+
Project-URL: Issues, https://github.com/pybricks/support/issues
|
|
35
|
+
Description-Content-Type: text/markdown
|
|
36
|
+
|
|
37
|
+
# pybricks-cli
|
|
38
|
+
|
|
39
|
+
The `pb` command: run Python programs on a [Pybricks](https://pybricks.com) hub
|
|
40
|
+
from your computer.
|
|
41
|
+
|
|
42
|
+
```bash
|
|
43
|
+
uv tool install pybricks-cli
|
|
44
|
+
```
|
|
45
|
+
|
|
46
|
+
Then switch a hub on:
|
|
47
|
+
|
|
48
|
+
```bash
|
|
49
|
+
pb connect # find a hub and connect to it
|
|
50
|
+
pb run demo.py # compile the script, send it, and run it
|
|
51
|
+
```
|
|
52
|
+
|
|
53
|
+
**The hub stays connected after the command finishes**, so the second `pb run`
|
|
54
|
+
starts instantly, and so does the hundredth.
|
|
55
|
+
|
|
56
|
+
`pb repl` talks to the hub's interpreter a line at a time, and `pb run -c` runs
|
|
57
|
+
a line or two of Python without a file. `pb --help` lists the rest.
|
|
58
|
+
|
|
59
|
+
> **Status:** beta. Commands and APIs may still change before 1.0.0.
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
# pybricks-cli
|
|
2
|
+
|
|
3
|
+
The `pb` command: run Python programs on a [Pybricks](https://pybricks.com) hub
|
|
4
|
+
from your computer.
|
|
5
|
+
|
|
6
|
+
```bash
|
|
7
|
+
uv tool install pybricks-cli
|
|
8
|
+
```
|
|
9
|
+
|
|
10
|
+
Then switch a hub on:
|
|
11
|
+
|
|
12
|
+
```bash
|
|
13
|
+
pb connect # find a hub and connect to it
|
|
14
|
+
pb run demo.py # compile the script, send it, and run it
|
|
15
|
+
```
|
|
16
|
+
|
|
17
|
+
**The hub stays connected after the command finishes**, so the second `pb run`
|
|
18
|
+
starts instantly, and so does the hundredth.
|
|
19
|
+
|
|
20
|
+
`pb repl` talks to the hub's interpreter a line at a time, and `pb run -c` runs
|
|
21
|
+
a line or two of Python without a file. `pb --help` lists the rest.
|
|
22
|
+
|
|
23
|
+
> **Status:** beta. Commands and APIs may still change before 1.0.0.
|
|
@@ -0,0 +1,70 @@
|
|
|
1
|
+
[project]
|
|
2
|
+
name = "pybricks-cli"
|
|
3
|
+
version = "1.0.0b1"
|
|
4
|
+
description = "The Pybricks command line interface"
|
|
5
|
+
readme = "README.md"
|
|
6
|
+
license = "MIT"
|
|
7
|
+
license-files = ["LICENSE"]
|
|
8
|
+
requires-python = ">=3.11"
|
|
9
|
+
keywords = [
|
|
10
|
+
"pybricks",
|
|
11
|
+
"lego",
|
|
12
|
+
"micropython",
|
|
13
|
+
"bluetooth",
|
|
14
|
+
"robotics",
|
|
15
|
+
]
|
|
16
|
+
classifiers = [
|
|
17
|
+
"Development Status :: 4 - Beta",
|
|
18
|
+
"Environment :: Console",
|
|
19
|
+
"Intended Audience :: Developers",
|
|
20
|
+
"Intended Audience :: Education",
|
|
21
|
+
"Natural Language :: English",
|
|
22
|
+
"Operating System :: OS Independent",
|
|
23
|
+
"Topic :: Software Development :: Embedded Systems",
|
|
24
|
+
"Typing :: Typed",
|
|
25
|
+
]
|
|
26
|
+
dependencies = [
|
|
27
|
+
"pybricks-core>=1.0.0b1",
|
|
28
|
+
"pybricks-client>=1.0.0b1",
|
|
29
|
+
"pybricks-service>=1.0.0b1",
|
|
30
|
+
"anyio>=4.14",
|
|
31
|
+
"cyclopts>=4.22",
|
|
32
|
+
"rich>=13.7",
|
|
33
|
+
"typing-extensions>=4.15",
|
|
34
|
+
"watchfiles>=1.2",
|
|
35
|
+
]
|
|
36
|
+
|
|
37
|
+
[[project.authors]]
|
|
38
|
+
name = "The Pybricks Authors"
|
|
39
|
+
email = "dev@pybricks.com"
|
|
40
|
+
|
|
41
|
+
[[project.maintainers]]
|
|
42
|
+
name = "David Lechner"
|
|
43
|
+
email = "david@pybricks.com"
|
|
44
|
+
|
|
45
|
+
[project.scripts]
|
|
46
|
+
pb = "pb_cli.cli:main"
|
|
47
|
+
|
|
48
|
+
[project.urls]
|
|
49
|
+
homepage = "https://pybricks.com"
|
|
50
|
+
repository = "https://github.com/pybricks/pybricks-tools"
|
|
51
|
+
documentation = "https://pybricks.com/pybricks-tools/cli/"
|
|
52
|
+
Changelog = "https://github.com/pybricks/pybricks-tools/blob/main/packages/pb-cli/CHANGELOG.md"
|
|
53
|
+
Support = "https://github.com/pybricks/support/discussions"
|
|
54
|
+
Issues = "https://github.com/pybricks/support/issues"
|
|
55
|
+
|
|
56
|
+
[build-system]
|
|
57
|
+
requires = ["uv_build>=0.12.7,<0.13.0"]
|
|
58
|
+
build-backend = "uv_build"
|
|
59
|
+
|
|
60
|
+
[tool.uv.build-backend]
|
|
61
|
+
module-name = "pb_cli"
|
|
62
|
+
|
|
63
|
+
[tool.uv.sources.pybricks-core]
|
|
64
|
+
workspace = true
|
|
65
|
+
|
|
66
|
+
[tool.uv.sources.pybricks-client]
|
|
67
|
+
workspace = true
|
|
68
|
+
|
|
69
|
+
[tool.uv.sources.pybricks-service]
|
|
70
|
+
workspace = true
|
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
[project]
|
|
2
|
+
name = "pybricks-cli"
|
|
3
|
+
version = "1.0.0b1"
|
|
4
|
+
description = "The Pybricks command line interface"
|
|
5
|
+
readme = "README.md"
|
|
6
|
+
license = "MIT"
|
|
7
|
+
license-files = ["LICENSE"]
|
|
8
|
+
authors = [{ name = "The Pybricks Authors", email = "dev@pybricks.com" }]
|
|
9
|
+
maintainers = [{ name = "David Lechner", email = "david@pybricks.com" }]
|
|
10
|
+
requires-python = ">=3.11"
|
|
11
|
+
keywords = ["pybricks", "lego", "micropython", "bluetooth", "robotics"]
|
|
12
|
+
classifiers = [
|
|
13
|
+
"Development Status :: 4 - Beta",
|
|
14
|
+
"Environment :: Console",
|
|
15
|
+
"Intended Audience :: Developers",
|
|
16
|
+
"Intended Audience :: Education",
|
|
17
|
+
"Natural Language :: English",
|
|
18
|
+
"Operating System :: OS Independent",
|
|
19
|
+
"Topic :: Software Development :: Embedded Systems",
|
|
20
|
+
"Typing :: Typed",
|
|
21
|
+
]
|
|
22
|
+
dependencies = [
|
|
23
|
+
"pybricks-core>=1.0.0b1",
|
|
24
|
+
"pybricks-client>=1.0.0b1",
|
|
25
|
+
"pybricks-service>=1.0.0b1",
|
|
26
|
+
"anyio>=4.14",
|
|
27
|
+
"cyclopts>=4.22",
|
|
28
|
+
"rich>=13.7",
|
|
29
|
+
"typing-extensions>=4.15",
|
|
30
|
+
"watchfiles>=1.2",
|
|
31
|
+
]
|
|
32
|
+
|
|
33
|
+
[project.scripts]
|
|
34
|
+
pb = "pb_cli.cli:main"
|
|
35
|
+
|
|
36
|
+
[project.urls]
|
|
37
|
+
homepage = "https://pybricks.com"
|
|
38
|
+
repository = "https://github.com/pybricks/pybricks-tools"
|
|
39
|
+
documentation = "https://pybricks.com/pybricks-tools/cli/"
|
|
40
|
+
"Changelog" = "https://github.com/pybricks/pybricks-tools/blob/main/packages/pb-cli/CHANGELOG.md"
|
|
41
|
+
"Support" = "https://github.com/pybricks/support/discussions"
|
|
42
|
+
"Issues" = "https://github.com/pybricks/support/issues"
|
|
43
|
+
|
|
44
|
+
[build-system]
|
|
45
|
+
requires = ["uv_build>=0.12.7,<0.13.0"]
|
|
46
|
+
build-backend = "uv_build"
|
|
47
|
+
|
|
48
|
+
[tool.uv.build-backend]
|
|
49
|
+
module-name = "pb_cli"
|
|
50
|
+
|
|
51
|
+
[tool.uv.sources]
|
|
52
|
+
pybricks-core = { workspace = true }
|
|
53
|
+
pybricks-client = { workspace = true }
|
|
54
|
+
pybricks-service = { workspace = true }
|
|
@@ -0,0 +1,127 @@
|
|
|
1
|
+
# SPDX-License-Identifier: MIT
|
|
2
|
+
# Copyright (c) 2026 The Pybricks Authors
|
|
3
|
+
|
|
4
|
+
"""The command line's shape: the app itself, its groups, and shared options.
|
|
5
|
+
|
|
6
|
+
Separate from the commands so that the command modules can all import it
|
|
7
|
+
without importing each other. :mod:`pb_cli.cli` pulls those modules in, which is
|
|
8
|
+
what registers them.
|
|
9
|
+
"""
|
|
10
|
+
|
|
11
|
+
from __future__ import annotations
|
|
12
|
+
|
|
13
|
+
import logging
|
|
14
|
+
import sys
|
|
15
|
+
from collections.abc import Sequence
|
|
16
|
+
from typing import Annotated
|
|
17
|
+
|
|
18
|
+
import cyclopts
|
|
19
|
+
|
|
20
|
+
from pb_cli import __version__
|
|
21
|
+
from pb_cli.config import config_sources
|
|
22
|
+
from pb_core.logging import TRACE, setup_logging
|
|
23
|
+
|
|
24
|
+
__all__ = [
|
|
25
|
+
"HUB_COMMANDS",
|
|
26
|
+
"PROGRAM_COMMANDS",
|
|
27
|
+
"SERVICE_COMMANDS",
|
|
28
|
+
"Verbose",
|
|
29
|
+
"app",
|
|
30
|
+
"configure_logging",
|
|
31
|
+
"service_app",
|
|
32
|
+
]
|
|
33
|
+
|
|
34
|
+
app = cyclopts.App(
|
|
35
|
+
name="pb",
|
|
36
|
+
version=__version__,
|
|
37
|
+
help=(
|
|
38
|
+
"Talk to Pybricks hubs.\n\n"
|
|
39
|
+
"A hub stays connected between commands, so you only need to run "
|
|
40
|
+
"pb connect once."
|
|
41
|
+
),
|
|
42
|
+
# Every command is in a group of its own, so what is left in the default
|
|
43
|
+
# panel is --help and --version. Calling that panel "Commands" would be a
|
|
44
|
+
# lie, and putting it last keeps the commands at the top where someone
|
|
45
|
+
# reading the help is looking.
|
|
46
|
+
group_commands=cyclopts.Group("Options", sort_key=3),
|
|
47
|
+
config=config_sources(),
|
|
48
|
+
)
|
|
49
|
+
|
|
50
|
+
HUB_COMMANDS = cyclopts.Group("Hubs", sort_key=0)
|
|
51
|
+
"""Finding a hub, holding on to one, and asking what it is doing.
|
|
52
|
+
|
|
53
|
+
Ordered by what someone actually has to do, which is why each command carries
|
|
54
|
+
an explicit ``sort_key`` rather than falling back to alphabetical. ``connect``
|
|
55
|
+
comes first, because it is the one command nobody gets anywhere without, with
|
|
56
|
+
its opposite ``disconnect`` beside it.
|
|
57
|
+
|
|
58
|
+
The rest are things a user may want rather than steps they must take, so they
|
|
59
|
+
come after: ``status`` and ``list`` report on what is already connected, and
|
|
60
|
+
``scan`` comes last because nobody has to run it at all -- ``connect`` does its
|
|
61
|
+
own looking, and ``connect --scan`` shows the same list when the choice
|
|
62
|
+
matters.
|
|
63
|
+
"""
|
|
64
|
+
|
|
65
|
+
PROGRAM_COMMANDS = cyclopts.Group("Programs", sort_key=1)
|
|
66
|
+
"""Getting a program onto a hub and controlling it once it is there."""
|
|
67
|
+
|
|
68
|
+
SERVICE_COMMANDS = cyclopts.Group("Background service", sort_key=2)
|
|
69
|
+
"""The thing holding the connections open. Rarely needed, so it comes last."""
|
|
70
|
+
|
|
71
|
+
service_app = cyclopts.App(
|
|
72
|
+
name="service",
|
|
73
|
+
help="Manage the background service that keeps hubs connected.",
|
|
74
|
+
group=SERVICE_COMMANDS,
|
|
75
|
+
# Not inherited from the root app, where the default panel holds only the
|
|
76
|
+
# flags: down here it really is the list of commands.
|
|
77
|
+
group_commands=cyclopts.Group("Commands"),
|
|
78
|
+
)
|
|
79
|
+
app.command(service_app)
|
|
80
|
+
|
|
81
|
+
|
|
82
|
+
Verbose = Annotated[
|
|
83
|
+
Sequence[bool],
|
|
84
|
+
cyclopts.Parameter(
|
|
85
|
+
name=["-v", "--verbose"],
|
|
86
|
+
help="Increase log detail. Repeat for more (-v, -vv, -vvv).",
|
|
87
|
+
show_default=False,
|
|
88
|
+
# No --no-verbose: there is nothing to turn off, and offering it
|
|
89
|
+
# would be one more thing in every command's help for no reason.
|
|
90
|
+
negative=(),
|
|
91
|
+
),
|
|
92
|
+
]
|
|
93
|
+
"""How loud to be, counted rather than measured.
|
|
94
|
+
|
|
95
|
+
A sequence of flags rather than a number, because that is what makes the help
|
|
96
|
+
true. As an ``int`` this took a value -- ``-v 1`` worked and ``-v`` was an
|
|
97
|
+
error saying it "requires an argument", while every command's help said to
|
|
98
|
+
repeat it. Collecting the flags gives ``-v``, ``-v -v`` and the stacked
|
|
99
|
+
``-vv``, which is what anybody typing it expects.
|
|
100
|
+
"""
|
|
101
|
+
|
|
102
|
+
|
|
103
|
+
def configure_logging(verbose: Verbose | int = ()) -> None:
|
|
104
|
+
"""Map repeated ``-v`` flags onto log levels.
|
|
105
|
+
|
|
106
|
+
Args:
|
|
107
|
+
verbose: The flags as they were given, or how many there
|
|
108
|
+
were. Both, because the commands hand over what they
|
|
109
|
+
were passed and a count reads better everywhere else.
|
|
110
|
+
"""
|
|
111
|
+
count = verbose if isinstance(verbose, int) else len(verbose)
|
|
112
|
+
if count <= 0:
|
|
113
|
+
level = logging.WARNING
|
|
114
|
+
elif count == 1:
|
|
115
|
+
level = logging.INFO
|
|
116
|
+
elif count == 2:
|
|
117
|
+
level = logging.DEBUG
|
|
118
|
+
else:
|
|
119
|
+
level = TRACE
|
|
120
|
+
|
|
121
|
+
setup_logging(
|
|
122
|
+
level=level,
|
|
123
|
+
style="rich" if sys.stderr.isatty() else "plain",
|
|
124
|
+
# bleak is noisy enough to bury our own lines, and is only interesting
|
|
125
|
+
# once someone is debugging the radio itself.
|
|
126
|
+
quiet_loggers=("bleak", "asyncio") if count < 3 else (),
|
|
127
|
+
)
|