ally-demo-python 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.
- ally_demo_python-0.1.0/LICENSE +21 -0
- ally_demo_python-0.1.0/PKG-INFO +135 -0
- ally_demo_python-0.1.0/README.md +106 -0
- ally_demo_python-0.1.0/ally_demo_python.egg-info/PKG-INFO +135 -0
- ally_demo_python-0.1.0/ally_demo_python.egg-info/SOURCES.txt +13 -0
- ally_demo_python-0.1.0/ally_demo_python.egg-info/dependency_links.txt +1 -0
- ally_demo_python-0.1.0/ally_demo_python.egg-info/entry_points.txt +2 -0
- ally_demo_python-0.1.0/ally_demo_python.egg-info/requires.txt +6 -0
- ally_demo_python-0.1.0/ally_demo_python.egg-info/top_level.txt +1 -0
- ally_demo_python-0.1.0/demo_cli/__init__.py +3 -0
- ally_demo_python-0.1.0/demo_cli/cli.py +182 -0
- ally_demo_python-0.1.0/demo_cli/errors.py +52 -0
- ally_demo_python-0.1.0/pyproject.toml +56 -0
- ally_demo_python-0.1.0/setup.cfg +4 -0
- ally_demo_python-0.1.0/tests/test_errors.py +137 -0
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 mcp-tool-shop
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
|
@@ -0,0 +1,135 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: ally-demo-python
|
|
3
|
+
Version: 0.1.0
|
|
4
|
+
Summary: Reference integration for Ally (a11y-assist, a11y-lint, a11y-ci)
|
|
5
|
+
Author-email: mcp-tool-shop <64996768+mcp-tool-shop@users.noreply.github.com>
|
|
6
|
+
License: MIT
|
|
7
|
+
Project-URL: Homepage, https://github.com/mcp-tool-shop/ally-demo-python
|
|
8
|
+
Project-URL: Repository, https://github.com/mcp-tool-shop/ally-demo-python
|
|
9
|
+
Project-URL: Issues, https://github.com/mcp-tool-shop/ally-demo-python/issues
|
|
10
|
+
Keywords: a11y,accessibility,cli,demo,reference,ally,error-messages,assistive-technology
|
|
11
|
+
Classifier: Development Status :: 4 - Beta
|
|
12
|
+
Classifier: Environment :: Console
|
|
13
|
+
Classifier: Intended Audience :: Developers
|
|
14
|
+
Classifier: License :: OSI Approved :: MIT License
|
|
15
|
+
Classifier: Programming Language :: Python :: 3
|
|
16
|
+
Classifier: Programming Language :: Python :: 3.10
|
|
17
|
+
Classifier: Programming Language :: Python :: 3.11
|
|
18
|
+
Classifier: Programming Language :: Python :: 3.12
|
|
19
|
+
Classifier: Topic :: Software Development :: Quality Assurance
|
|
20
|
+
Requires-Python: >=3.10
|
|
21
|
+
Description-Content-Type: text/markdown
|
|
22
|
+
License-File: LICENSE
|
|
23
|
+
Requires-Dist: click>=8.0
|
|
24
|
+
Provides-Extra: dev
|
|
25
|
+
Requires-Dist: pytest>=7.0; extra == "dev"
|
|
26
|
+
Requires-Dist: a11y-assist; extra == "dev"
|
|
27
|
+
Requires-Dist: a11y-lint; extra == "dev"
|
|
28
|
+
Dynamic: license-file
|
|
29
|
+
|
|
30
|
+
# Ally Demo (Python)
|
|
31
|
+
|
|
32
|
+
A minimal Python CLI that emits **cli.error.v0.1** (ground truth) and demonstrates the full Ally pipeline:
|
|
33
|
+
|
|
34
|
+
- `a11y-assist` (profiles: lowvision, cognitive-load, screen-reader, dyslexia, plain-language)
|
|
35
|
+
- `a11y-lint` (validate + scan)
|
|
36
|
+
- `a11y-ci` (gate)
|
|
37
|
+
|
|
38
|
+
This repo is intentionally small and boring. It's a reference integration.
|
|
39
|
+
|
|
40
|
+
## Quickstart
|
|
41
|
+
|
|
42
|
+
```bash
|
|
43
|
+
python -m venv .venv
|
|
44
|
+
# Windows: .venv\Scripts\activate
|
|
45
|
+
# macOS/Linux: source .venv/bin/activate
|
|
46
|
+
|
|
47
|
+
pip install -e .
|
|
48
|
+
pip install a11y-assist a11y-lint
|
|
49
|
+
```
|
|
50
|
+
|
|
51
|
+
Run a failing command that emits structured JSON:
|
|
52
|
+
|
|
53
|
+
```bash
|
|
54
|
+
demo-cli network-timeout --json-out /tmp/cli_error.json || true
|
|
55
|
+
```
|
|
56
|
+
|
|
57
|
+
Explain the same error using different profiles:
|
|
58
|
+
|
|
59
|
+
```bash
|
|
60
|
+
a11y-assist explain --json /tmp/cli_error.json --profile lowvision
|
|
61
|
+
a11y-assist explain --json /tmp/cli_error.json --profile cognitive-load
|
|
62
|
+
a11y-assist explain --json /tmp/cli_error.json --profile screen-reader
|
|
63
|
+
a11y-assist explain --json /tmp/cli_error.json --profile dyslexia
|
|
64
|
+
a11y-assist explain --json /tmp/cli_error.json --profile plain-language
|
|
65
|
+
```
|
|
66
|
+
|
|
67
|
+
Validate the message contract:
|
|
68
|
+
|
|
69
|
+
```bash
|
|
70
|
+
a11y-lint validate /tmp/cli_error.json
|
|
71
|
+
```
|
|
72
|
+
|
|
73
|
+
## Available Error Scenarios
|
|
74
|
+
|
|
75
|
+
The demo CLI includes several error scenarios to demonstrate different patterns:
|
|
76
|
+
|
|
77
|
+
| Command | Error ID | Description |
|
|
78
|
+
|---------|----------|-------------|
|
|
79
|
+
| `demo-cli network-timeout` | DEMO.NETWORK.TIMEOUT | HTTP request timeout |
|
|
80
|
+
| `demo-cli config-missing` | DEMO.CONFIG.MISSING | Missing config file |
|
|
81
|
+
| `demo-cli auth-failed` | DEMO.AUTH.INVALID_TOKEN | Authentication failure |
|
|
82
|
+
| `demo-cli permission-denied` | DEMO.FS.PERMISSION_DENIED | File permission error |
|
|
83
|
+
| `demo-cli validation-error` | DEMO.VALIDATION.SCHEMA | Schema validation failure |
|
|
84
|
+
|
|
85
|
+
Each command:
|
|
86
|
+
- Prints human-readable output to stdout
|
|
87
|
+
- Writes JSON to the specified `--json-out` path
|
|
88
|
+
- Emits JSON to stderr for machine capture
|
|
89
|
+
|
|
90
|
+
## What "good" looks like
|
|
91
|
+
|
|
92
|
+
The CLI prints a human-readable message in a predictable structure:
|
|
93
|
+
|
|
94
|
+
```
|
|
95
|
+
[ERROR] Title (ID: ...)
|
|
96
|
+
|
|
97
|
+
What:
|
|
98
|
+
Description of what happened.
|
|
99
|
+
|
|
100
|
+
Why:
|
|
101
|
+
Explanation of why it happened.
|
|
102
|
+
|
|
103
|
+
Fix:
|
|
104
|
+
Steps to resolve the issue.
|
|
105
|
+
Re-run: command --dry-run
|
|
106
|
+
```
|
|
107
|
+
|
|
108
|
+
The CLI also emits valid `cli.error.v0.1` JSON for machine capture.
|
|
109
|
+
|
|
110
|
+
- `a11y-assist` never rewrites the original output; it adds an ASSIST block.
|
|
111
|
+
- SAFE-only commands are suggested (and only when present in the input).
|
|
112
|
+
|
|
113
|
+
## Design notes
|
|
114
|
+
|
|
115
|
+
This demo intentionally emits:
|
|
116
|
+
|
|
117
|
+
- A stable `id` namespace (DEMO.*)
|
|
118
|
+
- Fix lines that include a `Re-run:` command containing `--dry-run` (SAFE)
|
|
119
|
+
|
|
120
|
+
That makes it easy to test the whole pipeline end-to-end.
|
|
121
|
+
|
|
122
|
+
## Adopt Ally in 10 minutes (for tool authors)
|
|
123
|
+
|
|
124
|
+
1. Emit `cli.error.v0.1` JSON on failure (machine output)
|
|
125
|
+
2. Print the same content in a predictable text structure (human output)
|
|
126
|
+
3. Add CI:
|
|
127
|
+
- `a11y-lint validate <message.json>`
|
|
128
|
+
- `a11y-ci gate` for scorecards if you lint text output
|
|
129
|
+
4. Tell users:
|
|
130
|
+
- `a11y-assist explain --json <file>`
|
|
131
|
+
- or wrapper-first: `assist-run <command>` then `a11y-assist last`
|
|
132
|
+
|
|
133
|
+
## License
|
|
134
|
+
|
|
135
|
+
MIT
|
|
@@ -0,0 +1,106 @@
|
|
|
1
|
+
# Ally Demo (Python)
|
|
2
|
+
|
|
3
|
+
A minimal Python CLI that emits **cli.error.v0.1** (ground truth) and demonstrates the full Ally pipeline:
|
|
4
|
+
|
|
5
|
+
- `a11y-assist` (profiles: lowvision, cognitive-load, screen-reader, dyslexia, plain-language)
|
|
6
|
+
- `a11y-lint` (validate + scan)
|
|
7
|
+
- `a11y-ci` (gate)
|
|
8
|
+
|
|
9
|
+
This repo is intentionally small and boring. It's a reference integration.
|
|
10
|
+
|
|
11
|
+
## Quickstart
|
|
12
|
+
|
|
13
|
+
```bash
|
|
14
|
+
python -m venv .venv
|
|
15
|
+
# Windows: .venv\Scripts\activate
|
|
16
|
+
# macOS/Linux: source .venv/bin/activate
|
|
17
|
+
|
|
18
|
+
pip install -e .
|
|
19
|
+
pip install a11y-assist a11y-lint
|
|
20
|
+
```
|
|
21
|
+
|
|
22
|
+
Run a failing command that emits structured JSON:
|
|
23
|
+
|
|
24
|
+
```bash
|
|
25
|
+
demo-cli network-timeout --json-out /tmp/cli_error.json || true
|
|
26
|
+
```
|
|
27
|
+
|
|
28
|
+
Explain the same error using different profiles:
|
|
29
|
+
|
|
30
|
+
```bash
|
|
31
|
+
a11y-assist explain --json /tmp/cli_error.json --profile lowvision
|
|
32
|
+
a11y-assist explain --json /tmp/cli_error.json --profile cognitive-load
|
|
33
|
+
a11y-assist explain --json /tmp/cli_error.json --profile screen-reader
|
|
34
|
+
a11y-assist explain --json /tmp/cli_error.json --profile dyslexia
|
|
35
|
+
a11y-assist explain --json /tmp/cli_error.json --profile plain-language
|
|
36
|
+
```
|
|
37
|
+
|
|
38
|
+
Validate the message contract:
|
|
39
|
+
|
|
40
|
+
```bash
|
|
41
|
+
a11y-lint validate /tmp/cli_error.json
|
|
42
|
+
```
|
|
43
|
+
|
|
44
|
+
## Available Error Scenarios
|
|
45
|
+
|
|
46
|
+
The demo CLI includes several error scenarios to demonstrate different patterns:
|
|
47
|
+
|
|
48
|
+
| Command | Error ID | Description |
|
|
49
|
+
|---------|----------|-------------|
|
|
50
|
+
| `demo-cli network-timeout` | DEMO.NETWORK.TIMEOUT | HTTP request timeout |
|
|
51
|
+
| `demo-cli config-missing` | DEMO.CONFIG.MISSING | Missing config file |
|
|
52
|
+
| `demo-cli auth-failed` | DEMO.AUTH.INVALID_TOKEN | Authentication failure |
|
|
53
|
+
| `demo-cli permission-denied` | DEMO.FS.PERMISSION_DENIED | File permission error |
|
|
54
|
+
| `demo-cli validation-error` | DEMO.VALIDATION.SCHEMA | Schema validation failure |
|
|
55
|
+
|
|
56
|
+
Each command:
|
|
57
|
+
- Prints human-readable output to stdout
|
|
58
|
+
- Writes JSON to the specified `--json-out` path
|
|
59
|
+
- Emits JSON to stderr for machine capture
|
|
60
|
+
|
|
61
|
+
## What "good" looks like
|
|
62
|
+
|
|
63
|
+
The CLI prints a human-readable message in a predictable structure:
|
|
64
|
+
|
|
65
|
+
```
|
|
66
|
+
[ERROR] Title (ID: ...)
|
|
67
|
+
|
|
68
|
+
What:
|
|
69
|
+
Description of what happened.
|
|
70
|
+
|
|
71
|
+
Why:
|
|
72
|
+
Explanation of why it happened.
|
|
73
|
+
|
|
74
|
+
Fix:
|
|
75
|
+
Steps to resolve the issue.
|
|
76
|
+
Re-run: command --dry-run
|
|
77
|
+
```
|
|
78
|
+
|
|
79
|
+
The CLI also emits valid `cli.error.v0.1` JSON for machine capture.
|
|
80
|
+
|
|
81
|
+
- `a11y-assist` never rewrites the original output; it adds an ASSIST block.
|
|
82
|
+
- SAFE-only commands are suggested (and only when present in the input).
|
|
83
|
+
|
|
84
|
+
## Design notes
|
|
85
|
+
|
|
86
|
+
This demo intentionally emits:
|
|
87
|
+
|
|
88
|
+
- A stable `id` namespace (DEMO.*)
|
|
89
|
+
- Fix lines that include a `Re-run:` command containing `--dry-run` (SAFE)
|
|
90
|
+
|
|
91
|
+
That makes it easy to test the whole pipeline end-to-end.
|
|
92
|
+
|
|
93
|
+
## Adopt Ally in 10 minutes (for tool authors)
|
|
94
|
+
|
|
95
|
+
1. Emit `cli.error.v0.1` JSON on failure (machine output)
|
|
96
|
+
2. Print the same content in a predictable text structure (human output)
|
|
97
|
+
3. Add CI:
|
|
98
|
+
- `a11y-lint validate <message.json>`
|
|
99
|
+
- `a11y-ci gate` for scorecards if you lint text output
|
|
100
|
+
4. Tell users:
|
|
101
|
+
- `a11y-assist explain --json <file>`
|
|
102
|
+
- or wrapper-first: `assist-run <command>` then `a11y-assist last`
|
|
103
|
+
|
|
104
|
+
## License
|
|
105
|
+
|
|
106
|
+
MIT
|
|
@@ -0,0 +1,135 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: ally-demo-python
|
|
3
|
+
Version: 0.1.0
|
|
4
|
+
Summary: Reference integration for Ally (a11y-assist, a11y-lint, a11y-ci)
|
|
5
|
+
Author-email: mcp-tool-shop <64996768+mcp-tool-shop@users.noreply.github.com>
|
|
6
|
+
License: MIT
|
|
7
|
+
Project-URL: Homepage, https://github.com/mcp-tool-shop/ally-demo-python
|
|
8
|
+
Project-URL: Repository, https://github.com/mcp-tool-shop/ally-demo-python
|
|
9
|
+
Project-URL: Issues, https://github.com/mcp-tool-shop/ally-demo-python/issues
|
|
10
|
+
Keywords: a11y,accessibility,cli,demo,reference,ally,error-messages,assistive-technology
|
|
11
|
+
Classifier: Development Status :: 4 - Beta
|
|
12
|
+
Classifier: Environment :: Console
|
|
13
|
+
Classifier: Intended Audience :: Developers
|
|
14
|
+
Classifier: License :: OSI Approved :: MIT License
|
|
15
|
+
Classifier: Programming Language :: Python :: 3
|
|
16
|
+
Classifier: Programming Language :: Python :: 3.10
|
|
17
|
+
Classifier: Programming Language :: Python :: 3.11
|
|
18
|
+
Classifier: Programming Language :: Python :: 3.12
|
|
19
|
+
Classifier: Topic :: Software Development :: Quality Assurance
|
|
20
|
+
Requires-Python: >=3.10
|
|
21
|
+
Description-Content-Type: text/markdown
|
|
22
|
+
License-File: LICENSE
|
|
23
|
+
Requires-Dist: click>=8.0
|
|
24
|
+
Provides-Extra: dev
|
|
25
|
+
Requires-Dist: pytest>=7.0; extra == "dev"
|
|
26
|
+
Requires-Dist: a11y-assist; extra == "dev"
|
|
27
|
+
Requires-Dist: a11y-lint; extra == "dev"
|
|
28
|
+
Dynamic: license-file
|
|
29
|
+
|
|
30
|
+
# Ally Demo (Python)
|
|
31
|
+
|
|
32
|
+
A minimal Python CLI that emits **cli.error.v0.1** (ground truth) and demonstrates the full Ally pipeline:
|
|
33
|
+
|
|
34
|
+
- `a11y-assist` (profiles: lowvision, cognitive-load, screen-reader, dyslexia, plain-language)
|
|
35
|
+
- `a11y-lint` (validate + scan)
|
|
36
|
+
- `a11y-ci` (gate)
|
|
37
|
+
|
|
38
|
+
This repo is intentionally small and boring. It's a reference integration.
|
|
39
|
+
|
|
40
|
+
## Quickstart
|
|
41
|
+
|
|
42
|
+
```bash
|
|
43
|
+
python -m venv .venv
|
|
44
|
+
# Windows: .venv\Scripts\activate
|
|
45
|
+
# macOS/Linux: source .venv/bin/activate
|
|
46
|
+
|
|
47
|
+
pip install -e .
|
|
48
|
+
pip install a11y-assist a11y-lint
|
|
49
|
+
```
|
|
50
|
+
|
|
51
|
+
Run a failing command that emits structured JSON:
|
|
52
|
+
|
|
53
|
+
```bash
|
|
54
|
+
demo-cli network-timeout --json-out /tmp/cli_error.json || true
|
|
55
|
+
```
|
|
56
|
+
|
|
57
|
+
Explain the same error using different profiles:
|
|
58
|
+
|
|
59
|
+
```bash
|
|
60
|
+
a11y-assist explain --json /tmp/cli_error.json --profile lowvision
|
|
61
|
+
a11y-assist explain --json /tmp/cli_error.json --profile cognitive-load
|
|
62
|
+
a11y-assist explain --json /tmp/cli_error.json --profile screen-reader
|
|
63
|
+
a11y-assist explain --json /tmp/cli_error.json --profile dyslexia
|
|
64
|
+
a11y-assist explain --json /tmp/cli_error.json --profile plain-language
|
|
65
|
+
```
|
|
66
|
+
|
|
67
|
+
Validate the message contract:
|
|
68
|
+
|
|
69
|
+
```bash
|
|
70
|
+
a11y-lint validate /tmp/cli_error.json
|
|
71
|
+
```
|
|
72
|
+
|
|
73
|
+
## Available Error Scenarios
|
|
74
|
+
|
|
75
|
+
The demo CLI includes several error scenarios to demonstrate different patterns:
|
|
76
|
+
|
|
77
|
+
| Command | Error ID | Description |
|
|
78
|
+
|---------|----------|-------------|
|
|
79
|
+
| `demo-cli network-timeout` | DEMO.NETWORK.TIMEOUT | HTTP request timeout |
|
|
80
|
+
| `demo-cli config-missing` | DEMO.CONFIG.MISSING | Missing config file |
|
|
81
|
+
| `demo-cli auth-failed` | DEMO.AUTH.INVALID_TOKEN | Authentication failure |
|
|
82
|
+
| `demo-cli permission-denied` | DEMO.FS.PERMISSION_DENIED | File permission error |
|
|
83
|
+
| `demo-cli validation-error` | DEMO.VALIDATION.SCHEMA | Schema validation failure |
|
|
84
|
+
|
|
85
|
+
Each command:
|
|
86
|
+
- Prints human-readable output to stdout
|
|
87
|
+
- Writes JSON to the specified `--json-out` path
|
|
88
|
+
- Emits JSON to stderr for machine capture
|
|
89
|
+
|
|
90
|
+
## What "good" looks like
|
|
91
|
+
|
|
92
|
+
The CLI prints a human-readable message in a predictable structure:
|
|
93
|
+
|
|
94
|
+
```
|
|
95
|
+
[ERROR] Title (ID: ...)
|
|
96
|
+
|
|
97
|
+
What:
|
|
98
|
+
Description of what happened.
|
|
99
|
+
|
|
100
|
+
Why:
|
|
101
|
+
Explanation of why it happened.
|
|
102
|
+
|
|
103
|
+
Fix:
|
|
104
|
+
Steps to resolve the issue.
|
|
105
|
+
Re-run: command --dry-run
|
|
106
|
+
```
|
|
107
|
+
|
|
108
|
+
The CLI also emits valid `cli.error.v0.1` JSON for machine capture.
|
|
109
|
+
|
|
110
|
+
- `a11y-assist` never rewrites the original output; it adds an ASSIST block.
|
|
111
|
+
- SAFE-only commands are suggested (and only when present in the input).
|
|
112
|
+
|
|
113
|
+
## Design notes
|
|
114
|
+
|
|
115
|
+
This demo intentionally emits:
|
|
116
|
+
|
|
117
|
+
- A stable `id` namespace (DEMO.*)
|
|
118
|
+
- Fix lines that include a `Re-run:` command containing `--dry-run` (SAFE)
|
|
119
|
+
|
|
120
|
+
That makes it easy to test the whole pipeline end-to-end.
|
|
121
|
+
|
|
122
|
+
## Adopt Ally in 10 minutes (for tool authors)
|
|
123
|
+
|
|
124
|
+
1. Emit `cli.error.v0.1` JSON on failure (machine output)
|
|
125
|
+
2. Print the same content in a predictable text structure (human output)
|
|
126
|
+
3. Add CI:
|
|
127
|
+
- `a11y-lint validate <message.json>`
|
|
128
|
+
- `a11y-ci gate` for scorecards if you lint text output
|
|
129
|
+
4. Tell users:
|
|
130
|
+
- `a11y-assist explain --json <file>`
|
|
131
|
+
- or wrapper-first: `assist-run <command>` then `a11y-assist last`
|
|
132
|
+
|
|
133
|
+
## License
|
|
134
|
+
|
|
135
|
+
MIT
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
LICENSE
|
|
2
|
+
README.md
|
|
3
|
+
pyproject.toml
|
|
4
|
+
ally_demo_python.egg-info/PKG-INFO
|
|
5
|
+
ally_demo_python.egg-info/SOURCES.txt
|
|
6
|
+
ally_demo_python.egg-info/dependency_links.txt
|
|
7
|
+
ally_demo_python.egg-info/entry_points.txt
|
|
8
|
+
ally_demo_python.egg-info/requires.txt
|
|
9
|
+
ally_demo_python.egg-info/top_level.txt
|
|
10
|
+
demo_cli/__init__.py
|
|
11
|
+
demo_cli/cli.py
|
|
12
|
+
demo_cli/errors.py
|
|
13
|
+
tests/test_errors.py
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
demo_cli
|
|
@@ -0,0 +1,182 @@
|
|
|
1
|
+
"""Demo CLI with multiple failure scenarios.
|
|
2
|
+
|
|
3
|
+
Each command demonstrates a different error pattern while emitting valid
|
|
4
|
+
cli.error.v0.1 JSON for machine capture.
|
|
5
|
+
"""
|
|
6
|
+
|
|
7
|
+
import sys
|
|
8
|
+
from pathlib import Path
|
|
9
|
+
|
|
10
|
+
import click
|
|
11
|
+
|
|
12
|
+
from .errors import CliErrorV01, emit_cli_error
|
|
13
|
+
|
|
14
|
+
|
|
15
|
+
def print_human_error(err: CliErrorV01) -> None:
|
|
16
|
+
"""Print error in predictable human-readable structure.
|
|
17
|
+
|
|
18
|
+
This is the pattern a11y-lint expects for high accessibility scores.
|
|
19
|
+
"""
|
|
20
|
+
click.echo(f"[{err.level}] {err.title} (ID: {err.id})")
|
|
21
|
+
click.echo("")
|
|
22
|
+
click.echo("What:")
|
|
23
|
+
for line in err.what:
|
|
24
|
+
click.echo(f" {line}")
|
|
25
|
+
click.echo("")
|
|
26
|
+
click.echo("Why:")
|
|
27
|
+
for line in err.why:
|
|
28
|
+
click.echo(f" {line}")
|
|
29
|
+
click.echo("")
|
|
30
|
+
click.echo("Fix:")
|
|
31
|
+
for line in err.fix:
|
|
32
|
+
click.echo(f" {line}")
|
|
33
|
+
|
|
34
|
+
|
|
35
|
+
def emit_and_exit(err: CliErrorV01, json_out: str | None) -> None:
|
|
36
|
+
"""Emit error to human output, optional file, and stderr JSON."""
|
|
37
|
+
# Human-readable output (stdout)
|
|
38
|
+
print_human_error(err)
|
|
39
|
+
|
|
40
|
+
# Machine-readable JSON
|
|
41
|
+
payload = emit_cli_error(err)
|
|
42
|
+
|
|
43
|
+
# Optional file output
|
|
44
|
+
if json_out:
|
|
45
|
+
Path(json_out).write_text(payload, encoding="utf-8")
|
|
46
|
+
click.echo(f"\nJSON written to: {json_out}", err=True)
|
|
47
|
+
|
|
48
|
+
# Always emit JSON to stderr for machine capture
|
|
49
|
+
print(payload, file=sys.stderr)
|
|
50
|
+
|
|
51
|
+
raise SystemExit(1)
|
|
52
|
+
|
|
53
|
+
|
|
54
|
+
@click.group()
|
|
55
|
+
def main() -> None:
|
|
56
|
+
"""Demo CLI for Ally integration.
|
|
57
|
+
|
|
58
|
+
Run any subcommand to see a structured error message.
|
|
59
|
+
Use --json-out to capture cli.error.v0.1 JSON.
|
|
60
|
+
"""
|
|
61
|
+
pass
|
|
62
|
+
|
|
63
|
+
|
|
64
|
+
@main.command()
|
|
65
|
+
@click.option("--json-out", type=click.Path(dir_okay=False), help="Write JSON to file")
|
|
66
|
+
def network_timeout(json_out: str | None) -> None:
|
|
67
|
+
"""Simulate a network timeout error."""
|
|
68
|
+
err = CliErrorV01(
|
|
69
|
+
level="ERROR",
|
|
70
|
+
code="DEM001",
|
|
71
|
+
id="DEMO.NETWORK.TIMEOUT",
|
|
72
|
+
title="Request timed out",
|
|
73
|
+
what=["The HTTP request did not complete within 30 seconds."],
|
|
74
|
+
why=[
|
|
75
|
+
"The server did not respond before the timeout limit.",
|
|
76
|
+
"This may indicate network congestion or server overload.",
|
|
77
|
+
],
|
|
78
|
+
fix=[
|
|
79
|
+
"Check your network connection.",
|
|
80
|
+
"Verify the server is reachable: ping api.example.com",
|
|
81
|
+
"Re-run with increased timeout: demo-cli network-timeout --timeout 60 --dry-run",
|
|
82
|
+
],
|
|
83
|
+
)
|
|
84
|
+
emit_and_exit(err, json_out)
|
|
85
|
+
|
|
86
|
+
|
|
87
|
+
@main.command()
|
|
88
|
+
@click.option("--json-out", type=click.Path(dir_okay=False), help="Write JSON to file")
|
|
89
|
+
def config_missing(json_out: str | None) -> None:
|
|
90
|
+
"""Simulate a missing config file error."""
|
|
91
|
+
err = CliErrorV01(
|
|
92
|
+
level="ERROR",
|
|
93
|
+
code="DEM002",
|
|
94
|
+
id="DEMO.CONFIG.MISSING",
|
|
95
|
+
title="Config file not found",
|
|
96
|
+
what=["The configuration file 'config.json' was not found."],
|
|
97
|
+
why=[
|
|
98
|
+
"The tool requires a config file to know which project to use.",
|
|
99
|
+
"The file may have been deleted or never created.",
|
|
100
|
+
],
|
|
101
|
+
fix=[
|
|
102
|
+
"Create config.json in the current directory.",
|
|
103
|
+
"Copy the template: cp config.example.json config.json",
|
|
104
|
+
"Re-run: demo-cli config-missing --dry-run",
|
|
105
|
+
],
|
|
106
|
+
)
|
|
107
|
+
emit_and_exit(err, json_out)
|
|
108
|
+
|
|
109
|
+
|
|
110
|
+
@main.command()
|
|
111
|
+
@click.option("--json-out", type=click.Path(dir_okay=False), help="Write JSON to file")
|
|
112
|
+
def auth_failed(json_out: str | None) -> None:
|
|
113
|
+
"""Simulate an authentication failure."""
|
|
114
|
+
err = CliErrorV01(
|
|
115
|
+
level="ERROR",
|
|
116
|
+
code="DEM003",
|
|
117
|
+
id="DEMO.AUTH.INVALID_TOKEN",
|
|
118
|
+
title="Authentication failed",
|
|
119
|
+
what=["The API token was rejected by the server."],
|
|
120
|
+
why=[
|
|
121
|
+
"The token may have expired.",
|
|
122
|
+
"The token may lack required permissions.",
|
|
123
|
+
],
|
|
124
|
+
fix=[
|
|
125
|
+
"Generate a new token at https://example.com/settings/tokens",
|
|
126
|
+
"Set the token: export DEMO_API_TOKEN=<new-token>",
|
|
127
|
+
"Re-run: demo-cli auth-failed --dry-run",
|
|
128
|
+
],
|
|
129
|
+
)
|
|
130
|
+
emit_and_exit(err, json_out)
|
|
131
|
+
|
|
132
|
+
|
|
133
|
+
@main.command()
|
|
134
|
+
@click.option("--json-out", type=click.Path(dir_okay=False), help="Write JSON to file")
|
|
135
|
+
def permission_denied(json_out: str | None) -> None:
|
|
136
|
+
"""Simulate a file permission error."""
|
|
137
|
+
err = CliErrorV01(
|
|
138
|
+
level="ERROR",
|
|
139
|
+
code="DEM004",
|
|
140
|
+
id="DEMO.FS.PERMISSION_DENIED",
|
|
141
|
+
title="Permission denied",
|
|
142
|
+
what=["Cannot write to '/var/log/demo.log'."],
|
|
143
|
+
why=[
|
|
144
|
+
"The current user does not have write permission.",
|
|
145
|
+
"The directory may be owned by root.",
|
|
146
|
+
],
|
|
147
|
+
fix=[
|
|
148
|
+
"Check permissions: ls -la /var/log/demo.log",
|
|
149
|
+
"Run with appropriate permissions or change the log path.",
|
|
150
|
+
"Re-run: demo-cli permission-denied --log-path ./demo.log --dry-run",
|
|
151
|
+
],
|
|
152
|
+
)
|
|
153
|
+
emit_and_exit(err, json_out)
|
|
154
|
+
|
|
155
|
+
|
|
156
|
+
@main.command()
|
|
157
|
+
@click.option("--json-out", type=click.Path(dir_okay=False), help="Write JSON to file")
|
|
158
|
+
def validation_error(json_out: str | None) -> None:
|
|
159
|
+
"""Simulate a data validation error."""
|
|
160
|
+
err = CliErrorV01(
|
|
161
|
+
level="ERROR",
|
|
162
|
+
code="DEM005",
|
|
163
|
+
id="DEMO.VALIDATION.SCHEMA",
|
|
164
|
+
title="Schema validation failed",
|
|
165
|
+
what=[
|
|
166
|
+
"The input file 'data.json' does not match the expected schema.",
|
|
167
|
+
"Field 'email' is required but missing.",
|
|
168
|
+
],
|
|
169
|
+
why=[
|
|
170
|
+
"The schema requires all user records to have an email field.",
|
|
171
|
+
"Record at index 3 is missing this field.",
|
|
172
|
+
],
|
|
173
|
+
fix=[
|
|
174
|
+
"Add the missing 'email' field to record 3.",
|
|
175
|
+
"Validate locally: demo-cli validate data.json --dry-run",
|
|
176
|
+
],
|
|
177
|
+
)
|
|
178
|
+
emit_and_exit(err, json_out)
|
|
179
|
+
|
|
180
|
+
|
|
181
|
+
if __name__ == "__main__":
|
|
182
|
+
main()
|
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
"""cli.error.v0.1 schema helpers.
|
|
2
|
+
|
|
3
|
+
This module provides a dataclass and helper for emitting valid cli.error.v0.1 JSON.
|
|
4
|
+
Copy this pattern into your own CLIs.
|
|
5
|
+
"""
|
|
6
|
+
|
|
7
|
+
from dataclasses import asdict, dataclass
|
|
8
|
+
import json
|
|
9
|
+
import re
|
|
10
|
+
|
|
11
|
+
|
|
12
|
+
@dataclass(frozen=True)
|
|
13
|
+
class CliErrorV01:
|
|
14
|
+
"""Ground truth error message following cli.error.v0.1 schema.
|
|
15
|
+
|
|
16
|
+
Attributes:
|
|
17
|
+
level: OK, WARN, or ERROR (required)
|
|
18
|
+
code: Error code matching pattern ^[A-Z][A-Z0-9]{1,3}[0-9]{3}$ (e.g., "DEM001")
|
|
19
|
+
id: Alternate dot-namespaced identifier (e.g., "DEMO.NETWORK.TIMEOUT")
|
|
20
|
+
title: Short human-readable summary
|
|
21
|
+
what: List of sentences describing what happened (required)
|
|
22
|
+
why: List of sentences explaining why it happened (required for ERROR)
|
|
23
|
+
fix: List of actionable steps (required for ERROR, include SAFE commands)
|
|
24
|
+
"""
|
|
25
|
+
|
|
26
|
+
level: str
|
|
27
|
+
code: str
|
|
28
|
+
id: str
|
|
29
|
+
title: str
|
|
30
|
+
what: list[str]
|
|
31
|
+
why: list[str]
|
|
32
|
+
fix: list[str]
|
|
33
|
+
|
|
34
|
+
def __post_init__(self) -> None:
|
|
35
|
+
"""Validate fields."""
|
|
36
|
+
if self.level not in ("OK", "WARN", "ERROR"):
|
|
37
|
+
raise ValueError(f"Invalid level: {self.level}")
|
|
38
|
+
# Code pattern: ^[A-Z][A-Z0-9]{1,3}[0-9]{3}$
|
|
39
|
+
if not re.match(r"^[A-Z][A-Z0-9]{1,3}[0-9]{3}$", self.code):
|
|
40
|
+
raise ValueError(f"Invalid code format: {self.code} (expected e.g., DEM001)")
|
|
41
|
+
|
|
42
|
+
|
|
43
|
+
def emit_cli_error(err: CliErrorV01) -> str:
|
|
44
|
+
"""Serialize a CliErrorV01 to JSON string.
|
|
45
|
+
|
|
46
|
+
Args:
|
|
47
|
+
err: The error to serialize.
|
|
48
|
+
|
|
49
|
+
Returns:
|
|
50
|
+
JSON string with 2-space indent.
|
|
51
|
+
"""
|
|
52
|
+
return json.dumps(asdict(err), indent=2, ensure_ascii=False)
|
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
[build-system]
|
|
2
|
+
requires = ["setuptools>=61.0"]
|
|
3
|
+
build-backend = "setuptools.build_meta"
|
|
4
|
+
|
|
5
|
+
[project]
|
|
6
|
+
name = "ally-demo-python"
|
|
7
|
+
version = "0.1.0"
|
|
8
|
+
description = "Reference integration for Ally (a11y-assist, a11y-lint, a11y-ci)"
|
|
9
|
+
readme = "README.md"
|
|
10
|
+
license = {text = "MIT"}
|
|
11
|
+
requires-python = ">=3.10"
|
|
12
|
+
authors = [
|
|
13
|
+
{name = "mcp-tool-shop", email = "64996768+mcp-tool-shop@users.noreply.github.com"}
|
|
14
|
+
]
|
|
15
|
+
keywords = [
|
|
16
|
+
"a11y",
|
|
17
|
+
"accessibility",
|
|
18
|
+
"cli",
|
|
19
|
+
"demo",
|
|
20
|
+
"reference",
|
|
21
|
+
"ally",
|
|
22
|
+
"error-messages",
|
|
23
|
+
"assistive-technology",
|
|
24
|
+
]
|
|
25
|
+
classifiers = [
|
|
26
|
+
"Development Status :: 4 - Beta",
|
|
27
|
+
"Environment :: Console",
|
|
28
|
+
"Intended Audience :: Developers",
|
|
29
|
+
"License :: OSI Approved :: MIT License",
|
|
30
|
+
"Programming Language :: Python :: 3",
|
|
31
|
+
"Programming Language :: Python :: 3.10",
|
|
32
|
+
"Programming Language :: Python :: 3.11",
|
|
33
|
+
"Programming Language :: Python :: 3.12",
|
|
34
|
+
"Topic :: Software Development :: Quality Assurance",
|
|
35
|
+
]
|
|
36
|
+
dependencies = [
|
|
37
|
+
"click>=8.0",
|
|
38
|
+
]
|
|
39
|
+
|
|
40
|
+
[project.optional-dependencies]
|
|
41
|
+
dev = [
|
|
42
|
+
"pytest>=7.0",
|
|
43
|
+
"a11y-assist",
|
|
44
|
+
"a11y-lint",
|
|
45
|
+
]
|
|
46
|
+
|
|
47
|
+
[project.scripts]
|
|
48
|
+
demo-cli = "demo_cli.cli:main"
|
|
49
|
+
|
|
50
|
+
[project.urls]
|
|
51
|
+
Homepage = "https://github.com/mcp-tool-shop/ally-demo-python"
|
|
52
|
+
Repository = "https://github.com/mcp-tool-shop/ally-demo-python"
|
|
53
|
+
Issues = "https://github.com/mcp-tool-shop/ally-demo-python/issues"
|
|
54
|
+
|
|
55
|
+
[tool.setuptools.packages.find]
|
|
56
|
+
include = ["demo_cli*"]
|
|
@@ -0,0 +1,137 @@
|
|
|
1
|
+
"""Tests for cli.error.v0.1 emission."""
|
|
2
|
+
|
|
3
|
+
import json
|
|
4
|
+
|
|
5
|
+
import pytest
|
|
6
|
+
|
|
7
|
+
from demo_cli.errors import CliErrorV01, emit_cli_error
|
|
8
|
+
|
|
9
|
+
|
|
10
|
+
def test_cli_error_v01_valid():
|
|
11
|
+
"""Valid CliErrorV01 should serialize correctly."""
|
|
12
|
+
err = CliErrorV01(
|
|
13
|
+
level="ERROR",
|
|
14
|
+
code="TST001",
|
|
15
|
+
id="TEST.ERROR.001",
|
|
16
|
+
title="Test error",
|
|
17
|
+
what=["Something happened."],
|
|
18
|
+
why=["Because of reasons."],
|
|
19
|
+
fix=["Do this.", "Re-run: test --dry-run"],
|
|
20
|
+
)
|
|
21
|
+
payload = emit_cli_error(err)
|
|
22
|
+
parsed = json.loads(payload)
|
|
23
|
+
|
|
24
|
+
assert parsed["level"] == "ERROR"
|
|
25
|
+
assert parsed["code"] == "TST001"
|
|
26
|
+
assert parsed["id"] == "TEST.ERROR.001"
|
|
27
|
+
assert parsed["title"] == "Test error"
|
|
28
|
+
assert len(parsed["what"]) == 1
|
|
29
|
+
assert len(parsed["why"]) == 1
|
|
30
|
+
assert len(parsed["fix"]) == 2
|
|
31
|
+
|
|
32
|
+
|
|
33
|
+
def test_cli_error_v01_invalid_level():
|
|
34
|
+
"""Invalid level should raise ValueError."""
|
|
35
|
+
with pytest.raises(ValueError, match="Invalid level"):
|
|
36
|
+
CliErrorV01(
|
|
37
|
+
level="FAILURE",
|
|
38
|
+
code="TST001",
|
|
39
|
+
id="TEST.001",
|
|
40
|
+
title="Test",
|
|
41
|
+
what=["X"],
|
|
42
|
+
why=["Y"],
|
|
43
|
+
fix=["Z"],
|
|
44
|
+
)
|
|
45
|
+
|
|
46
|
+
|
|
47
|
+
def test_cli_error_v01_invalid_code_format():
|
|
48
|
+
"""Invalid code format should raise ValueError."""
|
|
49
|
+
with pytest.raises(ValueError, match="Invalid code format"):
|
|
50
|
+
CliErrorV01(
|
|
51
|
+
level="ERROR",
|
|
52
|
+
code="invalid",
|
|
53
|
+
id="TEST.001",
|
|
54
|
+
title="Test",
|
|
55
|
+
what=["X"],
|
|
56
|
+
why=["Y"],
|
|
57
|
+
fix=["Z"],
|
|
58
|
+
)
|
|
59
|
+
|
|
60
|
+
|
|
61
|
+
def test_cli_error_v01_valid_code_patterns():
|
|
62
|
+
"""Various valid code patterns should work."""
|
|
63
|
+
# Short code: AB001 (2 chars + 3 digits)
|
|
64
|
+
err1 = CliErrorV01(
|
|
65
|
+
level="ERROR",
|
|
66
|
+
code="AB001",
|
|
67
|
+
id="TEST.001",
|
|
68
|
+
title="Test",
|
|
69
|
+
what=["X"],
|
|
70
|
+
why=["Y"],
|
|
71
|
+
fix=["Z"],
|
|
72
|
+
)
|
|
73
|
+
assert err1.code == "AB001"
|
|
74
|
+
|
|
75
|
+
# Long code: DEMO001 (4 chars + 3 digits)
|
|
76
|
+
err2 = CliErrorV01(
|
|
77
|
+
level="ERROR",
|
|
78
|
+
code="DEMO001",
|
|
79
|
+
id="TEST.001",
|
|
80
|
+
title="Test",
|
|
81
|
+
what=["X"],
|
|
82
|
+
why=["Y"],
|
|
83
|
+
fix=["Z"],
|
|
84
|
+
)
|
|
85
|
+
assert err2.code == "DEMO001"
|
|
86
|
+
|
|
87
|
+
# Medium code: DEM001 (3 chars + 3 digits)
|
|
88
|
+
err3 = CliErrorV01(
|
|
89
|
+
level="ERROR",
|
|
90
|
+
code="DEM001",
|
|
91
|
+
id="TEST.001",
|
|
92
|
+
title="Test",
|
|
93
|
+
what=["X"],
|
|
94
|
+
why=["Y"],
|
|
95
|
+
fix=["Z"],
|
|
96
|
+
)
|
|
97
|
+
assert err3.code == "DEM001"
|
|
98
|
+
|
|
99
|
+
|
|
100
|
+
def test_emit_cli_error_json_format():
|
|
101
|
+
"""Emitted JSON should be properly formatted."""
|
|
102
|
+
err = CliErrorV01(
|
|
103
|
+
level="WARN",
|
|
104
|
+
code="TST002",
|
|
105
|
+
id="TEST.WARN.001",
|
|
106
|
+
title="Warning",
|
|
107
|
+
what=["Minor issue."],
|
|
108
|
+
why=["Expected behavior."],
|
|
109
|
+
fix=["No action needed."],
|
|
110
|
+
)
|
|
111
|
+
payload = emit_cli_error(err)
|
|
112
|
+
|
|
113
|
+
# Should be valid JSON
|
|
114
|
+
parsed = json.loads(payload)
|
|
115
|
+
assert parsed["level"] == "WARN"
|
|
116
|
+
|
|
117
|
+
# Should have indentation (pretty-printed)
|
|
118
|
+
assert "\n" in payload
|
|
119
|
+
assert " " in payload
|
|
120
|
+
|
|
121
|
+
|
|
122
|
+
def test_emit_cli_error_unicode():
|
|
123
|
+
"""Emitted JSON should handle unicode correctly."""
|
|
124
|
+
err = CliErrorV01(
|
|
125
|
+
level="ERROR",
|
|
126
|
+
code="TST003",
|
|
127
|
+
id="TEST.UNICODE.001",
|
|
128
|
+
title="Unicode test: \u2713 \u2717",
|
|
129
|
+
what=["Contains unicode: \u00e9\u00e8\u00ea"],
|
|
130
|
+
why=["Multi-language support."],
|
|
131
|
+
fix=["No changes needed."],
|
|
132
|
+
)
|
|
133
|
+
payload = emit_cli_error(err)
|
|
134
|
+
parsed = json.loads(payload)
|
|
135
|
+
|
|
136
|
+
assert "\u2713" in parsed["title"]
|
|
137
|
+
assert "\u00e9" in parsed["what"][0]
|