cliyard 0.3.2__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.
- cliyard-0.3.2/LICENSE +21 -0
- cliyard-0.3.2/PKG-INFO +120 -0
- cliyard-0.3.2/README.md +92 -0
- cliyard-0.3.2/pyproject.toml +40 -0
- cliyard-0.3.2/setup.cfg +4 -0
- cliyard-0.3.2/src/cliyard/__init__.py +9 -0
- cliyard-0.3.2/src/cliyard/cli/__init__.py +1 -0
- cliyard-0.3.2/src/cliyard/cli/__main__.py +96 -0
- cliyard-0.3.2/src/cliyard/cli/auth_cmd.py +167 -0
- cliyard-0.3.2/src/cliyard/cli/gen.py +412 -0
- cliyard-0.3.2/src/cliyard/client/__init__.py +4 -0
- cliyard-0.3.2/src/cliyard/client/auth.py +523 -0
- cliyard-0.3.2/src/cliyard/client/credentials.py +154 -0
- cliyard-0.3.2/src/cliyard/client/http.py +113 -0
- cliyard-0.3.2/src/cliyard/engine/__init__.py +22 -0
- cliyard-0.3.2/src/cliyard/engine/assembler.py +281 -0
- cliyard-0.3.2/src/cliyard/engine/binder.py +112 -0
- cliyard-0.3.2/src/cliyard/engine/builder.py +595 -0
- cliyard-0.3.2/src/cliyard/engine/error_handler.py +24 -0
- cliyard-0.3.2/src/cliyard/engine/errors.py +46 -0
- cliyard-0.3.2/src/cliyard/engine/hooks.py +28 -0
- cliyard-0.3.2/src/cliyard/engine/loader.py +218 -0
- cliyard-0.3.2/src/cliyard/engine/template.py +212 -0
- cliyard-0.3.2/src/cliyard/output/__init__.py +6 -0
- cliyard-0.3.2/src/cliyard/output/formatter.py +187 -0
- cliyard-0.3.2/src/cliyard/output/handler.py +96 -0
- cliyard-0.3.2/src/cliyard/plugin/__init__.py +195 -0
- cliyard-0.3.2/src/cliyard/plugin/discovery.py +107 -0
- cliyard-0.3.2/src/cliyard/runtime/__init__.py +25 -0
- cliyard-0.3.2/src/cliyard/runtime/auth_commands.py +195 -0
- cliyard-0.3.2/src/cliyard/runtime/runner.py +241 -0
- cliyard-0.3.2/src/cliyard/schema/__init__.py +34 -0
- cliyard-0.3.2/src/cliyard/schema/types.py +237 -0
- cliyard-0.3.2/src/cliyard/schema/validator.py +205 -0
- cliyard-0.3.2/src/cliyard/validate/__init__.py +17 -0
- cliyard-0.3.2/src/cliyard/validate/dependency.py +63 -0
- cliyard-0.3.2/src/cliyard/validate/types.py +291 -0
- cliyard-0.3.2/src/cliyard.egg-info/PKG-INFO +120 -0
- cliyard-0.3.2/src/cliyard.egg-info/SOURCES.txt +46 -0
- cliyard-0.3.2/src/cliyard.egg-info/dependency_links.txt +1 -0
- cliyard-0.3.2/src/cliyard.egg-info/entry_points.txt +2 -0
- cliyard-0.3.2/src/cliyard.egg-info/requires.txt +6 -0
- cliyard-0.3.2/src/cliyard.egg-info/top_level.txt +1 -0
- cliyard-0.3.2/tests/test_auth.py +517 -0
- cliyard-0.3.2/tests/test_integration.py +671 -0
- cliyard-0.3.2/tests/test_schema_types.py +74 -0
- cliyard-0.3.2/tests/test_validate_dependency.py +146 -0
- cliyard-0.3.2/tests/test_validate_types.py +301 -0
cliyard-0.3.2/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 keta
|
|
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.
|
cliyard-0.3.2/PKG-INFO
ADDED
|
@@ -0,0 +1,120 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: cliyard
|
|
3
|
+
Version: 0.3.2
|
|
4
|
+
Summary: YAML-driven CLI framework — turn any REST API into CLI commands
|
|
5
|
+
Author-email: keta <dev@ketaops.cc>
|
|
6
|
+
License: MIT
|
|
7
|
+
Project-URL: Homepage, https://github.com/guolong123/cliyard
|
|
8
|
+
Project-URL: Repository, https://github.com/guolong123/cliyard.git
|
|
9
|
+
Keywords: cli,yaml,rest-api,codegen,click
|
|
10
|
+
Classifier: Development Status :: 4 - Beta
|
|
11
|
+
Classifier: Intended Audience :: Developers
|
|
12
|
+
Classifier: License :: OSI Approved :: MIT License
|
|
13
|
+
Classifier: Programming Language :: Python :: 3
|
|
14
|
+
Classifier: Programming Language :: Python :: 3.10
|
|
15
|
+
Classifier: Programming Language :: Python :: 3.11
|
|
16
|
+
Classifier: Topic :: Software Development :: Code Generators
|
|
17
|
+
Classifier: Topic :: System :: Systems Administration
|
|
18
|
+
Requires-Python: >=3.10
|
|
19
|
+
Description-Content-Type: text/markdown
|
|
20
|
+
License-File: LICENSE
|
|
21
|
+
Requires-Dist: click>=8.1.0
|
|
22
|
+
Requires-Dist: pyyaml~=6.0
|
|
23
|
+
Requires-Dist: requests~=2.31.0
|
|
24
|
+
Requires-Dist: jinja2~=3.1
|
|
25
|
+
Requires-Dist: rich>=13.7.0
|
|
26
|
+
Requires-Dist: jsonpath-ng~=1.7.0
|
|
27
|
+
Dynamic: license-file
|
|
28
|
+
|
|
29
|
+
# cliyard
|
|
30
|
+
|
|
31
|
+
**CLI + YAML + Yard** — a framework that generates CLI commands from YAML specs. Define your REST API in a few YAML files, and cliyard produces a fully-structured Click CLI with parameters, types, and auto-generated help. Originally inspired by [ketacli](https://gitee.com/xishuhq/ketacli)'s YAML-driven architecture, now generalized for any REST API.
|
|
32
|
+
|
|
33
|
+
## Installation
|
|
34
|
+
|
|
35
|
+
```bash
|
|
36
|
+
pip install cliyard
|
|
37
|
+
```
|
|
38
|
+
|
|
39
|
+
For local development:
|
|
40
|
+
|
|
41
|
+
```bash
|
|
42
|
+
pip install -e .
|
|
43
|
+
```
|
|
44
|
+
|
|
45
|
+
## Quick Start
|
|
46
|
+
|
|
47
|
+
```bash
|
|
48
|
+
# Explore the example spec
|
|
49
|
+
cliyard --spec-dir examples/ketacli-repos/ repos list --help
|
|
50
|
+
|
|
51
|
+
# Usage: repos list [OPTIONS]
|
|
52
|
+
# --page INTEGER Page number
|
|
53
|
+
# --search TEXT Search keyword
|
|
54
|
+
# --help Show this message and exit.
|
|
55
|
+
```
|
|
56
|
+
|
|
57
|
+
## How It Works
|
|
58
|
+
|
|
59
|
+
cliyard turns a directory of YAML files into CLI commands. One directory equals one API service. Each YAML file (except `_service.yaml`) becomes a resource group with subcommands for every defined method.
|
|
60
|
+
|
|
61
|
+
```
|
|
62
|
+
your-api/ ┌─────────────────────────────────┐
|
|
63
|
+
├── _service.yaml ── service ──► │ cliyard Engine │
|
|
64
|
+
├── repos.yaml ── resource ──► │ │
|
|
65
|
+
├── users.yaml ── resource ──► │ Loader ──► Builder ──► Click │
|
|
66
|
+
└── ... │ │ │ │ │
|
|
67
|
+
│ │ Auth Chain │ │
|
|
68
|
+
│ ▼ ▼ ▼ │
|
|
69
|
+
│ YAML Commands CLI run │
|
|
70
|
+
│ parsing + params │
|
|
71
|
+
└─────────────────────────────────┘
|
|
72
|
+
```
|
|
73
|
+
|
|
74
|
+
**Data flow** for a single invocation:
|
|
75
|
+
|
|
76
|
+
```
|
|
77
|
+
cliyard --spec-dir examples/ketacli-repos/ repos list --page 1
|
|
78
|
+
|
|
79
|
+
1. Loader reads _service.yaml + repos.yaml
|
|
80
|
+
2. Auth chain runs (env → login → inject token)
|
|
81
|
+
3. Builder generates Click commands from method specs
|
|
82
|
+
4. Params are typed (int, string, enum, bool) and validated
|
|
83
|
+
5. (Future) HTTP request is assembled and sent
|
|
84
|
+
6. (Future) Response is formatted with Rich tables
|
|
85
|
+
```
|
|
86
|
+
|
|
87
|
+
## Directory Structure
|
|
88
|
+
|
|
89
|
+
Each service is a directory containing a `_service.yaml` for service-level config and one YAML file per resource:
|
|
90
|
+
|
|
91
|
+
```
|
|
92
|
+
ketacli-repos/
|
|
93
|
+
├── _service.yaml # Service config: name, server, auth
|
|
94
|
+
└── repos.yaml # Resource: repos (list, create, get, update, delete)
|
|
95
|
+
```
|
|
96
|
+
|
|
97
|
+
- `_service.yaml` defines the server URL, authentication chain, and global settings.
|
|
98
|
+
- Resource files (`repos.yaml`, `users.yaml`, etc.) define the methods, parameters, and output format for each API resource.
|
|
99
|
+
- Files prefixed with `_service.` (like `_service.local.yaml`) are reserved for future local overrides and are ignored during loading.
|
|
100
|
+
|
|
101
|
+
## Comparison with ketacli
|
|
102
|
+
|
|
103
|
+
| Feature | cliyard | ketacli |
|
|
104
|
+
|---------|---------|---------|
|
|
105
|
+
| API scope | Any REST API | KetaDB only |
|
|
106
|
+
| Config format | One directory per API service | Single YAML tree per resource |
|
|
107
|
+
| Auth model | Chain-based (env, login, inject) | Built-in, KetaDB-specific |
|
|
108
|
+
| Auth caching | In-memory TTL token cache | Session-based |
|
|
109
|
+
| Param types | string, int, float, bool, enum | Pre-defined per command |
|
|
110
|
+
| Adding resources | Add a YAML file to the directory | Requires code changes |
|
|
111
|
+
| CLI generation | Dynamic from spec at runtime | Pre-built Click commands |
|
|
112
|
+
| Purpose | General-purpose API-to-CLI framework | KetaDB management tool |
|
|
113
|
+
|
|
114
|
+
## Creating Your Own Spec
|
|
115
|
+
|
|
116
|
+
See [examples/README.md](examples/README.md) for a step-by-step guide to creating new API specs.
|
|
117
|
+
|
|
118
|
+
## License
|
|
119
|
+
|
|
120
|
+
MIT
|
cliyard-0.3.2/README.md
ADDED
|
@@ -0,0 +1,92 @@
|
|
|
1
|
+
# cliyard
|
|
2
|
+
|
|
3
|
+
**CLI + YAML + Yard** — a framework that generates CLI commands from YAML specs. Define your REST API in a few YAML files, and cliyard produces a fully-structured Click CLI with parameters, types, and auto-generated help. Originally inspired by [ketacli](https://gitee.com/xishuhq/ketacli)'s YAML-driven architecture, now generalized for any REST API.
|
|
4
|
+
|
|
5
|
+
## Installation
|
|
6
|
+
|
|
7
|
+
```bash
|
|
8
|
+
pip install cliyard
|
|
9
|
+
```
|
|
10
|
+
|
|
11
|
+
For local development:
|
|
12
|
+
|
|
13
|
+
```bash
|
|
14
|
+
pip install -e .
|
|
15
|
+
```
|
|
16
|
+
|
|
17
|
+
## Quick Start
|
|
18
|
+
|
|
19
|
+
```bash
|
|
20
|
+
# Explore the example spec
|
|
21
|
+
cliyard --spec-dir examples/ketacli-repos/ repos list --help
|
|
22
|
+
|
|
23
|
+
# Usage: repos list [OPTIONS]
|
|
24
|
+
# --page INTEGER Page number
|
|
25
|
+
# --search TEXT Search keyword
|
|
26
|
+
# --help Show this message and exit.
|
|
27
|
+
```
|
|
28
|
+
|
|
29
|
+
## How It Works
|
|
30
|
+
|
|
31
|
+
cliyard turns a directory of YAML files into CLI commands. One directory equals one API service. Each YAML file (except `_service.yaml`) becomes a resource group with subcommands for every defined method.
|
|
32
|
+
|
|
33
|
+
```
|
|
34
|
+
your-api/ ┌─────────────────────────────────┐
|
|
35
|
+
├── _service.yaml ── service ──► │ cliyard Engine │
|
|
36
|
+
├── repos.yaml ── resource ──► │ │
|
|
37
|
+
├── users.yaml ── resource ──► │ Loader ──► Builder ──► Click │
|
|
38
|
+
└── ... │ │ │ │ │
|
|
39
|
+
│ │ Auth Chain │ │
|
|
40
|
+
│ ▼ ▼ ▼ │
|
|
41
|
+
│ YAML Commands CLI run │
|
|
42
|
+
│ parsing + params │
|
|
43
|
+
└─────────────────────────────────┘
|
|
44
|
+
```
|
|
45
|
+
|
|
46
|
+
**Data flow** for a single invocation:
|
|
47
|
+
|
|
48
|
+
```
|
|
49
|
+
cliyard --spec-dir examples/ketacli-repos/ repos list --page 1
|
|
50
|
+
|
|
51
|
+
1. Loader reads _service.yaml + repos.yaml
|
|
52
|
+
2. Auth chain runs (env → login → inject token)
|
|
53
|
+
3. Builder generates Click commands from method specs
|
|
54
|
+
4. Params are typed (int, string, enum, bool) and validated
|
|
55
|
+
5. (Future) HTTP request is assembled and sent
|
|
56
|
+
6. (Future) Response is formatted with Rich tables
|
|
57
|
+
```
|
|
58
|
+
|
|
59
|
+
## Directory Structure
|
|
60
|
+
|
|
61
|
+
Each service is a directory containing a `_service.yaml` for service-level config and one YAML file per resource:
|
|
62
|
+
|
|
63
|
+
```
|
|
64
|
+
ketacli-repos/
|
|
65
|
+
├── _service.yaml # Service config: name, server, auth
|
|
66
|
+
└── repos.yaml # Resource: repos (list, create, get, update, delete)
|
|
67
|
+
```
|
|
68
|
+
|
|
69
|
+
- `_service.yaml` defines the server URL, authentication chain, and global settings.
|
|
70
|
+
- Resource files (`repos.yaml`, `users.yaml`, etc.) define the methods, parameters, and output format for each API resource.
|
|
71
|
+
- Files prefixed with `_service.` (like `_service.local.yaml`) are reserved for future local overrides and are ignored during loading.
|
|
72
|
+
|
|
73
|
+
## Comparison with ketacli
|
|
74
|
+
|
|
75
|
+
| Feature | cliyard | ketacli |
|
|
76
|
+
|---------|---------|---------|
|
|
77
|
+
| API scope | Any REST API | KetaDB only |
|
|
78
|
+
| Config format | One directory per API service | Single YAML tree per resource |
|
|
79
|
+
| Auth model | Chain-based (env, login, inject) | Built-in, KetaDB-specific |
|
|
80
|
+
| Auth caching | In-memory TTL token cache | Session-based |
|
|
81
|
+
| Param types | string, int, float, bool, enum | Pre-defined per command |
|
|
82
|
+
| Adding resources | Add a YAML file to the directory | Requires code changes |
|
|
83
|
+
| CLI generation | Dynamic from spec at runtime | Pre-built Click commands |
|
|
84
|
+
| Purpose | General-purpose API-to-CLI framework | KetaDB management tool |
|
|
85
|
+
|
|
86
|
+
## Creating Your Own Spec
|
|
87
|
+
|
|
88
|
+
See [examples/README.md](examples/README.md) for a step-by-step guide to creating new API specs.
|
|
89
|
+
|
|
90
|
+
## License
|
|
91
|
+
|
|
92
|
+
MIT
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
[project]
|
|
2
|
+
name = "cliyard"
|
|
3
|
+
version = "0.3.2"
|
|
4
|
+
description = "YAML-driven CLI framework — turn any REST API into CLI commands"
|
|
5
|
+
authors = [
|
|
6
|
+
{name = "keta", email = "dev@ketaops.cc"},
|
|
7
|
+
]
|
|
8
|
+
license = {text = "MIT"}
|
|
9
|
+
readme = "README.md"
|
|
10
|
+
requires-python = ">=3.10"
|
|
11
|
+
keywords = ["cli", "yaml", "rest-api", "codegen", "click"]
|
|
12
|
+
classifiers = [
|
|
13
|
+
"Development Status :: 4 - Beta",
|
|
14
|
+
"Intended Audience :: Developers",
|
|
15
|
+
"License :: OSI Approved :: MIT License",
|
|
16
|
+
"Programming Language :: Python :: 3",
|
|
17
|
+
"Programming Language :: Python :: 3.10",
|
|
18
|
+
"Programming Language :: Python :: 3.11",
|
|
19
|
+
"Topic :: Software Development :: Code Generators",
|
|
20
|
+
"Topic :: System :: Systems Administration",
|
|
21
|
+
]
|
|
22
|
+
dependencies = [
|
|
23
|
+
"click>=8.1.0",
|
|
24
|
+
"pyyaml~=6.0",
|
|
25
|
+
"requests~=2.31.0",
|
|
26
|
+
"jinja2~=3.1",
|
|
27
|
+
"rich>=13.7.0",
|
|
28
|
+
"jsonpath-ng~=1.7.0",
|
|
29
|
+
]
|
|
30
|
+
|
|
31
|
+
[project.urls]
|
|
32
|
+
Homepage = "https://github.com/guolong123/cliyard"
|
|
33
|
+
Repository = "https://github.com/guolong123/cliyard.git"
|
|
34
|
+
|
|
35
|
+
[project.scripts]
|
|
36
|
+
cliyard = "cliyard.cli.__main__:main"
|
|
37
|
+
|
|
38
|
+
[build-system]
|
|
39
|
+
requires = ["setuptools>=65.0.0", "wheel"]
|
|
40
|
+
build-backend = "setuptools.build_meta"
|
cliyard-0.3.2/setup.cfg
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
"""CLI entry point and command registration."""
|
|
@@ -0,0 +1,96 @@
|
|
|
1
|
+
"""cliyard CLI entry point."""
|
|
2
|
+
|
|
3
|
+
import sys
|
|
4
|
+
|
|
5
|
+
import click
|
|
6
|
+
from click.exceptions import NoArgsIsHelpError, MissingParameter, UsageError
|
|
7
|
+
|
|
8
|
+
|
|
9
|
+
def _intercept_spec_dir() -> None:
|
|
10
|
+
"""If ``--spec-dir`` is in sys.argv, extract it and run the spec-based CLI.
|
|
11
|
+
|
|
12
|
+
This must happen *before* Click's command resolution because the
|
|
13
|
+
spec-driven commands (e.g. ``repos list``) don't exist as registered
|
|
14
|
+
Click subcommands — they are built dynamically from YAML specs.
|
|
15
|
+
|
|
16
|
+
Does NOT intercept if the command is ``auth``, ``gen``, ``init``, or ``run``
|
|
17
|
+
— those are native cliyard commands, not spec-driven commands.
|
|
18
|
+
"""
|
|
19
|
+
if "--spec-dir" not in sys.argv:
|
|
20
|
+
return
|
|
21
|
+
|
|
22
|
+
# Skip interception for native cliyard commands
|
|
23
|
+
for arg in sys.argv[1:]:
|
|
24
|
+
if not arg.startswith("-") and arg in ("auth", "gen", "init", "run"):
|
|
25
|
+
return
|
|
26
|
+
if arg.startswith("-"):
|
|
27
|
+
continue
|
|
28
|
+
break
|
|
29
|
+
|
|
30
|
+
try:
|
|
31
|
+
idx = sys.argv.index("--spec-dir")
|
|
32
|
+
except ValueError:
|
|
33
|
+
return
|
|
34
|
+
|
|
35
|
+
if idx + 1 >= len(sys.argv):
|
|
36
|
+
click.echo("Error: --spec-dir requires a directory path", err=True)
|
|
37
|
+
sys.exit(1)
|
|
38
|
+
|
|
39
|
+
spec_dir = sys.argv[idx + 1]
|
|
40
|
+
|
|
41
|
+
sys.argv.pop(idx)
|
|
42
|
+
sys.argv.pop(idx)
|
|
43
|
+
|
|
44
|
+
from cliyard.runtime.runner import run_with_spec
|
|
45
|
+
|
|
46
|
+
run_with_spec(spec_dir)
|
|
47
|
+
|
|
48
|
+
|
|
49
|
+
@click.group()
|
|
50
|
+
@click.version_option()
|
|
51
|
+
def cli():
|
|
52
|
+
"""cliyard — YAML-driven CLI framework for any REST API.
|
|
53
|
+
|
|
54
|
+
Turn any REST API into CLI commands by writing simple YAML specs.
|
|
55
|
+
"""
|
|
56
|
+
|
|
57
|
+
|
|
58
|
+
@cli.command()
|
|
59
|
+
def init():
|
|
60
|
+
"""Initialize a new cliyard spec directory."""
|
|
61
|
+
click.echo("Not yet implemented. Stay tuned.")
|
|
62
|
+
|
|
63
|
+
|
|
64
|
+
from cliyard.cli.gen import gen
|
|
65
|
+
|
|
66
|
+
cli.add_command(gen)
|
|
67
|
+
|
|
68
|
+
|
|
69
|
+
@cli.command()
|
|
70
|
+
@click.argument("spec_file")
|
|
71
|
+
@click.argument("resource")
|
|
72
|
+
@click.argument("operation", required=False)
|
|
73
|
+
@click.option("-e", "--extra", type=str, help="Extra args, key=val,key2=val2")
|
|
74
|
+
def run(spec_file, resource, operation, extra):
|
|
75
|
+
"""Run a command from a YAML spec file."""
|
|
76
|
+
click.echo(f"Not yet implemented. Would run: {spec_file} {resource} {operation}")
|
|
77
|
+
|
|
78
|
+
|
|
79
|
+
def main():
|
|
80
|
+
"""Entry point — intercept --spec-dir before Click, then run CLI."""
|
|
81
|
+
_intercept_spec_dir()
|
|
82
|
+
try:
|
|
83
|
+
cli(standalone_mode=False)
|
|
84
|
+
except MissingParameter as e:
|
|
85
|
+
click.echo(f"Error: {e}", err=True)
|
|
86
|
+
click.echo(f" Usage: cliyard gen --name <name> [--defs-path <path>]")
|
|
87
|
+
except UsageError as e:
|
|
88
|
+
click.echo(f"Error: {e}", err=True)
|
|
89
|
+
except NoArgsIsHelpError as e:
|
|
90
|
+
click.echo(e.format_message())
|
|
91
|
+
except SystemExit:
|
|
92
|
+
pass
|
|
93
|
+
|
|
94
|
+
|
|
95
|
+
if __name__ == "__main__":
|
|
96
|
+
main()
|
|
@@ -0,0 +1,167 @@
|
|
|
1
|
+
"""cliyard.auth — Manage authentication credentials.
|
|
2
|
+
|
|
3
|
+
Provides ``cliyard auth login``, ``cliyard auth status``, and
|
|
4
|
+
``cliyard auth logout`` subcommands.
|
|
5
|
+
"""
|
|
6
|
+
|
|
7
|
+
from __future__ import annotations
|
|
8
|
+
|
|
9
|
+
import os
|
|
10
|
+
import time
|
|
11
|
+
from pathlib import Path
|
|
12
|
+
|
|
13
|
+
import click
|
|
14
|
+
from rich.console import Console
|
|
15
|
+
from rich.table import Table
|
|
16
|
+
|
|
17
|
+
|
|
18
|
+
@click.group(name="auth")
|
|
19
|
+
def auth_group() -> None:
|
|
20
|
+
"""Manage authentication credentials.
|
|
21
|
+
|
|
22
|
+
Login to a service, check saved credential status, or clear
|
|
23
|
+
saved credentials.
|
|
24
|
+
"""
|
|
25
|
+
|
|
26
|
+
|
|
27
|
+
@auth_group.command(name="login")
|
|
28
|
+
@click.option("--spec-dir", required=True, help="Path to YAML spec directory")
|
|
29
|
+
def auth_login(spec_dir: str) -> None:
|
|
30
|
+
"""Initialize auth credentials by running the auth chain.
|
|
31
|
+
|
|
32
|
+
Reads the auth spec from ``_auth.yaml``, executes all auth steps,
|
|
33
|
+
and persists the resulting tokens according to the ``auth.persist``
|
|
34
|
+
configuration.
|
|
35
|
+
"""
|
|
36
|
+
from cliyard.client.auth import run_auth_chain
|
|
37
|
+
from cliyard.client.credentials import save_service_credentials
|
|
38
|
+
from cliyard.engine.loader import load_service
|
|
39
|
+
|
|
40
|
+
console = Console()
|
|
41
|
+
spec_path = Path(spec_dir).resolve()
|
|
42
|
+
|
|
43
|
+
try:
|
|
44
|
+
service = load_service(spec_path)
|
|
45
|
+
except Exception as exc:
|
|
46
|
+
console.print(f"[red]Error loading spec: {exc}[/red]")
|
|
47
|
+
return
|
|
48
|
+
|
|
49
|
+
auth_spec = service.get("auth")
|
|
50
|
+
if not auth_spec:
|
|
51
|
+
console.print("[red]No auth config found in _auth.yaml[/red]")
|
|
52
|
+
return
|
|
53
|
+
|
|
54
|
+
# Run the auth chain (no pre-filled — full re-login)
|
|
55
|
+
try:
|
|
56
|
+
from cliyard.client.http import HttpClient
|
|
57
|
+
|
|
58
|
+
server = service.get("server", {})
|
|
59
|
+
base_url = server.get("base_url", "http://localhost:8080")
|
|
60
|
+
client = HttpClient(base_url)
|
|
61
|
+
auth_state = run_auth_chain(auth_spec, http_client=client)
|
|
62
|
+
except Exception as exc:
|
|
63
|
+
console.print(f"[red]Auth failed: {exc}[/red]")
|
|
64
|
+
return
|
|
65
|
+
|
|
66
|
+
# Save based on persist config
|
|
67
|
+
persist = auth_spec.get("persist", {})
|
|
68
|
+
if persist.get("to") == "cliyard-config" or not persist.get("to"):
|
|
69
|
+
service_id = auth_spec.get("id", service.get("name", "default"))
|
|
70
|
+
fields: dict[str, str | int] = {}
|
|
71
|
+
|
|
72
|
+
persist_fields = persist.get("fields", {})
|
|
73
|
+
for field_name, field_config in persist_fields.items():
|
|
74
|
+
ref: str = field_config.get("from", "")
|
|
75
|
+
default = field_config.get("default")
|
|
76
|
+
|
|
77
|
+
if "." in ref:
|
|
78
|
+
step, key = ref.split(".", 1)
|
|
79
|
+
step_value = auth_state.get(step)
|
|
80
|
+
if isinstance(step_value, dict):
|
|
81
|
+
value = step_value.get(key)
|
|
82
|
+
else:
|
|
83
|
+
value = None
|
|
84
|
+
if value is not None:
|
|
85
|
+
fields[field_name] = value
|
|
86
|
+
elif default is not None:
|
|
87
|
+
fields[field_name] = default
|
|
88
|
+
else:
|
|
89
|
+
# Direct reference: field_name = step_name
|
|
90
|
+
value = auth_state.get(ref)
|
|
91
|
+
if value is not None:
|
|
92
|
+
fields[field_name] = value
|
|
93
|
+
elif default is not None:
|
|
94
|
+
fields[field_name] = default
|
|
95
|
+
|
|
96
|
+
# Auto-compute expires_at from ttl if present
|
|
97
|
+
# Look for a "ttl" key in the extracted auth state
|
|
98
|
+
ttl_value: int | None = None
|
|
99
|
+
for step_name, step_value in auth_state.items():
|
|
100
|
+
if isinstance(step_value, dict) and "ttl" in step_value:
|
|
101
|
+
ttl_value = step_value["ttl"]
|
|
102
|
+
break
|
|
103
|
+
if ttl_value:
|
|
104
|
+
fields["expires_at"] = int(time.time()) + int(ttl_value)
|
|
105
|
+
|
|
106
|
+
if fields:
|
|
107
|
+
save_service_credentials(service_id, fields)
|
|
108
|
+
console.print(f"[green]Credentials saved for '{service_id}'[/green]")
|
|
109
|
+
else:
|
|
110
|
+
console.print("[yellow]No fields configured for persistence.[/yellow]")
|
|
111
|
+
console.print(f"[dim]auth_state: {auth_state}[/dim]")
|
|
112
|
+
else:
|
|
113
|
+
console.print(f"[yellow]Persistence target {persist.get('to')!r} not supported yet.[/yellow]")
|
|
114
|
+
|
|
115
|
+
|
|
116
|
+
@auth_group.command(name="status")
|
|
117
|
+
def auth_status() -> None:
|
|
118
|
+
"""Show saved authentication status."""
|
|
119
|
+
from cliyard.client.credentials import load_credentials
|
|
120
|
+
|
|
121
|
+
console = Console()
|
|
122
|
+
creds = load_credentials()
|
|
123
|
+
services = creds.get("services", {})
|
|
124
|
+
if not services:
|
|
125
|
+
console.print("[yellow]No credentials saved.[/yellow]")
|
|
126
|
+
return
|
|
127
|
+
|
|
128
|
+
table = Table()
|
|
129
|
+
table.add_column("Service")
|
|
130
|
+
table.add_column("Fields")
|
|
131
|
+
table.add_column("Expires")
|
|
132
|
+
|
|
133
|
+
for svc_id, fields in services.items():
|
|
134
|
+
field_names = [k for k in fields.keys() if k != "expires_at"]
|
|
135
|
+
expires_at = fields.get("expires_at")
|
|
136
|
+
if expires_at:
|
|
137
|
+
remaining = int(expires_at) - int(time.time())
|
|
138
|
+
if remaining > 0:
|
|
139
|
+
hours = remaining // 3600
|
|
140
|
+
minutes = (remaining % 3600) // 60
|
|
141
|
+
expiry = f"{hours}h {minutes}m remaining"
|
|
142
|
+
else:
|
|
143
|
+
expiry = "[red]EXPIRED[/red]"
|
|
144
|
+
else:
|
|
145
|
+
expiry = "never"
|
|
146
|
+
table.add_row(svc_id, ", ".join(field_names), expiry)
|
|
147
|
+
|
|
148
|
+
console.print(table)
|
|
149
|
+
|
|
150
|
+
|
|
151
|
+
@auth_group.command(name="logout")
|
|
152
|
+
@click.option("--service", "-s", help="Service ID to logout (default: all)")
|
|
153
|
+
def auth_logout(service: str | None) -> None:
|
|
154
|
+
"""Clear saved credentials."""
|
|
155
|
+
console = Console()
|
|
156
|
+
if service:
|
|
157
|
+
from cliyard.client.credentials import clear_service_credentials
|
|
158
|
+
|
|
159
|
+
clear_service_credentials(service)
|
|
160
|
+
console.print(f"[green]Credentials cleared for '{service}'[/green]")
|
|
161
|
+
else:
|
|
162
|
+
creds_path = os.path.expanduser("~/.cliyard/credentials.yaml")
|
|
163
|
+
if os.path.exists(creds_path):
|
|
164
|
+
os.remove(creds_path)
|
|
165
|
+
console.print("[green]All credentials cleared.[/green]")
|
|
166
|
+
else:
|
|
167
|
+
console.print("[yellow]No credentials to clear.[/yellow]")
|