specli 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.
- specli-0.1.0/.gitattributes +2 -0
- specli-0.1.0/.gitignore +80 -0
- specli-0.1.0/CHANGELOG.md +41 -0
- specli-0.1.0/CONTRIBUTING.md +315 -0
- specli-0.1.0/LICENSE +21 -0
- specli-0.1.0/PKG-INFO +882 -0
- specli-0.1.0/README.md +845 -0
- specli-0.1.0/docs/auth.md +315 -0
- specli-0.1.0/docs/configuration.md +221 -0
- specli-0.1.0/docs/getting-started.md +252 -0
- specli-0.1.0/docs/path-rules.md +336 -0
- specli-0.1.0/docs/plugins.md +369 -0
- specli-0.1.0/docs/skill-generation.md +241 -0
- specli-0.1.0/examples/README.md +92 -0
- specli-0.1.0/examples/uspto-openapi.json +252 -0
- specli-0.1.0/plugins/example_plugin/__init__.py +1 -0
- specli-0.1.0/plugins/example_plugin/plugin.py +46 -0
- specli-0.1.0/pyproject.toml +65 -0
- specli-0.1.0/specli.json +3 -0
- specli-0.1.0/src/specli/__init__.py +25 -0
- specli-0.1.0/src/specli/__main__.py +6 -0
- specli-0.1.0/src/specli/app.py +343 -0
- specli-0.1.0/src/specli/auth/__init__.py +36 -0
- specli-0.1.0/src/specli/auth/base.py +132 -0
- specli-0.1.0/src/specli/auth/credential_store.py +187 -0
- specli-0.1.0/src/specli/auth/manager.py +156 -0
- specli-0.1.0/src/specli/cache/__init__.py +15 -0
- specli-0.1.0/src/specli/cache/cache.py +158 -0
- specli-0.1.0/src/specli/client/__init__.py +27 -0
- specli-0.1.0/src/specli/client/async_client.py +448 -0
- specli-0.1.0/src/specli/client/response.py +72 -0
- specli-0.1.0/src/specli/client/sync_client.py +519 -0
- specli-0.1.0/src/specli/commands/__init__.py +16 -0
- specli-0.1.0/src/specli/commands/auth.py +442 -0
- specli-0.1.0/src/specli/commands/config.py +147 -0
- specli-0.1.0/src/specli/commands/init.py +140 -0
- specli-0.1.0/src/specli/commands/inspect.py +222 -0
- specli-0.1.0/src/specli/config.py +474 -0
- specli-0.1.0/src/specli/enrichment/__init__.py +83 -0
- specli-0.1.0/src/specli/enrichment/enricher.py +243 -0
- specli-0.1.0/src/specli/enrichment/scanner.py +589 -0
- specli-0.1.0/src/specli/enrichment/strings.py +304 -0
- specli-0.1.0/src/specli/exceptions.py +103 -0
- specli-0.1.0/src/specli/exit_codes.py +40 -0
- specli-0.1.0/src/specli/generator/__init__.py +32 -0
- specli-0.1.0/src/specli/generator/command_tree.py +710 -0
- specli-0.1.0/src/specli/generator/param_mapper.py +258 -0
- specli-0.1.0/src/specli/generator/path_rules.py +280 -0
- specli-0.1.0/src/specli/models.py +405 -0
- specli-0.1.0/src/specli/output.py +539 -0
- specli-0.1.0/src/specli/parser/__init__.py +29 -0
- specli-0.1.0/src/specli/parser/extractor.py +436 -0
- specli-0.1.0/src/specli/parser/loader.py +248 -0
- specli-0.1.0/src/specli/parser/resolver.py +170 -0
- specli-0.1.0/src/specli/plugins/__init__.py +33 -0
- specli-0.1.0/src/specli/plugins/api_key/__init__.py +14 -0
- specli-0.1.0/src/specli/plugins/api_key/plugin.py +133 -0
- specli-0.1.0/src/specli/plugins/api_key_gen/__init__.py +18 -0
- specli-0.1.0/src/specli/plugins/api_key_gen/plugin.py +199 -0
- specli-0.1.0/src/specli/plugins/base.py +147 -0
- specli-0.1.0/src/specli/plugins/basic/__init__.py +14 -0
- specli-0.1.0/src/specli/plugins/basic/plugin.py +74 -0
- specli-0.1.0/src/specli/plugins/bearer/__init__.py +14 -0
- specli-0.1.0/src/specli/plugins/bearer/plugin.py +61 -0
- specli-0.1.0/src/specli/plugins/browser_login/__init__.py +21 -0
- specli-0.1.0/src/specli/plugins/browser_login/plugin.py +738 -0
- specli-0.1.0/src/specli/plugins/completion/__init__.py +13 -0
- specli-0.1.0/src/specli/plugins/completion/plugin.py +184 -0
- specli-0.1.0/src/specli/plugins/device_code/__init__.py +15 -0
- specli-0.1.0/src/specli/plugins/device_code/plugin.py +416 -0
- specli-0.1.0/src/specli/plugins/hooks.py +135 -0
- specli-0.1.0/src/specli/plugins/manager.py +218 -0
- specli-0.1.0/src/specli/plugins/manual_token/__init__.py +15 -0
- specli-0.1.0/src/specli/plugins/manual_token/plugin.py +149 -0
- specli-0.1.0/src/specli/plugins/oauth2_auth_code/__init__.py +23 -0
- specli-0.1.0/src/specli/plugins/oauth2_auth_code/plugin.py +384 -0
- specli-0.1.0/src/specli/plugins/oauth2_client_credentials/__init__.py +19 -0
- specli-0.1.0/src/specli/plugins/oauth2_client_credentials/plugin.py +189 -0
- specli-0.1.0/src/specli/plugins/openid_connect/__init__.py +16 -0
- specli-0.1.0/src/specli/plugins/openid_connect/plugin.py +177 -0
- specli-0.1.0/src/specli/plugins/skill/__init__.py +18 -0
- specli-0.1.0/src/specli/plugins/skill/generator.py +317 -0
- specli-0.1.0/src/specli/plugins/skill/plugin.py +97 -0
- specli-0.1.0/src/specli/plugins/skill/templates/auth_setup.md.j2 +54 -0
- specli-0.1.0/src/specli/plugins/skill/templates/reference.md.j2 +38 -0
- specli-0.1.0/src/specli/plugins/skill/templates/skill.md.j2 +40 -0
- specli-0.1.0/src/specli/py.typed +0 -0
- specli-0.1.0/tests/__init__.py +0 -0
- specli-0.1.0/tests/conftest.py +204 -0
- specli-0.1.0/tests/fixtures/complex_auth.json +256 -0
- specli-0.1.0/tests/fixtures/petstore_3.0.json +235 -0
- specli-0.1.0/tests/fixtures/petstore_3.1.json +240 -0
- specli-0.1.0/tests/test_auth/__init__.py +0 -0
- specli-0.1.0/tests/test_auth/test_api_key_gen.py +176 -0
- specli-0.1.0/tests/test_auth/test_auth_plugins.py +558 -0
- specli-0.1.0/tests/test_auth/test_browser_login.py +501 -0
- specli-0.1.0/tests/test_auth/test_credential_store.py +160 -0
- specli-0.1.0/tests/test_auth/test_device_code.py +520 -0
- specli-0.1.0/tests/test_auth/test_manual_token.py +135 -0
- specli-0.1.0/tests/test_auth/test_oauth2_plugins.py +811 -0
- specli-0.1.0/tests/test_cache/__init__.py +0 -0
- specli-0.1.0/tests/test_cache/test_cache.py +294 -0
- specli-0.1.0/tests/test_client/__init__.py +0 -0
- specli-0.1.0/tests/test_client/test_response.py +225 -0
- specli-0.1.0/tests/test_client/test_sync_client.py +898 -0
- specli-0.1.0/tests/test_config.py +648 -0
- specli-0.1.0/tests/test_enrichment/__init__.py +0 -0
- specli-0.1.0/tests/test_enrichment/test_enricher.py +254 -0
- specli-0.1.0/tests/test_enrichment/test_scanner.py +325 -0
- specli-0.1.0/tests/test_enrichment/test_strings.py +325 -0
- specli-0.1.0/tests/test_generator/__init__.py +0 -0
- specli-0.1.0/tests/test_generator/test_command_tree.py +786 -0
- specli-0.1.0/tests/test_generator/test_param_mapper.py +330 -0
- specli-0.1.0/tests/test_generator/test_path_rules.py +696 -0
- specli-0.1.0/tests/test_integration/__init__.py +0 -0
- specli-0.1.0/tests/test_integration/test_build.py +411 -0
- specli-0.1.0/tests/test_integration/test_completion.py +51 -0
- specli-0.1.0/tests/test_integration/test_dynamic_commands.py +674 -0
- specli-0.1.0/tests/test_integration/test_init_flow.py +588 -0
- specli-0.1.0/tests/test_integration/test_uspto_example.py +275 -0
- specli-0.1.0/tests/test_output.py +964 -0
- specli-0.1.0/tests/test_parser/__init__.py +0 -0
- specli-0.1.0/tests/test_parser/test_extractor.py +701 -0
- specli-0.1.0/tests/test_parser/test_loader.py +316 -0
- specli-0.1.0/tests/test_parser/test_resolver.py +350 -0
- specli-0.1.0/tests/test_plugins/__init__.py +0 -0
- specli-0.1.0/tests/test_plugins/test_plugins.py +706 -0
- specli-0.1.0/tests/test_skill/__init__.py +0 -0
- specli-0.1.0/tests/test_skill/test_skill_generator.py +766 -0
specli-0.1.0/.gitignore
ADDED
|
@@ -0,0 +1,80 @@
|
|
|
1
|
+
# Byte-compiled / optimized / DLL files
|
|
2
|
+
__pycache__/
|
|
3
|
+
*.py[cod]
|
|
4
|
+
*$py.class
|
|
5
|
+
|
|
6
|
+
# C extensions
|
|
7
|
+
*.so
|
|
8
|
+
|
|
9
|
+
# Distribution / packaging
|
|
10
|
+
.Python
|
|
11
|
+
build/
|
|
12
|
+
develop-eggs/
|
|
13
|
+
dist/
|
|
14
|
+
downloads/
|
|
15
|
+
eggs/
|
|
16
|
+
.eggs/
|
|
17
|
+
lib/
|
|
18
|
+
lib64/
|
|
19
|
+
parts/
|
|
20
|
+
sdist/
|
|
21
|
+
var/
|
|
22
|
+
wheels/
|
|
23
|
+
*.egg-info/
|
|
24
|
+
.installed.cfg
|
|
25
|
+
*.egg
|
|
26
|
+
|
|
27
|
+
# PyInstaller
|
|
28
|
+
*.manifest
|
|
29
|
+
*.spec
|
|
30
|
+
|
|
31
|
+
# Installer logs
|
|
32
|
+
pip-log.txt
|
|
33
|
+
pip-delete-this-directory.txt
|
|
34
|
+
|
|
35
|
+
# Unit test / coverage reports
|
|
36
|
+
htmlcov/
|
|
37
|
+
.tox/
|
|
38
|
+
.nox/
|
|
39
|
+
.coverage
|
|
40
|
+
.coverage.*
|
|
41
|
+
.cache
|
|
42
|
+
nosetests.xml
|
|
43
|
+
coverage.xml
|
|
44
|
+
*.cover
|
|
45
|
+
*.py,cover
|
|
46
|
+
.hypothesis/
|
|
47
|
+
.pytest_cache/
|
|
48
|
+
|
|
49
|
+
# Translations
|
|
50
|
+
*.mo
|
|
51
|
+
*.pot
|
|
52
|
+
|
|
53
|
+
# Environments
|
|
54
|
+
.env
|
|
55
|
+
.venv
|
|
56
|
+
env/
|
|
57
|
+
venv/
|
|
58
|
+
ENV/
|
|
59
|
+
|
|
60
|
+
# IDE
|
|
61
|
+
.vscode/
|
|
62
|
+
.idea/
|
|
63
|
+
*.swp
|
|
64
|
+
*.swo
|
|
65
|
+
*~
|
|
66
|
+
|
|
67
|
+
# mypy
|
|
68
|
+
.mypy_cache/
|
|
69
|
+
dmypy.json
|
|
70
|
+
dmypy.txt
|
|
71
|
+
|
|
72
|
+
# ruff
|
|
73
|
+
.ruff_cache/
|
|
74
|
+
|
|
75
|
+
# OS
|
|
76
|
+
.DS_Store
|
|
77
|
+
Thumbs.db
|
|
78
|
+
|
|
79
|
+
# Project-specific
|
|
80
|
+
*.log
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
# Changelog
|
|
2
|
+
|
|
3
|
+
All notable changes to this project will be documented in this file.
|
|
4
|
+
|
|
5
|
+
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/),
|
|
6
|
+
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
|
|
7
|
+
|
|
8
|
+
## [0.1.0] - 2026-02-25
|
|
9
|
+
|
|
10
|
+
### Added
|
|
11
|
+
- OpenAPI 3.0/3.1 spec parsing from URL, file, or stdin (JSON and YAML)
|
|
12
|
+
- `$ref` resolution with circular reference detection (internal refs only)
|
|
13
|
+
- Dynamic Typer CLI generation from spec operations
|
|
14
|
+
- Path rules engine with auto-strip prefix, keep, skip_segments, strip_prefix, and collapse
|
|
15
|
+
- HTTP method to CLI verb mapping (GET list/get, POST create, PUT update, DELETE delete)
|
|
16
|
+
- Parameter mapping: path params to positional arguments, query/header/cookie to `--options`
|
|
17
|
+
- `--body` option with `@filename` file reference support
|
|
18
|
+
- Built-in auth plugins: API key (header/query/cookie), bearer token, HTTP basic
|
|
19
|
+
- Credential source resolution: `env:VAR`, `file:/path`, `prompt`, `keyring:service:account`
|
|
20
|
+
- Plugin system with entry-point-based discovery and enable/disable configuration
|
|
21
|
+
- Pre-request, post-response, and error hooks via `HookRunner`
|
|
22
|
+
- Synchronous httpx client with auth injection, retry with exponential backoff, and dry-run mode
|
|
23
|
+
- Asynchronous httpx client mirroring the sync API
|
|
24
|
+
- Rich/JSON/plain output modes with automatic TTY detection
|
|
25
|
+
- stdout/stderr discipline following clig.dev conventions
|
|
26
|
+
- `NO_COLOR` and `TERM=dumb` environment variable support
|
|
27
|
+
- Pager support (`$PAGER` with `less -FIRX` fallback)
|
|
28
|
+
- XDG-compliant configuration on Linux/BSD (`~/.config/specli/`)
|
|
29
|
+
- macOS/Windows fallback to `~/.specli/`
|
|
30
|
+
- Atomic config file writes (temp file + rename)
|
|
31
|
+
- Profile system with per-API configuration (spec URL, base URL, auth, path rules, request settings)
|
|
32
|
+
- Config precedence chain: CLI flags > env vars > project config > user config > defaults
|
|
33
|
+
- Project-local config via `./specli.json`
|
|
34
|
+
- Built-in commands: `init`, `auth` (login, add, list, test, remove), `config` (show, set, reset), `inspect` (paths, schemas, auth, info)
|
|
35
|
+
- Plugins: `build` (compile/generate), `completion` (install/show), `skill` (generate)
|
|
36
|
+
- Claude Code skill generation with Jinja2 templates (SKILL.md, api-reference.md, auth-setup.md)
|
|
37
|
+
- Structured exit codes (0-10) following clig.dev conventions
|
|
38
|
+
- Custom exception hierarchy with per-type exit codes
|
|
39
|
+
- Crash log writing to `$XDG_DATA_HOME/specli/logs/`
|
|
40
|
+
- Global `--version`, `--profile`, `--json`, `--plain`, `--no-color`, `--quiet`, `--verbose`, `--dry-run`, `--force`, `--no-input`, `--output` flags
|
|
41
|
+
- Comprehensive test suite with 725+ tests
|
|
@@ -0,0 +1,315 @@
|
|
|
1
|
+
# Contributing to specli
|
|
2
|
+
|
|
3
|
+
Thank you for your interest in contributing. This guide covers the development workflow, project structure, and conventions used in the codebase.
|
|
4
|
+
|
|
5
|
+
## Development Setup
|
|
6
|
+
|
|
7
|
+
```bash
|
|
8
|
+
# Clone the repository
|
|
9
|
+
git clone https://github.com/aft/specli.git
|
|
10
|
+
cd specli
|
|
11
|
+
|
|
12
|
+
# Create a virtual environment
|
|
13
|
+
python -m venv .venv
|
|
14
|
+
source .venv/bin/activate # Linux/macOS
|
|
15
|
+
# .venv\Scripts\activate # Windows
|
|
16
|
+
|
|
17
|
+
# Install in editable mode with dev dependencies
|
|
18
|
+
pip install -e ".[dev]"
|
|
19
|
+
```
|
|
20
|
+
|
|
21
|
+
### Optional extras
|
|
22
|
+
|
|
23
|
+
```bash
|
|
24
|
+
# Keyring support (for system credential storage)
|
|
25
|
+
pip install -e ".[keyring]"
|
|
26
|
+
|
|
27
|
+
# JWT support
|
|
28
|
+
pip install -e ".[jwt]"
|
|
29
|
+
```
|
|
30
|
+
|
|
31
|
+
## Running Tests
|
|
32
|
+
|
|
33
|
+
The test suite uses pytest and contains 725+ tests across unit, integration, and edge-case scenarios.
|
|
34
|
+
|
|
35
|
+
```bash
|
|
36
|
+
# Run all tests
|
|
37
|
+
pytest
|
|
38
|
+
|
|
39
|
+
# Run with verbose output
|
|
40
|
+
pytest -v
|
|
41
|
+
|
|
42
|
+
# Run with coverage report
|
|
43
|
+
pytest --cov=specli --cov-report=term-missing
|
|
44
|
+
|
|
45
|
+
# Run a specific test file
|
|
46
|
+
pytest tests/test_config.py -v
|
|
47
|
+
|
|
48
|
+
# Run a specific test function
|
|
49
|
+
pytest tests/test_generator/test_path_rules.py::test_auto_strip_prefix -v
|
|
50
|
+
|
|
51
|
+
# Run only integration tests
|
|
52
|
+
pytest tests/test_integration/ -v
|
|
53
|
+
```
|
|
54
|
+
|
|
55
|
+
## Project Structure
|
|
56
|
+
|
|
57
|
+
```
|
|
58
|
+
specli/
|
|
59
|
+
src/specli/ # Source code (src layout)
|
|
60
|
+
__init__.py # Package init, __version__
|
|
61
|
+
__main__.py # python -m specli support
|
|
62
|
+
app.py # Typer app factory and entry point
|
|
63
|
+
config.py # XDG config, profiles, atomic writes
|
|
64
|
+
models.py # All Pydantic models (single source of truth)
|
|
65
|
+
output.py # Output system (Rich/JSON/plain, stdout/stderr)
|
|
66
|
+
exit_codes.py # Exit code constants (clig.dev conventions)
|
|
67
|
+
exceptions.py # Exception hierarchy
|
|
68
|
+
parser/ # OpenAPI spec loading and extraction
|
|
69
|
+
loader.py # URL, file, stdin loading (JSON + YAML)
|
|
70
|
+
resolver.py # $ref resolution with cycle detection
|
|
71
|
+
extractor.py # Extract operations, params, security schemes
|
|
72
|
+
generator/ # CLI command generation
|
|
73
|
+
command_tree.py # Build Typer command tree from parsed spec
|
|
74
|
+
param_mapper.py # Map OpenAPI params to Typer arguments/options
|
|
75
|
+
path_rules.py # Path transformation rules engine
|
|
76
|
+
client/ # HTTP clients
|
|
77
|
+
sync_client.py # Synchronous httpx client
|
|
78
|
+
async_client.py # Async httpx client
|
|
79
|
+
response.py # Response formatting bridge
|
|
80
|
+
auth/ # Authentication system
|
|
81
|
+
base.py # AuthPlugin ABC and AuthResult
|
|
82
|
+
manager.py # Auth plugin registry
|
|
83
|
+
plugins/ # Built-in auth plugins
|
|
84
|
+
api_key.py # API key (header, query, cookie)
|
|
85
|
+
bearer.py # Bearer token
|
|
86
|
+
basic.py # HTTP Basic
|
|
87
|
+
plugins/ # Plugin system
|
|
88
|
+
base.py # Plugin ABC with hook methods
|
|
89
|
+
hooks.py # HookRunner and HookContext
|
|
90
|
+
manager.py # Entry-point discovery, enable/disable
|
|
91
|
+
skill/ # Claude Code skill generation
|
|
92
|
+
generator.py # Skill file generator
|
|
93
|
+
templates/ # Jinja2 templates (skill.md.j2, etc.)
|
|
94
|
+
commands/ # Built-in CLI commands
|
|
95
|
+
init.py # specli init
|
|
96
|
+
auth.py # specli auth (login, add, list, test, remove)
|
|
97
|
+
config.py # specli config (show, set, reset)
|
|
98
|
+
inspect.py # specli inspect (paths, schemas, auth, info)
|
|
99
|
+
generate_skill.py # specli generate-skill
|
|
100
|
+
tests/ # Test suite
|
|
101
|
+
conftest.py # Shared fixtures
|
|
102
|
+
test_config.py # Config unit tests
|
|
103
|
+
test_output.py # Output system tests
|
|
104
|
+
test_parser/ # Parser tests (loader, resolver, extractor)
|
|
105
|
+
test_generator/ # Generator tests (command_tree, param_mapper, path_rules)
|
|
106
|
+
test_client/ # Client tests (sync_client, response)
|
|
107
|
+
test_auth/ # Auth plugin tests
|
|
108
|
+
test_plugins/ # Plugin system tests
|
|
109
|
+
test_skill/ # Skill generator tests
|
|
110
|
+
test_integration/ # Integration tests (init flow, dynamic commands)
|
|
111
|
+
```
|
|
112
|
+
|
|
113
|
+
## Key Design Decisions
|
|
114
|
+
|
|
115
|
+
### Single models file
|
|
116
|
+
|
|
117
|
+
All Pydantic models live in `src/specli/models.py`. Every other module imports from there. This prevents circular imports and makes the data contract easy to find.
|
|
118
|
+
|
|
119
|
+
### Output discipline
|
|
120
|
+
|
|
121
|
+
The output system follows [clig.dev](https://clig.dev) conventions:
|
|
122
|
+
|
|
123
|
+
- **stdout** is for primary data only (API responses, JSON, tables)
|
|
124
|
+
- **stderr** is for all diagnostics (progress, status, warnings, errors)
|
|
125
|
+
- The `OutputManager` class handles format selection, color detection, and pager support
|
|
126
|
+
- Module-level convenience functions (`info()`, `error()`, `debug()`, etc.) delegate to the global `OutputManager` instance
|
|
127
|
+
|
|
128
|
+
### Exit codes
|
|
129
|
+
|
|
130
|
+
Exit codes are defined in `exit_codes.py` and follow a structured scheme. Each exception class in `exceptions.py` carries its own exit code.
|
|
131
|
+
|
|
132
|
+
| Code | Meaning |
|
|
133
|
+
|------|---------|
|
|
134
|
+
| 0 | Success |
|
|
135
|
+
| 1 | Generic failure |
|
|
136
|
+
| 2 | Invalid usage / bad arguments |
|
|
137
|
+
| 3 | Auth failure |
|
|
138
|
+
| 4 | Not found |
|
|
139
|
+
| 5 | Server error |
|
|
140
|
+
| 6 | Connection error |
|
|
141
|
+
| 7 | Spec parse error |
|
|
142
|
+
| 10 | Plugin error |
|
|
143
|
+
|
|
144
|
+
### Atomic config writes
|
|
145
|
+
|
|
146
|
+
Config and profile files are written atomically using `tempfile + os.replace`. This prevents corruption if the process is interrupted mid-write.
|
|
147
|
+
|
|
148
|
+
## How to Create a Plugin
|
|
149
|
+
|
|
150
|
+
Plugins are discovered via Python entry points. Here is the complete process:
|
|
151
|
+
|
|
152
|
+
### 1. Create a package with a Plugin subclass
|
|
153
|
+
|
|
154
|
+
```python
|
|
155
|
+
# mypackage/plugin.py
|
|
156
|
+
from specli.plugins.base import Plugin
|
|
157
|
+
from specli.models import GlobalConfig
|
|
158
|
+
from typing import Any
|
|
159
|
+
|
|
160
|
+
|
|
161
|
+
class MyPlugin(Plugin):
|
|
162
|
+
@property
|
|
163
|
+
def name(self) -> str:
|
|
164
|
+
return "my-plugin"
|
|
165
|
+
|
|
166
|
+
@property
|
|
167
|
+
def version(self) -> str:
|
|
168
|
+
return "1.0.0"
|
|
169
|
+
|
|
170
|
+
@property
|
|
171
|
+
def description(self) -> str:
|
|
172
|
+
return "Adds custom logging to every request"
|
|
173
|
+
|
|
174
|
+
def on_init(self, config: GlobalConfig) -> None:
|
|
175
|
+
# Called once when the plugin is loaded
|
|
176
|
+
print(f"MyPlugin initialized with config: {config.default_profile}")
|
|
177
|
+
|
|
178
|
+
def on_pre_request(
|
|
179
|
+
self, method: str, url: str, headers: dict[str, str], params: dict[str, Any]
|
|
180
|
+
) -> dict[str, Any]:
|
|
181
|
+
print(f"Request: {method} {url}")
|
|
182
|
+
# Must return headers and params (possibly modified)
|
|
183
|
+
return {"headers": headers, "params": params}
|
|
184
|
+
|
|
185
|
+
def on_post_response(
|
|
186
|
+
self, status_code: int, headers: dict[str, str], body: Any
|
|
187
|
+
) -> Any:
|
|
188
|
+
print(f"Response: {status_code}")
|
|
189
|
+
return body # Return (possibly modified) body
|
|
190
|
+
|
|
191
|
+
def on_error(self, error: Exception) -> None:
|
|
192
|
+
print(f"Error: {error}")
|
|
193
|
+
|
|
194
|
+
def cleanup(self) -> None:
|
|
195
|
+
# Called on shutdown
|
|
196
|
+
pass
|
|
197
|
+
```
|
|
198
|
+
|
|
199
|
+
### 2. Register the entry point
|
|
200
|
+
|
|
201
|
+
In your package's `pyproject.toml`:
|
|
202
|
+
|
|
203
|
+
```toml
|
|
204
|
+
[project.entry-points."specli.plugins"]
|
|
205
|
+
my-plugin = "mypackage.plugin:MyPlugin"
|
|
206
|
+
```
|
|
207
|
+
|
|
208
|
+
### 3. Install and use
|
|
209
|
+
|
|
210
|
+
```bash
|
|
211
|
+
pip install mypackage
|
|
212
|
+
specli api pets list # Plugin hooks fire automatically
|
|
213
|
+
```
|
|
214
|
+
|
|
215
|
+
## How to Create an Auth Plugin
|
|
216
|
+
|
|
217
|
+
Auth plugins handle credential resolution for a specific authentication type.
|
|
218
|
+
|
|
219
|
+
```python
|
|
220
|
+
# mypackage/oauth2_plugin.py
|
|
221
|
+
from specli.auth.base import AuthPlugin, AuthResult
|
|
222
|
+
from specli.config import resolve_credential
|
|
223
|
+
from specli.models import AuthConfig
|
|
224
|
+
|
|
225
|
+
|
|
226
|
+
class OAuth2Plugin(AuthPlugin):
|
|
227
|
+
@property
|
|
228
|
+
def auth_type(self) -> str:
|
|
229
|
+
return "oauth2_client_credentials"
|
|
230
|
+
|
|
231
|
+
def authenticate(self, auth_config: AuthConfig) -> AuthResult:
|
|
232
|
+
client_id = resolve_credential(auth_config.client_id_source or "prompt")
|
|
233
|
+
client_secret = resolve_credential(auth_config.client_secret_source or "prompt")
|
|
234
|
+
# Exchange credentials for a token (implementation omitted)
|
|
235
|
+
token = self._get_token(client_id, client_secret, auth_config.token_url)
|
|
236
|
+
return AuthResult(headers={"Authorization": f"Bearer {token}"})
|
|
237
|
+
|
|
238
|
+
def validate_config(self, auth_config: AuthConfig) -> list[str]:
|
|
239
|
+
errors = []
|
|
240
|
+
if not auth_config.token_url:
|
|
241
|
+
errors.append("OAuth2 requires 'token_url'")
|
|
242
|
+
return errors
|
|
243
|
+
|
|
244
|
+
def _get_token(self, client_id: str, client_secret: str, token_url: str | None) -> str:
|
|
245
|
+
# Token exchange logic here
|
|
246
|
+
...
|
|
247
|
+
```
|
|
248
|
+
|
|
249
|
+
Register the auth plugin in your application code or via a regular plugin's `on_init` hook:
|
|
250
|
+
|
|
251
|
+
```python
|
|
252
|
+
from specli.auth.manager import AuthManager
|
|
253
|
+
|
|
254
|
+
manager = AuthManager()
|
|
255
|
+
manager.register(OAuth2Plugin())
|
|
256
|
+
```
|
|
257
|
+
|
|
258
|
+
## Code Style
|
|
259
|
+
|
|
260
|
+
### Tools
|
|
261
|
+
|
|
262
|
+
- **Formatter**: [ruff](https://github.com/astral-sh/ruff) (line length 100)
|
|
263
|
+
- **Linter**: ruff
|
|
264
|
+
- **Type checker**: mypy (strict mode, Python 3.10 target)
|
|
265
|
+
|
|
266
|
+
### Conventions
|
|
267
|
+
|
|
268
|
+
- All public functions and methods must have type annotations
|
|
269
|
+
- All modules use `from __future__ import annotations` for PEP 604 union syntax
|
|
270
|
+
- Docstrings use Google style (Args/Returns/Raises sections)
|
|
271
|
+
- Private functions are prefixed with `_`
|
|
272
|
+
- Constants are `UPPER_SNAKE_CASE`
|
|
273
|
+
- No version numbers in file names
|
|
274
|
+
|
|
275
|
+
### Running quality checks
|
|
276
|
+
|
|
277
|
+
```bash
|
|
278
|
+
# Lint
|
|
279
|
+
ruff check src/ tests/
|
|
280
|
+
|
|
281
|
+
# Format check
|
|
282
|
+
ruff format --check src/ tests/
|
|
283
|
+
|
|
284
|
+
# Type check
|
|
285
|
+
mypy src/specli/
|
|
286
|
+
```
|
|
287
|
+
|
|
288
|
+
## Pull Request Process
|
|
289
|
+
|
|
290
|
+
1. **Create a branch** from `main` with a descriptive name (e.g., `feat/oauth2-auth`, `fix/path-rules-edge-case`)
|
|
291
|
+
2. **Write tests** for any new functionality or bug fixes
|
|
292
|
+
3. **Run the full test suite** and ensure all 725+ tests pass
|
|
293
|
+
4. **Run linting and type checks** (`ruff check`, `mypy`)
|
|
294
|
+
5. **Update documentation** if your change affects user-facing behavior
|
|
295
|
+
6. **Write a clear PR description** explaining what changed and why
|
|
296
|
+
7. **Keep commits focused** -- one logical change per commit
|
|
297
|
+
|
|
298
|
+
### Commit message style
|
|
299
|
+
|
|
300
|
+
```
|
|
301
|
+
feat: add OAuth2 client credentials auth plugin
|
|
302
|
+
fix: handle empty path segments in path rules
|
|
303
|
+
docs: add plugin development guide
|
|
304
|
+
test: add edge cases for $ref circular resolution
|
|
305
|
+
```
|
|
306
|
+
|
|
307
|
+
## Reporting Issues
|
|
308
|
+
|
|
309
|
+
When filing a bug report, please include:
|
|
310
|
+
|
|
311
|
+
- Python version (`python --version`)
|
|
312
|
+
- specli version (`specli --version`)
|
|
313
|
+
- The OpenAPI spec (or a minimal reproduction) that triggers the issue
|
|
314
|
+
- Full command invocation and output
|
|
315
|
+
- The crash log path if one was generated (printed in the error message)
|
specli-0.1.0/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 Cem Baspinar
|
|
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.
|