muscles-cli 1.0.0__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.
@@ -0,0 +1,165 @@
1
+ Metadata-Version: 2.4
2
+ Name: muscles-cli
3
+ Version: 1.0.0
4
+ Summary: Muscles CLI runtime for shared command routing and strategy execution
5
+ License: MIT
6
+ Author: Denis B.
7
+ Author-email: denis@butko.info
8
+ Requires-Python: >=3.10,<4.0
9
+ Classifier: License :: OSI Approved :: MIT License
10
+ Classifier: Programming Language :: Python :: 3
11
+ Classifier: Programming Language :: Python :: 3.10
12
+ Classifier: Programming Language :: Python :: 3.11
13
+ Classifier: Programming Language :: Python :: 3.12
14
+ Classifier: Programming Language :: Python :: 3.13
15
+ Classifier: Programming Language :: Python :: 3.14
16
+ Requires-Dist: dnspython (==2.6.1)
17
+ Requires-Dist: greenlet (==3.0.2)
18
+ Requires-Dist: jsonschema (==4.16.0)
19
+ Requires-Dist: muscles (>=1.0.0rc1,<2.0.0)
20
+ Requires-Dist: pyyaml (>=6.0.0,<6.1.0)
21
+ Requires-Dist: watchdog (>=4.0.0,<5.0.0)
22
+ Requires-Dist: werkzeug (==2.1.1)
23
+ Description-Content-Type: text/markdown
24
+
25
+ # Muscles CLI
26
+
27
+ `muscles-cli` is the console runtime for Muscles. It uses the same application
28
+ shape as the HTTP runtimes: an `ApplicationMeta` class owns a `Context`, and the
29
+ context delegates execution to `CliStrategy`.
30
+
31
+ ## Installation
32
+
33
+ ```bash
34
+ pip install muscles-cli
35
+ ```
36
+
37
+ Canonical ecosystem install matrix is documented in core:
38
+ [Muscles installation matrix](https://github.com/butkoden/muscles/blob/master/docs/installation.md).
39
+
40
+ ## Related Repositories
41
+
42
+ - [`muscles`](https://github.com/butkoden/muscles) - core contracts, actions, inspect output and canonical documentation.
43
+ - [`muscles-asgi`](https://github.com/butkoden/muscles-asgi) - ASGI runtime generated projects can target.
44
+ - [`muscles-wsgi`](https://github.com/butkoden/muscles-wsgi) - WSGI runtime generated projects can target.
45
+ - [`muscles-mcp`](https://github.com/butkoden/muscles-mcp) - MCP projection over the same action contracts.
46
+ - [`muscles-benchmarks`](https://github.com/butkoden/muscles-benchmarks) - CLI regression and architecture checks.
47
+
48
+ ## Project Scaffolding
49
+
50
+ Use canonical command `muscles new`:
51
+
52
+ ```bash
53
+ muscles new demo --runtime asgi
54
+ muscles new demo --runtime wsgi
55
+ muscles new demo --runtime cli
56
+ ```
57
+
58
+ Notes:
59
+
60
+ - `muscular new` is not a canonical command.
61
+ - Existing non-empty directory is rejected unless `--force` is used.
62
+
63
+ ## AI Workflow Commands
64
+
65
+ ```bash
66
+ muscles capabilities --json
67
+ muscles inspect --json --app app.application:App
68
+ muscles action list --app app.application:App --json
69
+ muscles action inspect bookings.echo --app app.application:App --json
70
+ muscles action run bookings.echo --app app.application:App --payload-json '{"title":"Call"}' --json
71
+ muscles routes --app app.application:App
72
+ muscles schemas --app app.application:App
73
+ muscles rules --app app.application:App
74
+ muscles cli --app app.application:App
75
+ muscles generate page Home --with-tests
76
+ muscles doctor --json
77
+ muscles test --doctor
78
+ ```
79
+
80
+ ## Quick Start
81
+
82
+ ```python
83
+ from muscles import ApplicationMeta, Context, cli
84
+ from muscles.cli import CliStrategy
85
+
86
+
87
+ class App(metaclass=ApplicationMeta):
88
+ context = Context(CliStrategy)
89
+
90
+ def run(self, *args):
91
+ return self.context.execute(*args, shutup=True)
92
+
93
+
94
+ @cli.group()
95
+ def bookings(*args):
96
+ """Booking commands."""
97
+ return True
98
+
99
+
100
+ @bookings.command(command_name="remove")
101
+ def remove_booking(*args):
102
+ booking_id = args[0]
103
+ return f"removed {booking_id}"
104
+
105
+
106
+ app = App()
107
+ assert app.run("bookings", "remove", "1") == "removed 1"
108
+ ```
109
+
110
+ ## Routing Model
111
+
112
+ Groups and commands form a tree. That makes CLI routing close to HTTP routing:
113
+
114
+ - `bookings remove 1` is a nested route with an argument;
115
+ - `bookings/list` can be normalized by an application before calling the CLI;
116
+ - groups can have handlers, but returning `True` lets execution continue to a
117
+ child command.
118
+
119
+ More detail: [docs/routing.md](docs/routing.md).
120
+
121
+ ## Core Stream Output
122
+
123
+ English: CLI progress output uses core `StreamEvent` items from
124
+ `StreamResult`. `render_stream_result()` can print human-readable progress/log
125
+ and result lines, or machine-readable JSON lines. The CLI layer only owns
126
+ presentation and exit code mapping; stream semantics stay in `muscles.core`.
127
+
128
+ Русский: CLI progress output использует core `StreamEvent` из `StreamResult`.
129
+ `render_stream_result()` может печатать human-readable progress/log/result
130
+ строки или machine-readable JSON lines. CLI слой отвечает только за presentation
131
+ и exit code mapping; stream semantics остаётся в `muscles.core`.
132
+
133
+ ## Arguments
134
+
135
+ Use `@cli.argument()` for named arguments and `@cli.flag()` for boolean flags.
136
+ Required prompted arguments use `input()` or `getpass.getpass()` when hidden.
137
+
138
+ For nested commands, define options on the parent group object:
139
+
140
+ ```python
141
+ @cli.group(command_name="bookings")
142
+ def bookings(*args):
143
+ return True
144
+
145
+ @bookings.command(command_name="list")
146
+ @bookings.argument("--limit", nargs=1, default="25")
147
+ def bookings_list(*args, limit):
148
+ return limit
149
+ ```
150
+
151
+ Canonical invocations for benchmark/automation scripts:
152
+
153
+ ```bash
154
+ bookings list --limit 10
155
+ bookings list --limit=10
156
+ ```
157
+
158
+ ## Development
159
+
160
+ Run tests with the core package on `PYTHONPATH`:
161
+
162
+ ```bash
163
+ PYTHONPATH=../muscles/src:src python -m pytest -q
164
+ ```
165
+
@@ -0,0 +1,140 @@
1
+ # Muscles CLI
2
+
3
+ `muscles-cli` is the console runtime for Muscles. It uses the same application
4
+ shape as the HTTP runtimes: an `ApplicationMeta` class owns a `Context`, and the
5
+ context delegates execution to `CliStrategy`.
6
+
7
+ ## Installation
8
+
9
+ ```bash
10
+ pip install muscles-cli
11
+ ```
12
+
13
+ Canonical ecosystem install matrix is documented in core:
14
+ [Muscles installation matrix](https://github.com/butkoden/muscles/blob/master/docs/installation.md).
15
+
16
+ ## Related Repositories
17
+
18
+ - [`muscles`](https://github.com/butkoden/muscles) - core contracts, actions, inspect output and canonical documentation.
19
+ - [`muscles-asgi`](https://github.com/butkoden/muscles-asgi) - ASGI runtime generated projects can target.
20
+ - [`muscles-wsgi`](https://github.com/butkoden/muscles-wsgi) - WSGI runtime generated projects can target.
21
+ - [`muscles-mcp`](https://github.com/butkoden/muscles-mcp) - MCP projection over the same action contracts.
22
+ - [`muscles-benchmarks`](https://github.com/butkoden/muscles-benchmarks) - CLI regression and architecture checks.
23
+
24
+ ## Project Scaffolding
25
+
26
+ Use canonical command `muscles new`:
27
+
28
+ ```bash
29
+ muscles new demo --runtime asgi
30
+ muscles new demo --runtime wsgi
31
+ muscles new demo --runtime cli
32
+ ```
33
+
34
+ Notes:
35
+
36
+ - `muscular new` is not a canonical command.
37
+ - Existing non-empty directory is rejected unless `--force` is used.
38
+
39
+ ## AI Workflow Commands
40
+
41
+ ```bash
42
+ muscles capabilities --json
43
+ muscles inspect --json --app app.application:App
44
+ muscles action list --app app.application:App --json
45
+ muscles action inspect bookings.echo --app app.application:App --json
46
+ muscles action run bookings.echo --app app.application:App --payload-json '{"title":"Call"}' --json
47
+ muscles routes --app app.application:App
48
+ muscles schemas --app app.application:App
49
+ muscles rules --app app.application:App
50
+ muscles cli --app app.application:App
51
+ muscles generate page Home --with-tests
52
+ muscles doctor --json
53
+ muscles test --doctor
54
+ ```
55
+
56
+ ## Quick Start
57
+
58
+ ```python
59
+ from muscles import ApplicationMeta, Context, cli
60
+ from muscles.cli import CliStrategy
61
+
62
+
63
+ class App(metaclass=ApplicationMeta):
64
+ context = Context(CliStrategy)
65
+
66
+ def run(self, *args):
67
+ return self.context.execute(*args, shutup=True)
68
+
69
+
70
+ @cli.group()
71
+ def bookings(*args):
72
+ """Booking commands."""
73
+ return True
74
+
75
+
76
+ @bookings.command(command_name="remove")
77
+ def remove_booking(*args):
78
+ booking_id = args[0]
79
+ return f"removed {booking_id}"
80
+
81
+
82
+ app = App()
83
+ assert app.run("bookings", "remove", "1") == "removed 1"
84
+ ```
85
+
86
+ ## Routing Model
87
+
88
+ Groups and commands form a tree. That makes CLI routing close to HTTP routing:
89
+
90
+ - `bookings remove 1` is a nested route with an argument;
91
+ - `bookings/list` can be normalized by an application before calling the CLI;
92
+ - groups can have handlers, but returning `True` lets execution continue to a
93
+ child command.
94
+
95
+ More detail: [docs/routing.md](docs/routing.md).
96
+
97
+ ## Core Stream Output
98
+
99
+ English: CLI progress output uses core `StreamEvent` items from
100
+ `StreamResult`. `render_stream_result()` can print human-readable progress/log
101
+ and result lines, or machine-readable JSON lines. The CLI layer only owns
102
+ presentation and exit code mapping; stream semantics stay in `muscles.core`.
103
+
104
+ Русский: CLI progress output использует core `StreamEvent` из `StreamResult`.
105
+ `render_stream_result()` может печатать human-readable progress/log/result
106
+ строки или machine-readable JSON lines. CLI слой отвечает только за presentation
107
+ и exit code mapping; stream semantics остаётся в `muscles.core`.
108
+
109
+ ## Arguments
110
+
111
+ Use `@cli.argument()` for named arguments and `@cli.flag()` for boolean flags.
112
+ Required prompted arguments use `input()` or `getpass.getpass()` when hidden.
113
+
114
+ For nested commands, define options on the parent group object:
115
+
116
+ ```python
117
+ @cli.group(command_name="bookings")
118
+ def bookings(*args):
119
+ return True
120
+
121
+ @bookings.command(command_name="list")
122
+ @bookings.argument("--limit", nargs=1, default="25")
123
+ def bookings_list(*args, limit):
124
+ return limit
125
+ ```
126
+
127
+ Canonical invocations for benchmark/automation scripts:
128
+
129
+ ```bash
130
+ bookings list --limit 10
131
+ bookings list --limit=10
132
+ ```
133
+
134
+ ## Development
135
+
136
+ Run tests with the core package on `PYTHONPATH`:
137
+
138
+ ```bash
139
+ PYTHONPATH=../muscles/src:src python -m pytest -q
140
+ ```
@@ -0,0 +1,32 @@
1
+ [build-system]
2
+ requires = ["poetry-core>=1.0.0"]
3
+ build-backend = "poetry.core.masonry.api"
4
+
5
+
6
+ [tool.poetry]
7
+ name = "muscles-cli"
8
+ version = "1.0.0"
9
+ description = "Muscles CLI runtime for shared command routing and strategy execution"
10
+ readme = "README.md"
11
+ authors = ["Denis B. <denis@butko.info>"]
12
+ license = "MIT"
13
+ packages = [{include = "muscles", from = "src"}]
14
+
15
+
16
+ [tool.poetry.dependencies]
17
+ python = ">=3.10,<4.0"
18
+ muscles = ">=1.0.0rc1,<2.0.0"
19
+ watchdog = "^4.0.0"
20
+ dnspython = "==2.6.1"
21
+ pyyaml = "~=6.0.0"
22
+ werkzeug = "==2.1.1"
23
+ greenlet = "==3.0.2"
24
+ jsonschema = "==4.16.0"
25
+
26
+
27
+ [tool.poetry.dev-dependencies]
28
+ pytest = "==7.1.3"
29
+ setuptools = "==69.0.2"
30
+
31
+ [tool.poetry.scripts]
32
+ muscles = "muscles.cli.tooling:main"
@@ -0,0 +1,26 @@
1
+ # Makefile
2
+
3
+ start:
4
+ docker-compose up -d
5
+ docker exec -it butkoinfo_core_1 bash
6
+
7
+
8
+ stop:
9
+ docker-compose up -d
10
+ docker exec -it butkoinfo-core-1 bash
11
+
12
+
13
+ console:
14
+ docker exec -it butkoinfo_core_1 bash
15
+
16
+
17
+ logs:
18
+ docker-compose logs
19
+
20
+
21
+ develop:
22
+ #docker exec -it butkoinfo_core_1 pipenv install
23
+ pipenv run python setup.py develop
24
+ pipenv install
25
+
26
+
@@ -0,0 +1,6 @@
1
+ # -*- coding: utf-8 -*-
2
+ __name__ = "Muscular CLI Framework"
3
+ __version__ = "1.0.0.dev"
4
+ __description__ = (
5
+ "Muscular CLI Framework"
6
+ )
@@ -0,0 +1,66 @@
1
+ from .cli import Colors
2
+ from .cli import Command
3
+ from .cli import Argument
4
+ from .cli import Group
5
+ from .cli import Console
6
+ from .cli import cli
7
+ from .cli import argsparse
8
+ from .cli import CliStrategy
9
+ from .cli import ConsoleErrorHandler
10
+ from .streaming import CliStreamRenderResult
11
+ from .streaming import render_stream_result
12
+ from .tooling import build_capabilities_payload
13
+ from .tooling import build_inspection_payload
14
+ from .tooling import detect_project_root
15
+ from .tooling import generate_artifact
16
+ from .tooling import load_app_from_entrypoint
17
+ from .tooling import main
18
+ from .tooling import run_doctor
19
+ from .tooling import run_project_tests
20
+ from .tooling import scaffold_project
21
+
22
+
23
+ _TOOLING_EXPORTS = {
24
+ "scaffold_project",
25
+ "main",
26
+ "build_capabilities_payload",
27
+ "load_app_from_entrypoint",
28
+ "build_inspection_payload",
29
+ "generate_artifact",
30
+ "detect_project_root",
31
+ "run_doctor",
32
+ "run_project_tests",
33
+ }
34
+
35
+
36
+ def __getattr__(name):
37
+ if name in _TOOLING_EXPORTS:
38
+ import importlib
39
+
40
+ tooling = importlib.import_module(".tooling", __name__)
41
+ return getattr(tooling, name)
42
+ raise AttributeError(name)
43
+
44
+
45
+ __all__ = (
46
+ "Colors",
47
+ "Command",
48
+ "Argument",
49
+ "Group",
50
+ "Console",
51
+ "cli",
52
+ "argsparse",
53
+ "CliStrategy",
54
+ "ConsoleErrorHandler",
55
+ "scaffold_project",
56
+ "build_capabilities_payload",
57
+ "load_app_from_entrypoint",
58
+ "build_inspection_payload",
59
+ "generate_artifact",
60
+ "detect_project_root",
61
+ "run_doctor",
62
+ "run_project_tests",
63
+ "CliStreamRenderResult",
64
+ "render_stream_result",
65
+ "main",
66
+ )
@@ -0,0 +1,5 @@
1
+ from .tooling import main
2
+
3
+
4
+ if __name__ == "__main__":
5
+ raise SystemExit(main())
@@ -0,0 +1,6 @@
1
+ from .colory import Colors
2
+ from .command import Command, Argument, Group
3
+ from .instance import Console, cli
4
+ from .func import argsparse
5
+ from .strategy import CliStrategy
6
+ from .error_handler import ConsoleErrorHandler
@@ -0,0 +1,44 @@
1
+ from __future__ import annotations
2
+ from abc import ABC, abstractmethod
3
+ from typing import Any
4
+
5
+
6
+ class CliCommand(ABC):
7
+ """
8
+ Интерфейс консольной команды или группы
9
+
10
+ """
11
+
12
+ _key = None
13
+ _parent = None
14
+
15
+ @property
16
+ def parent(self) -> CliCommand | None:
17
+ return self._parent
18
+
19
+ @parent.setter
20
+ def parent(self, parent: CliCommand | None):
21
+ self._parent = parent
22
+
23
+ @property
24
+ def key(self) -> list[str]:
25
+ return self._key or []
26
+
27
+ @key.setter
28
+ def key(self, key) -> None:
29
+ self._key = key
30
+
31
+ def add(self, command) -> None:
32
+ pass
33
+
34
+ def remove(self, command) -> None:
35
+ pass
36
+
37
+ @abstractmethod
38
+ def execute(self, *args, **kwargs) -> Any:
39
+ """
40
+ Базовый Компонент может сам реализовать некоторое поведение по умолчанию
41
+ или поручить это конкретным классам, объявив метод, содержащий поведение
42
+ абстрактным.
43
+ """
44
+ pass
@@ -0,0 +1,15 @@
1
+ from __future__ import annotations
2
+
3
+
4
+ class Colors:
5
+ HEADER = '\033[95m'
6
+ OKBLUE = '\033[94m'
7
+ OKCYAN = '\033[96m'
8
+ OKGREEN = '\033[92m'
9
+ WARNING = '\033[93m'
10
+ FAIL = '\033[91m'
11
+ ENDC = '\033[0m'
12
+ BOLD = '\033[1m'
13
+ UNDERLINE = '\033[4m'
14
+
15
+