degardis 1.0.0__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (55) hide show
  1. degardis-1.0.0/LICENSE +21 -0
  2. degardis-1.0.0/MANIFEST.in +4 -0
  3. degardis-1.0.0/PKG-INFO +134 -0
  4. degardis-1.0.0/README.md +106 -0
  5. degardis-1.0.0/degardis/__init__.py +1 -0
  6. degardis-1.0.0/degardis/__main__.py +4 -0
  7. degardis-1.0.0/degardis/build.py +90 -0
  8. degardis-1.0.0/degardis/cli.py +197 -0
  9. degardis-1.0.0/degardis/icons.py +166 -0
  10. degardis-1.0.0/degardis/markdown.py +223 -0
  11. degardis-1.0.0/degardis/model.py +198 -0
  12. degardis-1.0.0/degardis/output.py +129 -0
  13. degardis-1.0.0/degardis/package.py +197 -0
  14. degardis-1.0.0/degardis/registry.py +263 -0
  15. degardis-1.0.0/degardis/resolver.py +374 -0
  16. degardis-1.0.0/degardis/validate.py +244 -0
  17. degardis-1.0.0/degardis.egg-info/PKG-INFO +134 -0
  18. degardis-1.0.0/degardis.egg-info/SOURCES.txt +53 -0
  19. degardis-1.0.0/degardis.egg-info/dependency_links.txt +1 -0
  20. degardis-1.0.0/degardis.egg-info/entry_points.txt +2 -0
  21. degardis-1.0.0/degardis.egg-info/requires.txt +7 -0
  22. degardis-1.0.0/degardis.egg-info/top_level.txt +1 -0
  23. degardis-1.0.0/docs/artifact-format.md +123 -0
  24. degardis-1.0.0/docs/authoring-guide.md +333 -0
  25. degardis-1.0.0/docs/concepts.md +99 -0
  26. degardis-1.0.0/docs/getting-started.md +165 -0
  27. degardis-1.0.0/docs/reference.md +297 -0
  28. degardis-1.0.0/examples/structured-summary/assets/icon.svg +4 -0
  29. degardis-1.0.0/examples/structured-summary/assets/template.md +10 -0
  30. degardis-1.0.0/examples/structured-summary/entries/audience.yaml +10 -0
  31. degardis-1.0.0/examples/structured-summary/entries/fidelity.yaml +10 -0
  32. degardis-1.0.0/examples/structured-summary/profiles/detailed.yaml +8 -0
  33. degardis-1.0.0/examples/structured-summary/profiles/details/detailed.md +4 -0
  34. degardis-1.0.0/examples/structured-summary/scripts/list_headings.py +26 -0
  35. degardis-1.0.0/examples/structured-summary/skill.yaml +29 -0
  36. degardis-1.0.0/examples/structured-summary/workflows/compose.yaml +15 -0
  37. degardis-1.0.0/examples/structured-summary/workflows/inspect.yaml +10 -0
  38. degardis-1.0.0/pyproject.toml +55 -0
  39. degardis-1.0.0/setup.cfg +4 -0
  40. degardis-1.0.0/tests/fixtures/skills/demo/alpha/assets/template.md +3 -0
  41. degardis-1.0.0/tests/fixtures/skills/demo/alpha/entries/rule-one.yaml +5 -0
  42. degardis-1.0.0/tests/fixtures/skills/demo/alpha/profiles/alpha-only.yaml +5 -0
  43. degardis-1.0.0/tests/fixtures/skills/demo/alpha/profiles/shared.yaml +5 -0
  44. degardis-1.0.0/tests/fixtures/skills/demo/alpha/scripts/greet.py +4 -0
  45. degardis-1.0.0/tests/fixtures/skills/demo/alpha/skill.yaml +19 -0
  46. degardis-1.0.0/tests/fixtures/skills/demo/alpha/workflows/run.yaml +6 -0
  47. degardis-1.0.0/tests/fixtures/skills/demo/beta/entries/rule-two.yaml +5 -0
  48. degardis-1.0.0/tests/fixtures/skills/demo/beta/profiles/beta-only.yaml +5 -0
  49. degardis-1.0.0/tests/fixtures/skills/demo/beta/profiles/shared.yaml +5 -0
  50. degardis-1.0.0/tests/fixtures/skills/demo/beta/skill.yaml +19 -0
  51. degardis-1.0.0/tests/fixtures/skills/demo/beta/workflows/run.yaml +6 -0
  52. degardis-1.0.0/tests/fixtures/skills/demo/gamma/entries/rule-three.yaml +5 -0
  53. degardis-1.0.0/tests/fixtures/skills/demo/gamma/skill.yaml +19 -0
  54. degardis-1.0.0/tests/fixtures/skills/demo/gamma/workflows/run.yaml +6 -0
  55. degardis-1.0.0/tests/test_compiler.py +1508 -0
degardis-1.0.0/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 Kambiz Khojasteh
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,4 @@
1
+ graft docs
2
+ graft examples
3
+ graft tests
4
+ global-exclude __pycache__ *.py[cod]
@@ -0,0 +1,134 @@
1
+ Metadata-Version: 2.4
2
+ Name: degardis
3
+ Version: 1.0.0
4
+ Summary: Compile validated Agent Skill sources into portable, installable bundles.
5
+ Author: Kambiz Khojasteh
6
+ License-Expression: MIT
7
+ Project-URL: Documentation, https://github.com/Khojasteh/degardis/tree/main/docs
8
+ Project-URL: Issues, https://github.com/Khojasteh/degardis/issues
9
+ Project-URL: Repository, https://github.com/Khojasteh/degardis
10
+ Keywords: agent-skills,ai-agents,cli,compiler
11
+ Classifier: Development Status :: 5 - Production/Stable
12
+ Classifier: Environment :: Console
13
+ Classifier: Intended Audience :: Developers
14
+ Classifier: Operating System :: OS Independent
15
+ Classifier: Programming Language :: Python :: 3
16
+ Classifier: Programming Language :: Python :: 3 :: Only
17
+ Classifier: Topic :: Software Development :: Build Tools
18
+ Requires-Python: >=3.10
19
+ Description-Content-Type: text/markdown
20
+ License-File: LICENSE
21
+ Requires-Dist: Pillow<13,>=10
22
+ Requires-Dist: PyYAML>=6.0
23
+ Requires-Dist: resvg_py<1.0,>=0.3
24
+ Provides-Extra: release
25
+ Requires-Dist: build>=1.2; extra == "release"
26
+ Requires-Dist: twine>=6; extra == "release"
27
+ Dynamic: license-file
28
+
29
+ # Degardis
30
+
31
+ Degardis is a command-line compiler for authors of portable Agent Skills. It
32
+ turns structured YAML sources into self-contained bundles for Claude, Codex,
33
+ Copilot, Cursor, Roo, and ChatGPT. Before writing an artifact, it validates the
34
+ source, selected profiles, generated paths, and generated links.
35
+
36
+ Authoring stays separate from generated output: edit the source files, validate
37
+ them, and rebuild either an installable folder or a distributable ZIP.
38
+
39
+ ## Quick start
40
+
41
+ Degardis requires Python 3.10 or later. Install it from PyPI:
42
+
43
+ ```console
44
+ python -m pip install degardis
45
+ degardis --version
46
+ ```
47
+
48
+ For an isolated command-line installation, use `pipx install degardis`.
49
+
50
+ To work on Degardis from this repository instead:
51
+
52
+ ```console
53
+ python -m pip install -e .
54
+ degardis validate examples/structured-summary
55
+ degardis list examples/structured-summary
56
+ degardis build examples/structured-summary --output .artifacts
57
+ ```
58
+
59
+ The successful build ends with `Summary: 1 skill built as folder.` and reports
60
+ the artifact path. The generated folder is ready to inspect or install:
61
+
62
+ ```text
63
+ .artifacts/structured-summary/
64
+ SKILL.md
65
+ agents/openai.yaml
66
+ references/
67
+ scripts/
68
+ assets/
69
+ ```
70
+
71
+ Degardis writes only beneath the requested output root. Rebuilding a skill
72
+ replaces that skill's existing folder and ZIP there, while preserving unrelated
73
+ entries. See [Artifact format](https://github.com/Khojasteh/degardis/blob/main/docs/artifact-format.md#replace-an-artifact)
74
+ before building directly into an agent's skill directory.
75
+
76
+ Add the example's optional profile or produce a ZIP:
77
+
78
+ ```console
79
+ degardis build examples/structured-summary --profile detailed --output .artifacts
80
+ degardis build examples/structured-summary --zip --output .artifacts
81
+ ```
82
+
83
+ `examples/structured-summary` is the canonical public example used throughout
84
+ the guides and CLI help. It summarizes supplied material from any subject;
85
+ synthetic skills under `tests/fixtures` are compiler test data.
86
+
87
+ ## Companion skills
88
+
89
+ The [Degardis skills catalog](https://github.com/Khojasteh/degardis-skills)
90
+ contains reusable skills authored with Degardis.
91
+
92
+ For the smoothest authoring experience, use
93
+ [Degardis Authoring](https://github.com/Khojasteh/degardis-skills/tree/main/skills/degardis-authoring).
94
+ It guides a skill from initial design through review, validation, packaging,
95
+ and installation while applying Degardis conventions across manifests,
96
+ workflows, entries, profiles, scripts, and assets.
97
+
98
+ ## Commands
99
+
100
+ ```console
101
+ degardis list PATH [PATH ...]
102
+ degardis validate PATH [PATH ...]
103
+ degardis build PATH [PATH ...] --output PATH [--profile [SKILL:]PROFILE] [--zip]
104
+ ```
105
+
106
+ Run `degardis COMMAND --help` for exact options and examples. All commands
107
+ accept individual skill directories, directories containing skills at any
108
+ depth, or a mixture of both. This lets a source tree group skills into
109
+ subdirectories while building all of them with one command.
110
+
111
+ An unqualified profile name applies to every selected skill that defines it.
112
+ `SKILL:PROFILE` selects one owner, and `all` selects all available profiles.
113
+ Supplying explicit selectors replaces manifest defaults for that build.
114
+
115
+ For exact command behavior, profile selectors, source schemas, and the Python
116
+ API, see the [reference](https://github.com/Khojasteh/degardis/blob/main/docs/reference.md).
117
+
118
+ ## Documentation
119
+
120
+ | Reader goal | Document |
121
+ | -------------------------------------------- | ------------------------------------------ |
122
+ | Build and install the example | [Getting started](https://github.com/Khojasteh/degardis/blob/main/docs/getting-started.md) |
123
+ | Understand the source and artifact models | [Concepts](https://github.com/Khojasteh/degardis/blob/main/docs/concepts.md) |
124
+ | Create or modify a skill | [Authoring guide](https://github.com/Khojasteh/degardis/blob/main/docs/authoring-guide.md) |
125
+ | Look up commands, schemas, or the Python API | [Reference](https://github.com/Khojasteh/degardis/blob/main/docs/reference.md) |
126
+ | Inspect and install generated output | [Artifact format](https://github.com/Khojasteh/degardis/blob/main/docs/artifact-format.md) |
127
+
128
+ ## Development
129
+
130
+ ```console
131
+ python -m unittest discover -s tests -v
132
+ python -m degardis validate examples/structured-summary
133
+ python -m degardis build examples/structured-summary --profile all --output .artifacts
134
+ ```
@@ -0,0 +1,106 @@
1
+ # Degardis
2
+
3
+ Degardis is a command-line compiler for authors of portable Agent Skills. It
4
+ turns structured YAML sources into self-contained bundles for Claude, Codex,
5
+ Copilot, Cursor, Roo, and ChatGPT. Before writing an artifact, it validates the
6
+ source, selected profiles, generated paths, and generated links.
7
+
8
+ Authoring stays separate from generated output: edit the source files, validate
9
+ them, and rebuild either an installable folder or a distributable ZIP.
10
+
11
+ ## Quick start
12
+
13
+ Degardis requires Python 3.10 or later. Install it from PyPI:
14
+
15
+ ```console
16
+ python -m pip install degardis
17
+ degardis --version
18
+ ```
19
+
20
+ For an isolated command-line installation, use `pipx install degardis`.
21
+
22
+ To work on Degardis from this repository instead:
23
+
24
+ ```console
25
+ python -m pip install -e .
26
+ degardis validate examples/structured-summary
27
+ degardis list examples/structured-summary
28
+ degardis build examples/structured-summary --output .artifacts
29
+ ```
30
+
31
+ The successful build ends with `Summary: 1 skill built as folder.` and reports
32
+ the artifact path. The generated folder is ready to inspect or install:
33
+
34
+ ```text
35
+ .artifacts/structured-summary/
36
+ SKILL.md
37
+ agents/openai.yaml
38
+ references/
39
+ scripts/
40
+ assets/
41
+ ```
42
+
43
+ Degardis writes only beneath the requested output root. Rebuilding a skill
44
+ replaces that skill's existing folder and ZIP there, while preserving unrelated
45
+ entries. See [Artifact format](https://github.com/Khojasteh/degardis/blob/main/docs/artifact-format.md#replace-an-artifact)
46
+ before building directly into an agent's skill directory.
47
+
48
+ Add the example's optional profile or produce a ZIP:
49
+
50
+ ```console
51
+ degardis build examples/structured-summary --profile detailed --output .artifacts
52
+ degardis build examples/structured-summary --zip --output .artifacts
53
+ ```
54
+
55
+ `examples/structured-summary` is the canonical public example used throughout
56
+ the guides and CLI help. It summarizes supplied material from any subject;
57
+ synthetic skills under `tests/fixtures` are compiler test data.
58
+
59
+ ## Companion skills
60
+
61
+ The [Degardis skills catalog](https://github.com/Khojasteh/degardis-skills)
62
+ contains reusable skills authored with Degardis.
63
+
64
+ For the smoothest authoring experience, use
65
+ [Degardis Authoring](https://github.com/Khojasteh/degardis-skills/tree/main/skills/degardis-authoring).
66
+ It guides a skill from initial design through review, validation, packaging,
67
+ and installation while applying Degardis conventions across manifests,
68
+ workflows, entries, profiles, scripts, and assets.
69
+
70
+ ## Commands
71
+
72
+ ```console
73
+ degardis list PATH [PATH ...]
74
+ degardis validate PATH [PATH ...]
75
+ degardis build PATH [PATH ...] --output PATH [--profile [SKILL:]PROFILE] [--zip]
76
+ ```
77
+
78
+ Run `degardis COMMAND --help` for exact options and examples. All commands
79
+ accept individual skill directories, directories containing skills at any
80
+ depth, or a mixture of both. This lets a source tree group skills into
81
+ subdirectories while building all of them with one command.
82
+
83
+ An unqualified profile name applies to every selected skill that defines it.
84
+ `SKILL:PROFILE` selects one owner, and `all` selects all available profiles.
85
+ Supplying explicit selectors replaces manifest defaults for that build.
86
+
87
+ For exact command behavior, profile selectors, source schemas, and the Python
88
+ API, see the [reference](https://github.com/Khojasteh/degardis/blob/main/docs/reference.md).
89
+
90
+ ## Documentation
91
+
92
+ | Reader goal | Document |
93
+ | -------------------------------------------- | ------------------------------------------ |
94
+ | Build and install the example | [Getting started](https://github.com/Khojasteh/degardis/blob/main/docs/getting-started.md) |
95
+ | Understand the source and artifact models | [Concepts](https://github.com/Khojasteh/degardis/blob/main/docs/concepts.md) |
96
+ | Create or modify a skill | [Authoring guide](https://github.com/Khojasteh/degardis/blob/main/docs/authoring-guide.md) |
97
+ | Look up commands, schemas, or the Python API | [Reference](https://github.com/Khojasteh/degardis/blob/main/docs/reference.md) |
98
+ | Inspect and install generated output | [Artifact format](https://github.com/Khojasteh/degardis/blob/main/docs/artifact-format.md) |
99
+
100
+ ## Development
101
+
102
+ ```console
103
+ python -m unittest discover -s tests -v
104
+ python -m degardis validate examples/structured-summary
105
+ python -m degardis build examples/structured-summary --profile all --output .artifacts
106
+ ```
@@ -0,0 +1 @@
1
+ __version__ = "1.0.0"
@@ -0,0 +1,4 @@
1
+ from .cli import main
2
+
3
+
4
+ raise SystemExit(main())
@@ -0,0 +1,90 @@
1
+ from __future__ import annotations
2
+
3
+ import warnings
4
+ from pathlib import Path
5
+ from tempfile import TemporaryDirectory
6
+
7
+ from .model import DegardisError, DegardisWarning
8
+ from .package import (
9
+ ArchivePackager,
10
+ ArtifactWriter,
11
+ replace_skill_artifacts,
12
+ )
13
+ from .resolver import SkillResolver
14
+ from .validate import bundle_warnings, validate_skill
15
+
16
+
17
+ class SkillCompiler:
18
+ def __init__(self, sources: Path | list[Path]) -> None:
19
+ self.resolver = SkillResolver(sources)
20
+ self.writer = ArtifactWriter()
21
+ self.packager = ArchivePackager()
22
+
23
+ def _check_output_path(self, output: Path) -> None:
24
+ resolved_output = output.resolve()
25
+ for source in self.resolver.skill_paths:
26
+ resolved_source = source.resolve()
27
+ if (
28
+ resolved_output == resolved_source
29
+ or resolved_output in resolved_source.parents
30
+ or resolved_source in resolved_output.parents
31
+ ):
32
+ raise DegardisError(
33
+ f"Output directory {resolved_output} must not overlap "
34
+ f"skill source {resolved_source}"
35
+ )
36
+
37
+ def build(
38
+ self,
39
+ output: Path,
40
+ profiles: list[str] | None = None,
41
+ as_zip: bool = False,
42
+ ) -> list[Path]:
43
+ self._check_output_path(output)
44
+ validation_errors = [
45
+ error
46
+ for skill_path in self.resolver.skill_paths
47
+ for error in validate_skill(skill_path)
48
+ ]
49
+ if validation_errors:
50
+ raise DegardisError("; ".join(validation_errors))
51
+ bundles = self.resolver.collect(profiles)
52
+ if not bundles:
53
+ raise DegardisError("at least one skill is required")
54
+ for bundle in bundles:
55
+ for message in bundle_warnings(bundle):
56
+ warnings.warn(message, DegardisWarning, stacklevel=2)
57
+ output.mkdir(parents=True, exist_ok=True)
58
+ paths: list[Path] = []
59
+ for bundle in bundles:
60
+ name = bundle.primary.name
61
+ with TemporaryDirectory(
62
+ prefix="degardis-build-",
63
+ ) as directory:
64
+ staging_root = Path(directory)
65
+ staged_folder = staging_root / name
66
+ self.writer.write_skill(bundle, staged_folder)
67
+ if as_zip:
68
+ destination = output / f"{name}.zip"
69
+ staged = staging_root / f"{name}.zip"
70
+ self.packager.create(staged_folder, staged)
71
+ else:
72
+ destination = output / name
73
+ staged = staged_folder
74
+ replace_skill_artifacts(
75
+ output,
76
+ name,
77
+ staged,
78
+ destination,
79
+ )
80
+ paths.append(destination)
81
+ return paths
82
+
83
+
84
+ def build_skills(
85
+ sources: Path | list[Path],
86
+ output_dir: Path,
87
+ profiles: list[str] | None = None,
88
+ as_zip: bool = False,
89
+ ) -> list[Path]:
90
+ return SkillCompiler(sources).build(output_dir, profiles, as_zip)
@@ -0,0 +1,197 @@
1
+ from __future__ import annotations
2
+
3
+ import argparse
4
+ import os
5
+ import sys
6
+ import warnings
7
+ from pathlib import Path
8
+
9
+ from . import __version__
10
+ from .build import SkillCompiler
11
+ from .model import DegardisError, DegardisWarning
12
+ from .output import (
13
+ write_build_report,
14
+ write_profile_matches,
15
+ write_skill_list,
16
+ write_validation_report,
17
+ )
18
+ from .registry import discover_skill_paths, load_skill_path, load_skill_profiles
19
+ from .resolver import collect_skills, profile_matches
20
+ from .validate import bundle_warnings, validate_skill
21
+
22
+
23
+ HELP_FORMATTER = argparse.RawDescriptionHelpFormatter
24
+
25
+
26
+ def _expand_path(value: str) -> Path:
27
+ """Expand and resolve a CLI path using one uniform policy."""
28
+ return Path(os.path.expandvars(os.path.expanduser(value))).resolve()
29
+
30
+
31
+ def _add_skill_paths(command: argparse.ArgumentParser) -> None:
32
+ command.add_argument(
33
+ "paths",
34
+ nargs="+",
35
+ type=_expand_path,
36
+ metavar="PATH",
37
+ help=(
38
+ "skill directory, or a directory recursively containing skill "
39
+ "directories"
40
+ ),
41
+ )
42
+
43
+
44
+ def parser() -> argparse.ArgumentParser:
45
+ command = argparse.ArgumentParser(
46
+ prog="degardis",
47
+ description="Compile self-contained agent skills into installable bundles.",
48
+ epilog="""\
49
+ Run `degardis COMMAND -h` for that command's options and examples.
50
+
51
+ Examples:
52
+ degardis list examples/structured-summary
53
+ degardis validate examples/structured-summary
54
+ degardis build examples/structured-summary --profile detailed --output .artifacts
55
+ """,
56
+ formatter_class=HELP_FORMATTER,
57
+ )
58
+ command.add_argument(
59
+ "-v",
60
+ "--version",
61
+ action="version",
62
+ version=f"%(prog)s {__version__}",
63
+ )
64
+ subcommands = command.add_subparsers(
65
+ dest="command", required=True, title="commands", metavar="COMMAND"
66
+ )
67
+
68
+ build = subcommands.add_parser(
69
+ "build",
70
+ help="build one or more self-contained skills",
71
+ description="Build selected skills into installable skill bundles.",
72
+ epilog="""\
73
+ Examples:
74
+ degardis build examples/structured-summary --output .artifacts
75
+ degardis build examples/structured-summary --profile detailed --output .artifacts
76
+ degardis build examples/structured-summary --zip --output dist
77
+ """,
78
+ formatter_class=HELP_FORMATTER,
79
+ )
80
+ _add_skill_paths(build)
81
+ build.add_argument(
82
+ "--profile",
83
+ action="append",
84
+ dest="profiles",
85
+ metavar="[SKILL:]PROFILE",
86
+ help=(
87
+ "profile to include; an unqualified name matches every selected "
88
+ "skill, and all includes every selected skill's profiles"
89
+ ),
90
+ )
91
+ build.add_argument(
92
+ "--output",
93
+ type=_expand_path,
94
+ required=True,
95
+ metavar="PATH",
96
+ help="output directory",
97
+ )
98
+ build.add_argument(
99
+ "--zip",
100
+ action="store_true",
101
+ help="output one zip archive per skill instead of an uncompressed folder",
102
+ )
103
+
104
+ validate_command = subcommands.add_parser(
105
+ "validate",
106
+ help="validate one or more skill sources",
107
+ description="Validate explicit skills or recursively discovered skills.",
108
+ epilog="""\
109
+ Examples:
110
+ degardis validate examples/structured-summary
111
+ """,
112
+ formatter_class=HELP_FORMATTER,
113
+ )
114
+ _add_skill_paths(validate_command)
115
+
116
+ list_command = subcommands.add_parser(
117
+ "list",
118
+ help="inspect selected skills and profiles",
119
+ description="Show metadata and profiles for selected skills.",
120
+ epilog="""\
121
+ Examples:
122
+ degardis list examples/structured-summary
123
+ """,
124
+ formatter_class=HELP_FORMATTER,
125
+ )
126
+ _add_skill_paths(list_command)
127
+ return command
128
+
129
+
130
+ def _normalize_help_position(argv: list[str]) -> list[str]:
131
+ """Let ``-h``/``--help`` work before or after the command name.
132
+
133
+ argparse only recognizes ``-h`` on the parser that reaches it, so
134
+ ``degardis -h build`` shows the top-level help instead of the ``build``
135
+ command's help that ``degardis build -h`` shows. Moving a leading
136
+ ``-h``/``--help`` after the command name makes both spellings equivalent.
137
+ """
138
+ if (
139
+ len(argv) >= 2
140
+ and argv[0] in ("-h", "--help")
141
+ and not argv[1].startswith("-")
142
+ ):
143
+ return [argv[1], argv[0], *argv[2:]]
144
+ return argv
145
+
146
+
147
+ def _run(argv: list[str] | None = None) -> int:
148
+ tokens = list(sys.argv[1:] if argv is None else argv)
149
+ args = parser().parse_args(_normalize_help_position(tokens))
150
+ skill_paths = discover_skill_paths(args.paths)
151
+
152
+ if args.command == "list":
153
+ skills = [load_skill_path(path) for path in skill_paths]
154
+ details = [(skill, load_skill_profiles(skill)) for skill in skills]
155
+ write_skill_list(sys.stdout, details)
156
+ return 0
157
+
158
+ if args.command == "validate":
159
+ results = []
160
+ for path in skill_paths:
161
+ skill = load_skill_path(path)
162
+ errors = validate_skill(path)
163
+ skill_warnings = (
164
+ []
165
+ if errors
166
+ else bundle_warnings(collect_skills([path])[0])
167
+ )
168
+ results.append((skill, errors, skill_warnings))
169
+ write_validation_report(sys.stdout, results)
170
+ return int(any(errors for _, errors, _ in results))
171
+
172
+ compiler = SkillCompiler(skill_paths)
173
+ if args.profiles:
174
+ write_profile_matches(
175
+ sys.stdout,
176
+ profile_matches(skill_paths, args.profiles),
177
+ )
178
+ with warnings.catch_warnings(record=True) as caught:
179
+ warnings.simplefilter("always", DegardisWarning)
180
+ paths = compiler.build(args.output, args.profiles, as_zip=args.zip)
181
+ for warning in caught:
182
+ print(f"[WARNING] {warning.message}", file=sys.stderr)
183
+ skills = [load_skill_path(path) for path in skill_paths]
184
+ write_build_report(sys.stdout, skills, paths, as_zip=args.zip)
185
+ return 0
186
+
187
+
188
+ def main(argv: list[str] | None = None) -> int:
189
+ try:
190
+ return _run(argv)
191
+ except (DegardisError, OSError) as error:
192
+ print(f"[ERROR] {error}", file=sys.stderr)
193
+ return 1
194
+
195
+
196
+ if __name__ == "__main__":
197
+ raise SystemExit(main())