cliss 0.1.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.
cliss-0.1.0/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 kernel
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.
cliss-0.1.0/PKG-INFO ADDED
@@ -0,0 +1,269 @@
1
+ Metadata-Version: 2.4
2
+ Name: cliss
3
+ Version: 0.1.0
4
+ Summary: cliss — A lightweight framework for building CLI applications on top of argparse
5
+ Requires-Python: >=3.9
6
+ Description-Content-Type: text/markdown
7
+ License-File: LICENSE
8
+ Dynamic: license-file
9
+
10
+ # cliss — A lightweight framework for building CLI applications on top of argparse
11
+
12
+ [![Python](https://img.shields.io/badge/python-3.9+-blue.svg)](https://python.org)
13
+ [![PyPI](https://img.shields.io/pypi/v/cliss.svg)](https://pypi.org/project/cliss/)
14
+ [![License](https://img.shields.io/badge/license-MIT-green.svg)](LICENSE)
15
+ [![Platform](https://img.shields.io/badge/platform-linux%20%7C%20macOS%20%7C%20windows-lightgrey)]()
16
+ [![Ruff](https://img.shields.io/badge/code%20style-ruff-261230?logo=ruff&logoColor=white)](https://docs.astral.sh/ruff/)
17
+
18
+ Write type-annotated Python functions, get a full CLI — automatic `--help`, validation, and async support with zero dependencies.
19
+
20
+ ## âœĻ Features
21
+
22
+ - **ðŸŠķ Zero Dependencies** — Pure stdlib: `argparse`, `asyncio`, `inspect`
23
+ - **🏷ïļ Type-Driven** — Automatic arguments from function signatures and type hints
24
+ - **ðŸ§Đ Flexible** — Declarative `Argument` objects, type inference, or both
25
+ - **⚡ Async-Native** — `async def` handlers with automatic event loop management
26
+ - **🌍 Global Args** — Define flags shared across all commands
27
+ - **🔧 argparse Access** — Full access to underlying parsers for advanced use
28
+
29
+ ## 🚀 Quick Start
30
+
31
+ ### Prerequisites
32
+ - Python 3.9+
33
+
34
+ ### Installation
35
+ ```bash
36
+ pip install cliss
37
+ ```
38
+
39
+ #### Via uv
40
+ ```bash
41
+ uv pip install cliss
42
+ ```
43
+
44
+ #### Via pipx (isolated environment)
45
+ ```bash
46
+ pipx install cliss
47
+ ```
48
+
49
+ #### From source (development)
50
+
51
+ ```bash
52
+ git clone https://github.com/yourusername/cliss.git && cd cliss
53
+ ```
54
+
55
+ **pip**
56
+ ```bash
57
+ pip install .
58
+ ```
59
+
60
+ **uv**
61
+ ```bash
62
+ uv pip install .
63
+ ```
64
+ **pipx**
65
+ ```bash
66
+ pipx install .
67
+ ```
68
+
69
+ ### Usage
70
+ ```python
71
+ from cliss import CLI
72
+
73
+ cli = CLI(name="todo", description="Task manager", version="1.0.0")
74
+
75
+ @cli.command()
76
+ def add(task: str, priority: int = 1, done: bool = False):
77
+ """Add a new task."""
78
+ status = "✓" if done else "○"
79
+ return f"[{status}] {task} (priority: {priority})"
80
+
81
+ if __name__ == "__main__":
82
+ cli.handle()
83
+ ```
84
+
85
+ ```bash
86
+ $ python todo.py add "Buy milk" --priority 2
87
+ [○] Buy milk (priority: 2)
88
+ $ python todo.py add "Call mom" --done --priority 3
89
+ [✓] Call mom (priority: 3)
90
+ ```
91
+
92
+ ## 📋 Commands
93
+
94
+ ### `CLI` class
95
+ ```python
96
+ CLI(name="myapp", description="...", version="1.0.0", auto_help=True)
97
+ ```
98
+ | Parameter | Type | Default | Description |
99
+ |-----------|------|---------|-------------|
100
+ | `name` | `str` | — | Program name in help output |
101
+ | `description` | `str` | — | Description in help output |
102
+ | `version` | `str` | — | Adds `--version` flag |
103
+ | `auto_help` | `bool` | `True` | Adds `--help` flag |
104
+
105
+ ### `Argument` class
106
+ ```python
107
+ Argument("--output", "-o", type=str, default=None, help="...", required=False, choices=["a","b"], action="store_true")
108
+ ```
109
+ | Parameter | Type | Default | Description |
110
+ |-----------|------|---------|-------------|
111
+ | `*flags` | `str` | — | Argument flags (e.g., `--output`, `-o`) |
112
+ | `type` | `type` | `str` | Value type for coercion |
113
+ | `default` | `Any` | `None` | Default value |
114
+ | `help` | `str` | `""` | Help text |
115
+ | `required` | `bool` | `False` | Make argument required |
116
+ | `choices` | `list` | — | Restrict allowed values |
117
+ | `action` | `str` | — | argparse action (`store_true`, etc.) |
118
+
119
+ ### Type → CLI Mapping
120
+ | Function Signature | CLI Argument |
121
+ |--------------------|--------------|
122
+ | `name: str` | Positional `name` |
123
+ | `count: int = 1` | `--count` with type `int`, default `1` |
124
+ | `verbose: bool = False` | `--verbose` flag (store_true) |
125
+ | `quiet: bool = True` | `--quiet` flag (store_false) |
126
+
127
+ ## 📖 Examples
128
+
129
+ ### CRUD Application
130
+ ```python
131
+ from cliss import CLI
132
+
133
+ cli = CLI(name="db", description="Key-value store")
134
+ db = {}
135
+
136
+ @cli.command()
137
+ def set(key: str, value: str):
138
+ """Store a value."""
139
+ db[key] = value
140
+ return f"OK: {key} = {value}"
141
+
142
+ @cli.command()
143
+ def get(key: str):
144
+ """Retrieve a value."""
145
+ return db.get(key, "Not found")
146
+
147
+ @cli.command()
148
+ def delete(key: str, force: bool = False):
149
+ """Delete a key."""
150
+ if force or key in db:
151
+ db.pop(key, None)
152
+ return f"Deleted: {key}"
153
+ return f"Not found: {key} (use --force)"
154
+
155
+ cli.handle()
156
+ ```
157
+
158
+ ### Explicit Arguments
159
+ ```python
160
+ from cliss import CLI, Argument
161
+
162
+ cli = CLI(name="convert")
163
+
164
+ @cli.command(arguments=[
165
+ Argument("input", help="Input file"),
166
+ Argument("--output", "-o", default="out.txt"),
167
+ Argument("--format", "-f", choices=["json", "csv"], default="json")
168
+ ])
169
+ def convert(input: str, output: str = "out.txt", format: str = "json"):
170
+ """Convert file format."""
171
+ return f"{input} -> {output} [{format}]"
172
+ ```
173
+
174
+ ### Async Handler
175
+ ```python
176
+ @cli.command()
177
+ async def fetch(url: str, retries: int = 3):
178
+ """Fetch data asynchronously."""
179
+ return f"Fetched {url} (retries: {retries})"
180
+ ```
181
+
182
+ ### Global Arguments
183
+ ```python
184
+ cli = CLI(name="myapp")
185
+ cli.add_global_argument("--verbose", "-v", action="store_true")
186
+
187
+ @cli.command()
188
+ def status(verbose: bool = False):
189
+ return "Detailed status..." if verbose else "OK"
190
+ ```
191
+ ```bash
192
+ $ myapp --verbose status
193
+ Detailed status...
194
+ ```
195
+
196
+ ## 📁 Project Structure
197
+ ```
198
+ cliss/
199
+ ├── cliss/
200
+ │ └── __init__.py # CLI, Argument classes
201
+ ├── pyproject.toml # Project metadata
202
+ ├── README.md # Documentation
203
+ └── LICENSE # MIT License
204
+ ```
205
+
206
+ ## 🔧 Requirements
207
+
208
+ | Dependency | Purpose |
209
+ |------------|---------|
210
+ | Python 3.9+ | Type hints, `inspect.signature` |
211
+
212
+ No external dependencies — stdlib only.
213
+
214
+ ## ❓ FAQ
215
+
216
+ ### Why cliss when argparse already works?
217
+
218
+ argparse is powerful but verbose. A simple app with 3 commands can easily require 100+ lines of parser setup. cliss reduces this to type-annotated functions — the boilerplate is inferred, not written.
219
+
220
+ ### What about Click/Typer/Fire?
221
+
222
+ | Tool | Dependencies | Style |
223
+ |------|-------------|-------|
224
+ | **cliss** | 0 (stdlib) | Decorators + type hints |
225
+ | Click | Click | Decorators |
226
+ | Typer | Click, typing-extensions | Type hints |
227
+ | Fire | 0 (stdlib) | Introspection |
228
+
229
+ cliss sits between Fire (zero-config, no validation) and Typer (rich features, heavy deps). It gives you type-driven CLI generation with argparse-compatible control, all in ~200 lines.
230
+
231
+ ### Can I use argparse features directly?
232
+
233
+ Yes. `cli.parser` and `cli.subparsers` are standard argparse objects. Add custom actions, mutually exclusive groups, or parent parsers as needed.
234
+
235
+ ### Does it support nested commands?
236
+
237
+ For subcommand groups, access `cli.subparsers` directly or use dotted command names:
238
+
239
+ ```python
240
+ @cli.command(name="compute:start")
241
+ def start(instance: str):
242
+ return f"Starting {instance}"
243
+ ```
244
+
245
+ ### How does async work?
246
+
247
+ If the command handler is `async def` or returns a coroutine, cliss automatically runs it with `asyncio.run()`. No manual event loop setup needed.
248
+
249
+ ## 🐛 Troubleshooting
250
+
251
+ | Issue | Solution |
252
+ |-------|----------|
253
+ | **Arguments not appearing** | Check that explicit `Argument` objects' `dest` matches parameter names |
254
+ | **Bool flag inverted** | `bool = False` → `store_true`, `bool = True` → `store_false` |
255
+ | **Type coercion fails** | argparse error message shown automatically |
256
+ | **Subcommand not found** | Verify command name: `func.__name__` with `_` → `-` unless overridden |
257
+
258
+ ## 📄 License
259
+
260
+ MIT License — see [LICENSE](LICENSE) file.
261
+
262
+ ## 🙏 Acknowledgments
263
+
264
+ - [argparse](https://docs.python.org/3/library/argparse.html) — The foundation this is built on
265
+
266
+ ---
267
+
268
+ **Repository:** [github.com/Fkernel653/cliss](https://github.com/Fkernel653/cliss)
269
+ **PyPI:** [pypi.org/project/cliss](https://pypi.org/project/cliss/)
cliss-0.1.0/README.md ADDED
@@ -0,0 +1,260 @@
1
+ # cliss — A lightweight framework for building CLI applications on top of argparse
2
+
3
+ [![Python](https://img.shields.io/badge/python-3.9+-blue.svg)](https://python.org)
4
+ [![PyPI](https://img.shields.io/pypi/v/cliss.svg)](https://pypi.org/project/cliss/)
5
+ [![License](https://img.shields.io/badge/license-MIT-green.svg)](LICENSE)
6
+ [![Platform](https://img.shields.io/badge/platform-linux%20%7C%20macOS%20%7C%20windows-lightgrey)]()
7
+ [![Ruff](https://img.shields.io/badge/code%20style-ruff-261230?logo=ruff&logoColor=white)](https://docs.astral.sh/ruff/)
8
+
9
+ Write type-annotated Python functions, get a full CLI — automatic `--help`, validation, and async support with zero dependencies.
10
+
11
+ ## âœĻ Features
12
+
13
+ - **ðŸŠķ Zero Dependencies** — Pure stdlib: `argparse`, `asyncio`, `inspect`
14
+ - **🏷ïļ Type-Driven** — Automatic arguments from function signatures and type hints
15
+ - **ðŸ§Đ Flexible** — Declarative `Argument` objects, type inference, or both
16
+ - **⚡ Async-Native** — `async def` handlers with automatic event loop management
17
+ - **🌍 Global Args** — Define flags shared across all commands
18
+ - **🔧 argparse Access** — Full access to underlying parsers for advanced use
19
+
20
+ ## 🚀 Quick Start
21
+
22
+ ### Prerequisites
23
+ - Python 3.9+
24
+
25
+ ### Installation
26
+ ```bash
27
+ pip install cliss
28
+ ```
29
+
30
+ #### Via uv
31
+ ```bash
32
+ uv pip install cliss
33
+ ```
34
+
35
+ #### Via pipx (isolated environment)
36
+ ```bash
37
+ pipx install cliss
38
+ ```
39
+
40
+ #### From source (development)
41
+
42
+ ```bash
43
+ git clone https://github.com/yourusername/cliss.git && cd cliss
44
+ ```
45
+
46
+ **pip**
47
+ ```bash
48
+ pip install .
49
+ ```
50
+
51
+ **uv**
52
+ ```bash
53
+ uv pip install .
54
+ ```
55
+ **pipx**
56
+ ```bash
57
+ pipx install .
58
+ ```
59
+
60
+ ### Usage
61
+ ```python
62
+ from cliss import CLI
63
+
64
+ cli = CLI(name="todo", description="Task manager", version="1.0.0")
65
+
66
+ @cli.command()
67
+ def add(task: str, priority: int = 1, done: bool = False):
68
+ """Add a new task."""
69
+ status = "✓" if done else "○"
70
+ return f"[{status}] {task} (priority: {priority})"
71
+
72
+ if __name__ == "__main__":
73
+ cli.handle()
74
+ ```
75
+
76
+ ```bash
77
+ $ python todo.py add "Buy milk" --priority 2
78
+ [○] Buy milk (priority: 2)
79
+ $ python todo.py add "Call mom" --done --priority 3
80
+ [✓] Call mom (priority: 3)
81
+ ```
82
+
83
+ ## 📋 Commands
84
+
85
+ ### `CLI` class
86
+ ```python
87
+ CLI(name="myapp", description="...", version="1.0.0", auto_help=True)
88
+ ```
89
+ | Parameter | Type | Default | Description |
90
+ |-----------|------|---------|-------------|
91
+ | `name` | `str` | — | Program name in help output |
92
+ | `description` | `str` | — | Description in help output |
93
+ | `version` | `str` | — | Adds `--version` flag |
94
+ | `auto_help` | `bool` | `True` | Adds `--help` flag |
95
+
96
+ ### `Argument` class
97
+ ```python
98
+ Argument("--output", "-o", type=str, default=None, help="...", required=False, choices=["a","b"], action="store_true")
99
+ ```
100
+ | Parameter | Type | Default | Description |
101
+ |-----------|------|---------|-------------|
102
+ | `*flags` | `str` | — | Argument flags (e.g., `--output`, `-o`) |
103
+ | `type` | `type` | `str` | Value type for coercion |
104
+ | `default` | `Any` | `None` | Default value |
105
+ | `help` | `str` | `""` | Help text |
106
+ | `required` | `bool` | `False` | Make argument required |
107
+ | `choices` | `list` | — | Restrict allowed values |
108
+ | `action` | `str` | — | argparse action (`store_true`, etc.) |
109
+
110
+ ### Type → CLI Mapping
111
+ | Function Signature | CLI Argument |
112
+ |--------------------|--------------|
113
+ | `name: str` | Positional `name` |
114
+ | `count: int = 1` | `--count` with type `int`, default `1` |
115
+ | `verbose: bool = False` | `--verbose` flag (store_true) |
116
+ | `quiet: bool = True` | `--quiet` flag (store_false) |
117
+
118
+ ## 📖 Examples
119
+
120
+ ### CRUD Application
121
+ ```python
122
+ from cliss import CLI
123
+
124
+ cli = CLI(name="db", description="Key-value store")
125
+ db = {}
126
+
127
+ @cli.command()
128
+ def set(key: str, value: str):
129
+ """Store a value."""
130
+ db[key] = value
131
+ return f"OK: {key} = {value}"
132
+
133
+ @cli.command()
134
+ def get(key: str):
135
+ """Retrieve a value."""
136
+ return db.get(key, "Not found")
137
+
138
+ @cli.command()
139
+ def delete(key: str, force: bool = False):
140
+ """Delete a key."""
141
+ if force or key in db:
142
+ db.pop(key, None)
143
+ return f"Deleted: {key}"
144
+ return f"Not found: {key} (use --force)"
145
+
146
+ cli.handle()
147
+ ```
148
+
149
+ ### Explicit Arguments
150
+ ```python
151
+ from cliss import CLI, Argument
152
+
153
+ cli = CLI(name="convert")
154
+
155
+ @cli.command(arguments=[
156
+ Argument("input", help="Input file"),
157
+ Argument("--output", "-o", default="out.txt"),
158
+ Argument("--format", "-f", choices=["json", "csv"], default="json")
159
+ ])
160
+ def convert(input: str, output: str = "out.txt", format: str = "json"):
161
+ """Convert file format."""
162
+ return f"{input} -> {output} [{format}]"
163
+ ```
164
+
165
+ ### Async Handler
166
+ ```python
167
+ @cli.command()
168
+ async def fetch(url: str, retries: int = 3):
169
+ """Fetch data asynchronously."""
170
+ return f"Fetched {url} (retries: {retries})"
171
+ ```
172
+
173
+ ### Global Arguments
174
+ ```python
175
+ cli = CLI(name="myapp")
176
+ cli.add_global_argument("--verbose", "-v", action="store_true")
177
+
178
+ @cli.command()
179
+ def status(verbose: bool = False):
180
+ return "Detailed status..." if verbose else "OK"
181
+ ```
182
+ ```bash
183
+ $ myapp --verbose status
184
+ Detailed status...
185
+ ```
186
+
187
+ ## 📁 Project Structure
188
+ ```
189
+ cliss/
190
+ ├── cliss/
191
+ │ └── __init__.py # CLI, Argument classes
192
+ ├── pyproject.toml # Project metadata
193
+ ├── README.md # Documentation
194
+ └── LICENSE # MIT License
195
+ ```
196
+
197
+ ## 🔧 Requirements
198
+
199
+ | Dependency | Purpose |
200
+ |------------|---------|
201
+ | Python 3.9+ | Type hints, `inspect.signature` |
202
+
203
+ No external dependencies — stdlib only.
204
+
205
+ ## ❓ FAQ
206
+
207
+ ### Why cliss when argparse already works?
208
+
209
+ argparse is powerful but verbose. A simple app with 3 commands can easily require 100+ lines of parser setup. cliss reduces this to type-annotated functions — the boilerplate is inferred, not written.
210
+
211
+ ### What about Click/Typer/Fire?
212
+
213
+ | Tool | Dependencies | Style |
214
+ |------|-------------|-------|
215
+ | **cliss** | 0 (stdlib) | Decorators + type hints |
216
+ | Click | Click | Decorators |
217
+ | Typer | Click, typing-extensions | Type hints |
218
+ | Fire | 0 (stdlib) | Introspection |
219
+
220
+ cliss sits between Fire (zero-config, no validation) and Typer (rich features, heavy deps). It gives you type-driven CLI generation with argparse-compatible control, all in ~200 lines.
221
+
222
+ ### Can I use argparse features directly?
223
+
224
+ Yes. `cli.parser` and `cli.subparsers` are standard argparse objects. Add custom actions, mutually exclusive groups, or parent parsers as needed.
225
+
226
+ ### Does it support nested commands?
227
+
228
+ For subcommand groups, access `cli.subparsers` directly or use dotted command names:
229
+
230
+ ```python
231
+ @cli.command(name="compute:start")
232
+ def start(instance: str):
233
+ return f"Starting {instance}"
234
+ ```
235
+
236
+ ### How does async work?
237
+
238
+ If the command handler is `async def` or returns a coroutine, cliss automatically runs it with `asyncio.run()`. No manual event loop setup needed.
239
+
240
+ ## 🐛 Troubleshooting
241
+
242
+ | Issue | Solution |
243
+ |-------|----------|
244
+ | **Arguments not appearing** | Check that explicit `Argument` objects' `dest` matches parameter names |
245
+ | **Bool flag inverted** | `bool = False` → `store_true`, `bool = True` → `store_false` |
246
+ | **Type coercion fails** | argparse error message shown automatically |
247
+ | **Subcommand not found** | Verify command name: `func.__name__` with `_` → `-` unless overridden |
248
+
249
+ ## 📄 License
250
+
251
+ MIT License — see [LICENSE](LICENSE) file.
252
+
253
+ ## 🙏 Acknowledgments
254
+
255
+ - [argparse](https://docs.python.org/3/library/argparse.html) — The foundation this is built on
256
+
257
+ ---
258
+
259
+ **Repository:** [github.com/Fkernel653/cliss](https://github.com/Fkernel653/cliss)
260
+ **PyPI:** [pypi.org/project/cliss](https://pypi.org/project/cliss/)
@@ -0,0 +1,233 @@
1
+ import argparse
2
+ import asyncio
3
+ import inspect
4
+ import sys
5
+ from typing import Any, Callable, Dict, List, Optional
6
+
7
+
8
+ class Argument:
9
+ """Description of an argument for a command."""
10
+
11
+ def __init__(
12
+ self,
13
+ *flags: str,
14
+ type: type = str,
15
+ default: Any = None,
16
+ help: str = "",
17
+ required: bool = False,
18
+ choices: Optional[List[Any]] = None,
19
+ action: Optional[str] = None,
20
+ ):
21
+ """
22
+ Initialize a command argument descriptor.
23
+
24
+ Args:
25
+ *flags: Argument flags (e.g., "--output", "-o").
26
+ type: Expected type of the argument value.
27
+ default: Default value if the argument is not provided.
28
+ help: Help text describing the argument.
29
+ required: Whether the argument must be provided.
30
+ choices: List of allowed values for the argument.
31
+ action: Custom argparse action (e.g., "store_true", "store_false").
32
+ """
33
+ self.flags = flags
34
+ self.type = type
35
+ self.default = default
36
+ self.help = help
37
+ self.required = required
38
+ self.choices = choices
39
+ self.action = action
40
+
41
+
42
+ class CLI:
43
+ """Advanced wrapper over argparse for building command-line interfaces."""
44
+
45
+ def __init__(
46
+ self,
47
+ name: Optional[str] = None,
48
+ description: Optional[str] = None,
49
+ version: Optional[str] = None,
50
+ auto_help: bool = True,
51
+ ):
52
+ """
53
+ Initialize the CLI application.
54
+
55
+ Args:
56
+ name: Name of the application (shown in help).
57
+ description: Description of the application (shown in help).
58
+ version: Version string for --version flag. If provided, adds automatic version display.
59
+ auto_help: Whether to automatically add a --help flag.
60
+ """
61
+ self.name = name
62
+ self.description = description
63
+ self.version = version
64
+ self.auto_help = auto_help
65
+
66
+ self.parser = argparse.ArgumentParser(
67
+ prog=name, description=description, add_help=auto_help
68
+ )
69
+
70
+ if version:
71
+ self.parser.add_argument("--version", action="version", version=version)
72
+
73
+ self.subparsers = self.parser.add_subparsers(dest="_command", title="Commands")
74
+ self._commands: Dict[str, dict] = {}
75
+ self._global_args: List[Argument] = []
76
+
77
+ def add_global_argument(self, *flags, **kwargs):
78
+ """
79
+ Add a global argument that applies to all commands.
80
+
81
+ Args:
82
+ *flags: Argument flags (e.g., "--verbose", "-v").
83
+ **kwargs: Additional keyword arguments passed to argparse.
84
+ """
85
+ self._global_args.append(Argument(*flags, **kwargs))
86
+ self.parser.add_argument(*flags, **kwargs)
87
+
88
+ def command(
89
+ self,
90
+ name: Optional[str] = None,
91
+ description: Optional[str] = None,
92
+ arguments: Optional[List[Argument]] = None,
93
+ **parser_kwargs,
94
+ ) -> Callable:
95
+ """
96
+ Decorator for creating a command.
97
+
98
+ You can pass a list of Argument objects or use type annotations in the function
99
+ to automatically generate arguments.
100
+
101
+ Args:
102
+ name: Name of the command. If not provided, uses the function name
103
+ with underscores replaced by hyphens.
104
+ description: Description of the command. If not provided, uses the
105
+ function's docstring.
106
+ arguments: Optional list of Argument objects describing the command's
107
+ arguments.
108
+ **parser_kwargs: Additional keyword arguments passed to the subparser.
109
+
110
+ Returns:
111
+ A decorator that registers the function as a command.
112
+ """
113
+
114
+ def decorator(func: Callable) -> Callable:
115
+ cmd_name = name or func.__name__.replace("_", "-")
116
+ cmd_help = description or (func.__doc__ or "").strip()
117
+
118
+ parser = self.subparsers.add_parser(
119
+ cmd_name,
120
+ help=cmd_help.split("\n")[0] if cmd_help else None,
121
+ description=cmd_help,
122
+ **parser_kwargs,
123
+ )
124
+
125
+ explicit_dests = set()
126
+
127
+ # Add explicitly specified arguments
128
+ if arguments:
129
+ for arg in arguments:
130
+ kwargs = {
131
+ "type": arg.type,
132
+ "default": arg.default,
133
+ "help": arg.help,
134
+ "required": arg.required,
135
+ "choices": arg.choices,
136
+ }
137
+
138
+ if arg.action:
139
+ kwargs["action"] = arg.action
140
+
141
+ action = parser.add_argument(*arg.flags, **kwargs)
142
+ explicit_dests.add(action.dest)
143
+
144
+ # Automatically add arguments from the function signature
145
+ sig = inspect.signature(func)
146
+ for param_name, param in sig.parameters.items():
147
+ if param_name in explicit_dests:
148
+ continue # Argument already explicitly added
149
+
150
+ if param.default is inspect.Parameter.empty:
151
+ # Positional argument
152
+ arg_type = (
153
+ param.annotation
154
+ if param.annotation != inspect.Parameter.empty
155
+ else str
156
+ )
157
+ parser.add_argument(param_name, type=arg_type, help=param_name)
158
+ else:
159
+ # Optional argument
160
+ flag = f"--{param_name.replace('_', '-')}"
161
+ is_bool = param.annotation == bool or (
162
+ isinstance(param.default, bool)
163
+ and param.annotation in (bool, inspect.Parameter.empty)
164
+ )
165
+
166
+ if is_bool:
167
+ action = "store_false" if param.default else "store_true"
168
+ parser.add_argument(
169
+ flag,
170
+ action=action,
171
+ default=param.default,
172
+ help=f"{param_name} (default: {param.default})",
173
+ )
174
+ else:
175
+ arg_type = (
176
+ param.annotation
177
+ if param.annotation != inspect.Parameter.empty
178
+ else type(param.default)
179
+ )
180
+ parser.add_argument(
181
+ flag,
182
+ type=arg_type,
183
+ default=param.default,
184
+ help=f"{param_name} (default: {param.default})",
185
+ )
186
+
187
+ self._commands[cmd_name] = {"func": func, "parser": parser}
188
+
189
+ return func
190
+
191
+ return decorator
192
+
193
+ def handle(self, args: Optional[List[str]] = None) -> None:
194
+ """
195
+ Parse command-line arguments and execute the appropriate command.
196
+
197
+ Args:
198
+ args: List of command-line arguments. If None, uses sys.argv[1:].
199
+
200
+ Raises:
201
+ SystemExit: If argument parsing fails or an error occurs during execution.
202
+ """
203
+ if args is None:
204
+ args = sys.argv[1:]
205
+
206
+ if not args:
207
+ self.parser.print_help()
208
+ return
209
+
210
+ try:
211
+ namespace = self.parser.parse_args(args)
212
+
213
+ command = getattr(namespace, "_command", None)
214
+ if not command or command not in self._commands:
215
+ self.parser.print_help()
216
+ return
217
+
218
+ cmd_data = self._commands[command]
219
+ func_kwargs = {k: v for k, v in vars(namespace).items() if k != "_command"}
220
+
221
+ result = cmd_data["func"](**func_kwargs)
222
+ if asyncio.iscoroutine(result):
223
+ result = asyncio.run(result)
224
+
225
+ if result is not None:
226
+ print(result)
227
+
228
+ except SystemExit as e:
229
+ if e.code is not None and e.code != 0:
230
+ raise
231
+ except (ValueError, TypeError) as e:
232
+ print(f"Error: {e}", file=sys.stderr)
233
+ sys.exit(1)
@@ -0,0 +1,269 @@
1
+ Metadata-Version: 2.4
2
+ Name: cliss
3
+ Version: 0.1.0
4
+ Summary: cliss — A lightweight framework for building CLI applications on top of argparse
5
+ Requires-Python: >=3.9
6
+ Description-Content-Type: text/markdown
7
+ License-File: LICENSE
8
+ Dynamic: license-file
9
+
10
+ # cliss — A lightweight framework for building CLI applications on top of argparse
11
+
12
+ [![Python](https://img.shields.io/badge/python-3.9+-blue.svg)](https://python.org)
13
+ [![PyPI](https://img.shields.io/pypi/v/cliss.svg)](https://pypi.org/project/cliss/)
14
+ [![License](https://img.shields.io/badge/license-MIT-green.svg)](LICENSE)
15
+ [![Platform](https://img.shields.io/badge/platform-linux%20%7C%20macOS%20%7C%20windows-lightgrey)]()
16
+ [![Ruff](https://img.shields.io/badge/code%20style-ruff-261230?logo=ruff&logoColor=white)](https://docs.astral.sh/ruff/)
17
+
18
+ Write type-annotated Python functions, get a full CLI — automatic `--help`, validation, and async support with zero dependencies.
19
+
20
+ ## âœĻ Features
21
+
22
+ - **ðŸŠķ Zero Dependencies** — Pure stdlib: `argparse`, `asyncio`, `inspect`
23
+ - **🏷ïļ Type-Driven** — Automatic arguments from function signatures and type hints
24
+ - **ðŸ§Đ Flexible** — Declarative `Argument` objects, type inference, or both
25
+ - **⚡ Async-Native** — `async def` handlers with automatic event loop management
26
+ - **🌍 Global Args** — Define flags shared across all commands
27
+ - **🔧 argparse Access** — Full access to underlying parsers for advanced use
28
+
29
+ ## 🚀 Quick Start
30
+
31
+ ### Prerequisites
32
+ - Python 3.9+
33
+
34
+ ### Installation
35
+ ```bash
36
+ pip install cliss
37
+ ```
38
+
39
+ #### Via uv
40
+ ```bash
41
+ uv pip install cliss
42
+ ```
43
+
44
+ #### Via pipx (isolated environment)
45
+ ```bash
46
+ pipx install cliss
47
+ ```
48
+
49
+ #### From source (development)
50
+
51
+ ```bash
52
+ git clone https://github.com/yourusername/cliss.git && cd cliss
53
+ ```
54
+
55
+ **pip**
56
+ ```bash
57
+ pip install .
58
+ ```
59
+
60
+ **uv**
61
+ ```bash
62
+ uv pip install .
63
+ ```
64
+ **pipx**
65
+ ```bash
66
+ pipx install .
67
+ ```
68
+
69
+ ### Usage
70
+ ```python
71
+ from cliss import CLI
72
+
73
+ cli = CLI(name="todo", description="Task manager", version="1.0.0")
74
+
75
+ @cli.command()
76
+ def add(task: str, priority: int = 1, done: bool = False):
77
+ """Add a new task."""
78
+ status = "✓" if done else "○"
79
+ return f"[{status}] {task} (priority: {priority})"
80
+
81
+ if __name__ == "__main__":
82
+ cli.handle()
83
+ ```
84
+
85
+ ```bash
86
+ $ python todo.py add "Buy milk" --priority 2
87
+ [○] Buy milk (priority: 2)
88
+ $ python todo.py add "Call mom" --done --priority 3
89
+ [✓] Call mom (priority: 3)
90
+ ```
91
+
92
+ ## 📋 Commands
93
+
94
+ ### `CLI` class
95
+ ```python
96
+ CLI(name="myapp", description="...", version="1.0.0", auto_help=True)
97
+ ```
98
+ | Parameter | Type | Default | Description |
99
+ |-----------|------|---------|-------------|
100
+ | `name` | `str` | — | Program name in help output |
101
+ | `description` | `str` | — | Description in help output |
102
+ | `version` | `str` | — | Adds `--version` flag |
103
+ | `auto_help` | `bool` | `True` | Adds `--help` flag |
104
+
105
+ ### `Argument` class
106
+ ```python
107
+ Argument("--output", "-o", type=str, default=None, help="...", required=False, choices=["a","b"], action="store_true")
108
+ ```
109
+ | Parameter | Type | Default | Description |
110
+ |-----------|------|---------|-------------|
111
+ | `*flags` | `str` | — | Argument flags (e.g., `--output`, `-o`) |
112
+ | `type` | `type` | `str` | Value type for coercion |
113
+ | `default` | `Any` | `None` | Default value |
114
+ | `help` | `str` | `""` | Help text |
115
+ | `required` | `bool` | `False` | Make argument required |
116
+ | `choices` | `list` | — | Restrict allowed values |
117
+ | `action` | `str` | — | argparse action (`store_true`, etc.) |
118
+
119
+ ### Type → CLI Mapping
120
+ | Function Signature | CLI Argument |
121
+ |--------------------|--------------|
122
+ | `name: str` | Positional `name` |
123
+ | `count: int = 1` | `--count` with type `int`, default `1` |
124
+ | `verbose: bool = False` | `--verbose` flag (store_true) |
125
+ | `quiet: bool = True` | `--quiet` flag (store_false) |
126
+
127
+ ## 📖 Examples
128
+
129
+ ### CRUD Application
130
+ ```python
131
+ from cliss import CLI
132
+
133
+ cli = CLI(name="db", description="Key-value store")
134
+ db = {}
135
+
136
+ @cli.command()
137
+ def set(key: str, value: str):
138
+ """Store a value."""
139
+ db[key] = value
140
+ return f"OK: {key} = {value}"
141
+
142
+ @cli.command()
143
+ def get(key: str):
144
+ """Retrieve a value."""
145
+ return db.get(key, "Not found")
146
+
147
+ @cli.command()
148
+ def delete(key: str, force: bool = False):
149
+ """Delete a key."""
150
+ if force or key in db:
151
+ db.pop(key, None)
152
+ return f"Deleted: {key}"
153
+ return f"Not found: {key} (use --force)"
154
+
155
+ cli.handle()
156
+ ```
157
+
158
+ ### Explicit Arguments
159
+ ```python
160
+ from cliss import CLI, Argument
161
+
162
+ cli = CLI(name="convert")
163
+
164
+ @cli.command(arguments=[
165
+ Argument("input", help="Input file"),
166
+ Argument("--output", "-o", default="out.txt"),
167
+ Argument("--format", "-f", choices=["json", "csv"], default="json")
168
+ ])
169
+ def convert(input: str, output: str = "out.txt", format: str = "json"):
170
+ """Convert file format."""
171
+ return f"{input} -> {output} [{format}]"
172
+ ```
173
+
174
+ ### Async Handler
175
+ ```python
176
+ @cli.command()
177
+ async def fetch(url: str, retries: int = 3):
178
+ """Fetch data asynchronously."""
179
+ return f"Fetched {url} (retries: {retries})"
180
+ ```
181
+
182
+ ### Global Arguments
183
+ ```python
184
+ cli = CLI(name="myapp")
185
+ cli.add_global_argument("--verbose", "-v", action="store_true")
186
+
187
+ @cli.command()
188
+ def status(verbose: bool = False):
189
+ return "Detailed status..." if verbose else "OK"
190
+ ```
191
+ ```bash
192
+ $ myapp --verbose status
193
+ Detailed status...
194
+ ```
195
+
196
+ ## 📁 Project Structure
197
+ ```
198
+ cliss/
199
+ ├── cliss/
200
+ │ └── __init__.py # CLI, Argument classes
201
+ ├── pyproject.toml # Project metadata
202
+ ├── README.md # Documentation
203
+ └── LICENSE # MIT License
204
+ ```
205
+
206
+ ## 🔧 Requirements
207
+
208
+ | Dependency | Purpose |
209
+ |------------|---------|
210
+ | Python 3.9+ | Type hints, `inspect.signature` |
211
+
212
+ No external dependencies — stdlib only.
213
+
214
+ ## ❓ FAQ
215
+
216
+ ### Why cliss when argparse already works?
217
+
218
+ argparse is powerful but verbose. A simple app with 3 commands can easily require 100+ lines of parser setup. cliss reduces this to type-annotated functions — the boilerplate is inferred, not written.
219
+
220
+ ### What about Click/Typer/Fire?
221
+
222
+ | Tool | Dependencies | Style |
223
+ |------|-------------|-------|
224
+ | **cliss** | 0 (stdlib) | Decorators + type hints |
225
+ | Click | Click | Decorators |
226
+ | Typer | Click, typing-extensions | Type hints |
227
+ | Fire | 0 (stdlib) | Introspection |
228
+
229
+ cliss sits between Fire (zero-config, no validation) and Typer (rich features, heavy deps). It gives you type-driven CLI generation with argparse-compatible control, all in ~200 lines.
230
+
231
+ ### Can I use argparse features directly?
232
+
233
+ Yes. `cli.parser` and `cli.subparsers` are standard argparse objects. Add custom actions, mutually exclusive groups, or parent parsers as needed.
234
+
235
+ ### Does it support nested commands?
236
+
237
+ For subcommand groups, access `cli.subparsers` directly or use dotted command names:
238
+
239
+ ```python
240
+ @cli.command(name="compute:start")
241
+ def start(instance: str):
242
+ return f"Starting {instance}"
243
+ ```
244
+
245
+ ### How does async work?
246
+
247
+ If the command handler is `async def` or returns a coroutine, cliss automatically runs it with `asyncio.run()`. No manual event loop setup needed.
248
+
249
+ ## 🐛 Troubleshooting
250
+
251
+ | Issue | Solution |
252
+ |-------|----------|
253
+ | **Arguments not appearing** | Check that explicit `Argument` objects' `dest` matches parameter names |
254
+ | **Bool flag inverted** | `bool = False` → `store_true`, `bool = True` → `store_false` |
255
+ | **Type coercion fails** | argparse error message shown automatically |
256
+ | **Subcommand not found** | Verify command name: `func.__name__` with `_` → `-` unless overridden |
257
+
258
+ ## 📄 License
259
+
260
+ MIT License — see [LICENSE](LICENSE) file.
261
+
262
+ ## 🙏 Acknowledgments
263
+
264
+ - [argparse](https://docs.python.org/3/library/argparse.html) — The foundation this is built on
265
+
266
+ ---
267
+
268
+ **Repository:** [github.com/Fkernel653/cliss](https://github.com/Fkernel653/cliss)
269
+ **PyPI:** [pypi.org/project/cliss](https://pypi.org/project/cliss/)
@@ -0,0 +1,8 @@
1
+ LICENSE
2
+ README.md
3
+ pyproject.toml
4
+ cliss/__init__.py
5
+ cliss.egg-info/PKG-INFO
6
+ cliss.egg-info/SOURCES.txt
7
+ cliss.egg-info/dependency_links.txt
8
+ cliss.egg-info/top_level.txt
@@ -0,0 +1,2 @@
1
+ cliss
2
+ dist
@@ -0,0 +1,13 @@
1
+ [build-system]
2
+ requires = ["setuptools>=61.0", "wheel"]
3
+ build-backend = "setuptools.build_meta"
4
+
5
+ [project]
6
+ name = "cliss"
7
+ version = "0.1.0"
8
+ description = "cliss — A lightweight framework for building CLI applications on top of argparse"
9
+ readme = "README.md"
10
+ requires-python = ">=3.9"
11
+
12
+ [tool.setuptools.packages.find]
13
+ where = ["."]
cliss-0.1.0/setup.cfg ADDED
@@ -0,0 +1,4 @@
1
+ [egg_info]
2
+ tag_build =
3
+ tag_date = 0
4
+