spawn-cli 0.1.1__tar.gz → 0.1.3__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.
- {spawn_cli-0.1.1 → spawn_cli-0.1.3}/PKG-INFO +1 -1
- spawn_cli-0.1.3/README.md +114 -0
- {spawn_cli-0.1.1 → spawn_cli-0.1.3}/pyproject.toml +1 -1
- spawn_cli-0.1.3/src/spawn_cli/cli.py +454 -0
- {spawn_cli-0.1.1 → spawn_cli-0.1.3}/src/spawn_cli/core/low_level.py +0 -5
- {spawn_cli-0.1.1 → spawn_cli-0.1.3}/src/spawn_cli/ide/_stub.py +1 -12
- {spawn_cli-0.1.1 → spawn_cli-0.1.3}/src/spawn_cli/ide/registry.py +0 -1
- {spawn_cli-0.1.1 → spawn_cli-0.1.3}/src/spawn_cli.egg-info/PKG-INFO +1 -1
- {spawn_cli-0.1.1 → spawn_cli-0.1.3}/src/spawn_cli.egg-info/SOURCES.txt +1 -0
- {spawn_cli-0.1.1 → spawn_cli-0.1.3}/tests/test_cli.py +9 -0
- spawn_cli-0.1.1/src/spawn_cli/cli.py +0 -243
- {spawn_cli-0.1.1 → spawn_cli-0.1.3}/setup.cfg +0 -0
- {spawn_cli-0.1.1 → spawn_cli-0.1.3}/src/spawn_cli/__init__.py +0 -0
- {spawn_cli-0.1.1 → spawn_cli-0.1.3}/src/spawn_cli/core/__init__.py +0 -0
- {spawn_cli-0.1.1 → spawn_cli-0.1.3}/src/spawn_cli/core/download.py +0 -0
- {spawn_cli-0.1.1 → spawn_cli-0.1.3}/src/spawn_cli/core/errors.py +0 -0
- {spawn_cli-0.1.1 → spawn_cli-0.1.3}/src/spawn_cli/core/high_level.py +0 -0
- {spawn_cli-0.1.1 → spawn_cli-0.1.3}/src/spawn_cli/core/scripts.py +0 -0
- {spawn_cli-0.1.1 → spawn_cli-0.1.3}/src/spawn_cli/errors.py +0 -0
- {spawn_cli-0.1.1 → spawn_cli-0.1.3}/src/spawn_cli/ide/__init__.py +0 -0
- {spawn_cli-0.1.1 → spawn_cli-0.1.3}/src/spawn_cli/ide/_helpers.py +0 -0
- {spawn_cli-0.1.1 → spawn_cli-0.1.3}/src/spawn_cli/ide/_vacancy.py +0 -0
- {spawn_cli-0.1.1 → spawn_cli-0.1.3}/src/spawn_cli/ide/claude_code.py +0 -0
- {spawn_cli-0.1.1 → spawn_cli-0.1.3}/src/spawn_cli/ide/codex.py +0 -0
- {spawn_cli-0.1.1 → spawn_cli-0.1.3}/src/spawn_cli/ide/cursor.py +0 -0
- {spawn_cli-0.1.1 → spawn_cli-0.1.3}/src/spawn_cli/ide/gemini_cli.py +0 -0
- {spawn_cli-0.1.1 → spawn_cli-0.1.3}/src/spawn_cli/ide/github_copilot.py +0 -0
- {spawn_cli-0.1.1 → spawn_cli-0.1.3}/src/spawn_cli/ide/windsurf.py +0 -0
- {spawn_cli-0.1.1 → spawn_cli-0.1.3}/src/spawn_cli/io/__init__.py +0 -0
- {spawn_cli-0.1.1 → spawn_cli-0.1.3}/src/spawn_cli/io/json_io.py +0 -0
- {spawn_cli-0.1.1 → spawn_cli-0.1.3}/src/spawn_cli/io/lock.py +0 -0
- {spawn_cli-0.1.1 → spawn_cli-0.1.3}/src/spawn_cli/io/paths.py +0 -0
- {spawn_cli-0.1.1 → spawn_cli-0.1.3}/src/spawn_cli/io/text_io.py +0 -0
- {spawn_cli-0.1.1 → spawn_cli-0.1.3}/src/spawn_cli/io/toml_io.py +0 -0
- {spawn_cli-0.1.1 → spawn_cli-0.1.3}/src/spawn_cli/io/yaml_io.py +0 -0
- {spawn_cli-0.1.1 → spawn_cli-0.1.3}/src/spawn_cli/models/__init__.py +0 -0
- {spawn_cli-0.1.1 → spawn_cli-0.1.3}/src/spawn_cli/models/config.py +0 -0
- {spawn_cli-0.1.1 → spawn_cli-0.1.3}/src/spawn_cli/models/mcp.py +0 -0
- {spawn_cli-0.1.1 → spawn_cli-0.1.3}/src/spawn_cli/models/metadata.py +0 -0
- {spawn_cli-0.1.1 → spawn_cli-0.1.3}/src/spawn_cli/models/navigation.py +0 -0
- {spawn_cli-0.1.1 → spawn_cli-0.1.3}/src/spawn_cli/models/skill.py +0 -0
- {spawn_cli-0.1.1 → spawn_cli-0.1.3}/src/spawn_cli/resources/__init__.py +0 -0
- {spawn_cli-0.1.1 → spawn_cli-0.1.3}/src/spawn_cli/resources/default_core_config.yaml +0 -0
- {spawn_cli-0.1.1 → spawn_cli-0.1.3}/src/spawn_cli/warnings_display.py +0 -0
- {spawn_cli-0.1.1 → spawn_cli-0.1.3}/src/spawn_cli.egg-info/dependency_links.txt +0 -0
- {spawn_cli-0.1.1 → spawn_cli-0.1.3}/src/spawn_cli.egg-info/entry_points.txt +0 -0
- {spawn_cli-0.1.1 → spawn_cli-0.1.3}/src/spawn_cli.egg-info/requires.txt +0 -0
- {spawn_cli-0.1.1 → spawn_cli-0.1.3}/src/spawn_cli.egg-info/top_level.txt +0 -0
- {spawn_cli-0.1.1 → spawn_cli-0.1.3}/tests/test_warnings_display.py +0 -0
|
@@ -0,0 +1,114 @@
|
|
|
1
|
+
# spawn-cli
|
|
2
|
+
|
|
3
|
+
Installable CLI **`spawn`** for initializing and managing Spawn metadata (extensions, IDE adapters, navigation) in a repository.
|
|
4
|
+
|
|
5
|
+
## Quick start (uv)
|
|
6
|
+
|
|
7
|
+
Install `spawn` on your `PATH` (persistent **uv** tool environment):
|
|
8
|
+
|
|
9
|
+
```bash
|
|
10
|
+
uv tool install spawn-cli && spawn --help
|
|
11
|
+
```
|
|
12
|
+
|
|
13
|
+
Run once without installing a tool (ephemeral **uvx**):
|
|
14
|
+
|
|
15
|
+
```bash
|
|
16
|
+
uvx --from spawn-cli spawn --help
|
|
17
|
+
```
|
|
18
|
+
|
|
19
|
+
## Upgrade (uv)
|
|
20
|
+
|
|
21
|
+
Upgrade the persistent tool:
|
|
22
|
+
|
|
23
|
+
```bash
|
|
24
|
+
uv tool upgrade spawn-cli && spawn --help
|
|
25
|
+
```
|
|
26
|
+
|
|
27
|
+
Latest resolver result for a one-shot run:
|
|
28
|
+
|
|
29
|
+
```bash
|
|
30
|
+
uvx --refresh --from spawn-cli spawn --help
|
|
31
|
+
```
|
|
32
|
+
|
|
33
|
+
---
|
|
34
|
+
|
|
35
|
+
- **Python:** 3.10+ (see `pyproject.toml`).
|
|
36
|
+
- **Usage:** run from the **root of the repository** you configure; most commands need `spawn init` first.
|
|
37
|
+
|
|
38
|
+
## Install
|
|
39
|
+
|
|
40
|
+
The PyPI package name is **`spawn-cli`**; the console script is **`spawn`**.
|
|
41
|
+
|
|
42
|
+
### From PyPI
|
|
43
|
+
|
|
44
|
+
```bash
|
|
45
|
+
pip install spawn-cli
|
|
46
|
+
spawn --help
|
|
47
|
+
```
|
|
48
|
+
|
|
49
|
+
Isolated install:
|
|
50
|
+
|
|
51
|
+
```bash
|
|
52
|
+
pipx install spawn-cli
|
|
53
|
+
spawn --help
|
|
54
|
+
```
|
|
55
|
+
|
|
56
|
+
With **uv** (tool on `PATH`):
|
|
57
|
+
|
|
58
|
+
```bash
|
|
59
|
+
uv tool install spawn-cli
|
|
60
|
+
spawn --help
|
|
61
|
+
```
|
|
62
|
+
|
|
63
|
+
One-shot without installing into an environment:
|
|
64
|
+
|
|
65
|
+
```bash
|
|
66
|
+
uvx --from spawn-cli spawn --help
|
|
67
|
+
```
|
|
68
|
+
|
|
69
|
+
### From a local checkout
|
|
70
|
+
|
|
71
|
+
```bash
|
|
72
|
+
cd /path/to/spawn-cli
|
|
73
|
+
uv sync
|
|
74
|
+
uv run spawn --help
|
|
75
|
+
```
|
|
76
|
+
|
|
77
|
+
Or editable install with **pip**:
|
|
78
|
+
|
|
79
|
+
```bash
|
|
80
|
+
cd /path/to/spawn-cli
|
|
81
|
+
pip install -e .
|
|
82
|
+
spawn --help
|
|
83
|
+
```
|
|
84
|
+
|
|
85
|
+
## Upgrade
|
|
86
|
+
|
|
87
|
+
Use the same installer you used for the initial install. **If you use uv only, see [Upgrade (uv)](#upgrade-uv) at the top.**
|
|
88
|
+
|
|
89
|
+
**pip** (current environment):
|
|
90
|
+
|
|
91
|
+
```bash
|
|
92
|
+
pip install --upgrade spawn-cli
|
|
93
|
+
spawn --help
|
|
94
|
+
```
|
|
95
|
+
|
|
96
|
+
**pipx**:
|
|
97
|
+
|
|
98
|
+
```bash
|
|
99
|
+
pipx upgrade spawn-cli
|
|
100
|
+
spawn --help
|
|
101
|
+
```
|
|
102
|
+
|
|
103
|
+
**Local checkout:** pull changes, then reinstall so the editable install sees updates:
|
|
104
|
+
|
|
105
|
+
```bash
|
|
106
|
+
cd /path/to/spawn-cli
|
|
107
|
+
git pull
|
|
108
|
+
uv sync
|
|
109
|
+
# or: pip install -e .
|
|
110
|
+
```
|
|
111
|
+
|
|
112
|
+
## More
|
|
113
|
+
|
|
114
|
+
Design and command details: `spec/design/user-guide.md` and `spec/design/utility.md`.
|
|
@@ -0,0 +1,454 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
import argparse
|
|
4
|
+
import sys
|
|
5
|
+
from pathlib import Path
|
|
6
|
+
|
|
7
|
+
from spawn_cli import __version__
|
|
8
|
+
from spawn_cli.core import download as dl
|
|
9
|
+
from spawn_cli.core import high_level as hl
|
|
10
|
+
from spawn_cli.core import low_level as ll
|
|
11
|
+
from spawn_cli.core.errors import SpawnError
|
|
12
|
+
from spawn_cli.ide.registry import DetectResult, detect_supported_ides
|
|
13
|
+
from spawn_cli.io.lock import spawn_lock
|
|
14
|
+
from spawn_cli.warnings_display import install_spawn_warning_format
|
|
15
|
+
|
|
16
|
+
|
|
17
|
+
def _require_init(target_root: Path) -> None:
|
|
18
|
+
if not (target_root / "spawn").is_dir():
|
|
19
|
+
raise SpawnError("need init before: run spawn init in this repository.")
|
|
20
|
+
|
|
21
|
+
|
|
22
|
+
def build_parser() -> argparse.ArgumentParser:
|
|
23
|
+
parser = argparse.ArgumentParser(
|
|
24
|
+
prog="spawn",
|
|
25
|
+
description="Spawn CLI: manage Spawn extensions and IDE adapters in this repository.",
|
|
26
|
+
)
|
|
27
|
+
parser.add_argument(
|
|
28
|
+
"--version",
|
|
29
|
+
action="version",
|
|
30
|
+
version=f"spawn {__version__}",
|
|
31
|
+
)
|
|
32
|
+
sub = parser.add_subparsers(dest="command", required=True)
|
|
33
|
+
|
|
34
|
+
sub.add_parser(
|
|
35
|
+
"init",
|
|
36
|
+
help="Initialize spawn/ in the current repository",
|
|
37
|
+
description=(
|
|
38
|
+
"Create the spawn/ tree in the repository root "
|
|
39
|
+
"(config, metadata layout, navigation stub)."
|
|
40
|
+
),
|
|
41
|
+
)
|
|
42
|
+
|
|
43
|
+
rules_p = sub.add_parser(
|
|
44
|
+
"rules",
|
|
45
|
+
help="Rebuild navigation mandatory reads from spawn/rules/",
|
|
46
|
+
description="Refresh navigation.yaml from files under spawn/rules/.",
|
|
47
|
+
)
|
|
48
|
+
rules_sub = rules_p.add_subparsers(dest="rules_command", required=True)
|
|
49
|
+
rules_sub.add_parser(
|
|
50
|
+
"refresh",
|
|
51
|
+
help="Sync spawn/rules/ into navigation.yaml",
|
|
52
|
+
description=(
|
|
53
|
+
"Scan spawn/rules/ and rewrite read-required paths in spawn/navigation.yaml."
|
|
54
|
+
),
|
|
55
|
+
)
|
|
56
|
+
|
|
57
|
+
ide_p = sub.add_parser(
|
|
58
|
+
"ide",
|
|
59
|
+
help="Add, remove, or list IDE adapters registered for this repo",
|
|
60
|
+
description=(
|
|
61
|
+
"Control which IDE keys Spawn configures (rendered metadata under spawn/.metadata/)."
|
|
62
|
+
),
|
|
63
|
+
)
|
|
64
|
+
ide_sub = ide_p.add_subparsers(dest="ide_command", required=True)
|
|
65
|
+
|
|
66
|
+
_ide_add = ide_sub.add_parser(
|
|
67
|
+
"add",
|
|
68
|
+
help="Install metadata rendering for IDE key(s)",
|
|
69
|
+
description="Install metadata rendering for one or more IDE keys (cursor, codex, ...).",
|
|
70
|
+
)
|
|
71
|
+
_ide_add.add_argument(
|
|
72
|
+
"ides",
|
|
73
|
+
nargs="+",
|
|
74
|
+
metavar="IDE",
|
|
75
|
+
help="IDE identifiers to add (canonical keys from Spawn).",
|
|
76
|
+
)
|
|
77
|
+
|
|
78
|
+
_ide_remove = ide_sub.add_parser(
|
|
79
|
+
"remove",
|
|
80
|
+
help="Unregister IDE key(s) and clean vacancy where supported",
|
|
81
|
+
description="Unregister IDE keys and drop rendered outputs when the adapter defines cleanup.",
|
|
82
|
+
)
|
|
83
|
+
_ide_remove.add_argument(
|
|
84
|
+
"ides",
|
|
85
|
+
nargs="+",
|
|
86
|
+
metavar="IDE",
|
|
87
|
+
help="IDE identifiers to remove.",
|
|
88
|
+
)
|
|
89
|
+
|
|
90
|
+
ide_sub.add_parser(
|
|
91
|
+
"list",
|
|
92
|
+
help="Print registered IDE identifiers for this repository",
|
|
93
|
+
description="Print one IDE identifier per line from the Spawn registry.",
|
|
94
|
+
)
|
|
95
|
+
ide_sub.add_parser(
|
|
96
|
+
"list-supported-ides",
|
|
97
|
+
help="Print YAML map of IDE keys to detected capabilities",
|
|
98
|
+
description=(
|
|
99
|
+
"Probe installed IDE adapters on this machine; emit YAML suited for tooling."
|
|
100
|
+
),
|
|
101
|
+
)
|
|
102
|
+
|
|
103
|
+
ext_p = sub.add_parser(
|
|
104
|
+
"extension",
|
|
105
|
+
help="Install, scaffold, upgrade, validate, or list Spawn extensions",
|
|
106
|
+
description=(
|
|
107
|
+
"Manage Spawn extensions under spawn/.extend/ (sources, reinstall, scaffold, checks)."
|
|
108
|
+
),
|
|
109
|
+
)
|
|
110
|
+
ext_sub = ext_p.add_subparsers(dest="ext_command", required=True)
|
|
111
|
+
|
|
112
|
+
_ext_add = ext_sub.add_parser(
|
|
113
|
+
"add",
|
|
114
|
+
help="Fetch and register an extension (git archive, ZIP, local path)",
|
|
115
|
+
description="Download or copy an extension into spawn/.extend/ and refresh renders.",
|
|
116
|
+
)
|
|
117
|
+
_ext_add.add_argument(
|
|
118
|
+
"path",
|
|
119
|
+
metavar="SOURCE",
|
|
120
|
+
help="Extension source URL, git ref, ZIP path, or local directory/archive.",
|
|
121
|
+
)
|
|
122
|
+
_ext_add.add_argument(
|
|
123
|
+
"--branch",
|
|
124
|
+
default=None,
|
|
125
|
+
metavar="REV",
|
|
126
|
+
help="Git revision to use when the source is a Git URL (ignored for non-git sources).",
|
|
127
|
+
)
|
|
128
|
+
|
|
129
|
+
_ext_update = ext_sub.add_parser(
|
|
130
|
+
"update",
|
|
131
|
+
help="Pull or resync extension content from recorded source.yaml",
|
|
132
|
+
description="Upgrade an installed extension to its configured upstream revision.",
|
|
133
|
+
)
|
|
134
|
+
_ext_update.add_argument(
|
|
135
|
+
"extension_name",
|
|
136
|
+
metavar="NAME",
|
|
137
|
+
help="Installed extension name (directory spawn/.extend/<name>/).",
|
|
138
|
+
)
|
|
139
|
+
|
|
140
|
+
_ext_remove = ext_sub.add_parser(
|
|
141
|
+
"remove",
|
|
142
|
+
help="Unregister and delete an extension from this repo",
|
|
143
|
+
description="Remove spawn/.extend/<name>/ entry and unload extension-side scripts.",
|
|
144
|
+
)
|
|
145
|
+
_ext_remove.add_argument(
|
|
146
|
+
"extension_name",
|
|
147
|
+
metavar="NAME",
|
|
148
|
+
help="Installed extension name to remove.",
|
|
149
|
+
)
|
|
150
|
+
|
|
151
|
+
_ext_reinstall = ext_sub.add_parser(
|
|
152
|
+
"reinstall",
|
|
153
|
+
help="Re-download extension from spawn/.extend/<name>/source.yaml",
|
|
154
|
+
description="Staging reinstall from cached source.yaml (recovery after corrupted tree).",
|
|
155
|
+
)
|
|
156
|
+
_ext_reinstall.add_argument(
|
|
157
|
+
"extension_name",
|
|
158
|
+
metavar="NAME",
|
|
159
|
+
help="Installed extension name to reinstall.",
|
|
160
|
+
)
|
|
161
|
+
|
|
162
|
+
ext_sub.add_parser(
|
|
163
|
+
"list",
|
|
164
|
+
help="Print installed extension names",
|
|
165
|
+
description="Emit one extension name per line.",
|
|
166
|
+
)
|
|
167
|
+
|
|
168
|
+
_ext_init = ext_sub.add_parser(
|
|
169
|
+
"init",
|
|
170
|
+
help="Create a Spawn extension scaffold from a filesystem path",
|
|
171
|
+
description="Lay out skeleton files for a new extension under the chosen directory.",
|
|
172
|
+
)
|
|
173
|
+
_ext_init.add_argument(
|
|
174
|
+
"path",
|
|
175
|
+
nargs="?",
|
|
176
|
+
default=".",
|
|
177
|
+
metavar="PATH",
|
|
178
|
+
help="Directory receiving the scaffold (defaults to '.').",
|
|
179
|
+
)
|
|
180
|
+
_ext_init.add_argument(
|
|
181
|
+
"--name",
|
|
182
|
+
required=True,
|
|
183
|
+
metavar="ID",
|
|
184
|
+
help="Canonical extension id used in spawn/.extend/<id>/ when installed.",
|
|
185
|
+
)
|
|
186
|
+
|
|
187
|
+
_ext_check = ext_sub.add_parser(
|
|
188
|
+
"check",
|
|
189
|
+
help="Lint extension workspace against authoring rules",
|
|
190
|
+
description="Warn on missing declares, dangling paths, skill layout issues.",
|
|
191
|
+
)
|
|
192
|
+
_ext_check.add_argument(
|
|
193
|
+
"path",
|
|
194
|
+
nargs="?",
|
|
195
|
+
default=".",
|
|
196
|
+
metavar="PATH",
|
|
197
|
+
help="Extension project root directory to validate.",
|
|
198
|
+
)
|
|
199
|
+
_ext_check.add_argument(
|
|
200
|
+
"--strict",
|
|
201
|
+
action="store_true",
|
|
202
|
+
help=(
|
|
203
|
+
"Fail on issues that default to warnings "
|
|
204
|
+
"(missing skill files, bad mcp.json, empty file descriptions)."
|
|
205
|
+
),
|
|
206
|
+
)
|
|
207
|
+
|
|
208
|
+
_ext_from_rules = ext_sub.add_parser(
|
|
209
|
+
"from-rules",
|
|
210
|
+
help="Compose an extension scaffold from YAML rules-derived sources",
|
|
211
|
+
description="Hydrate packaged rules into a scaffold under --output.",
|
|
212
|
+
)
|
|
213
|
+
_ext_from_rules.add_argument(
|
|
214
|
+
"source",
|
|
215
|
+
metavar="SOURCE",
|
|
216
|
+
help=(
|
|
217
|
+
"Repository root containing spawn/rules (local dir), "
|
|
218
|
+
"or remote URL/Git ref staged before import."
|
|
219
|
+
),
|
|
220
|
+
)
|
|
221
|
+
_ext_from_rules.add_argument(
|
|
222
|
+
"--name",
|
|
223
|
+
required=True,
|
|
224
|
+
metavar="ID",
|
|
225
|
+
help="Canonical extension id for the generated scaffold.",
|
|
226
|
+
)
|
|
227
|
+
_ext_from_rules.add_argument(
|
|
228
|
+
"--branch",
|
|
229
|
+
default=None,
|
|
230
|
+
metavar="REV",
|
|
231
|
+
help="Revision for Git-backed sources referenced by SOURCE.",
|
|
232
|
+
)
|
|
233
|
+
_ext_from_rules.add_argument(
|
|
234
|
+
"--output",
|
|
235
|
+
default=".",
|
|
236
|
+
metavar="DIR",
|
|
237
|
+
help="Existing directory receiving new extension files.",
|
|
238
|
+
)
|
|
239
|
+
|
|
240
|
+
_ext_health = ext_sub.add_parser(
|
|
241
|
+
"healthcheck",
|
|
242
|
+
help="Verify installed extension manifests and renders",
|
|
243
|
+
description="Smoke-test extension installs (non-zero exit on failure).",
|
|
244
|
+
)
|
|
245
|
+
_ext_health.add_argument(
|
|
246
|
+
"extension_name",
|
|
247
|
+
metavar="NAME",
|
|
248
|
+
help="Installed extension name to diagnose.",
|
|
249
|
+
)
|
|
250
|
+
|
|
251
|
+
build_p = sub.add_parser(
|
|
252
|
+
"build",
|
|
253
|
+
help="Install extensions listed in an aggregate build YAML manifest",
|
|
254
|
+
description="Batch installs driven by YAML enumerating URLs or paths.",
|
|
255
|
+
)
|
|
256
|
+
build_sub = build_p.add_subparsers(dest="build_command", required=True)
|
|
257
|
+
|
|
258
|
+
_build_install = build_sub.add_parser(
|
|
259
|
+
"install",
|
|
260
|
+
help="Resolve manifest entries and spawn extension add each",
|
|
261
|
+
description="Iterate manifest entries calling install-extension logic serially.",
|
|
262
|
+
)
|
|
263
|
+
_build_install.add_argument(
|
|
264
|
+
"path",
|
|
265
|
+
metavar="MANIFEST",
|
|
266
|
+
help="YAML file listing extensions to install.",
|
|
267
|
+
)
|
|
268
|
+
_build_install.add_argument(
|
|
269
|
+
"--branch",
|
|
270
|
+
default=None,
|
|
271
|
+
metavar="REV",
|
|
272
|
+
help="Fallback Git revision for entries omitting explicit branch.",
|
|
273
|
+
)
|
|
274
|
+
|
|
275
|
+
_build_list = build_sub.add_parser(
|
|
276
|
+
"list",
|
|
277
|
+
help="Dry-run: print resolved manifest entries without installing",
|
|
278
|
+
description="Normalize manifest and emit YAML of resolved extension specs.",
|
|
279
|
+
)
|
|
280
|
+
_build_list.add_argument(
|
|
281
|
+
"path",
|
|
282
|
+
metavar="MANIFEST",
|
|
283
|
+
help="YAML aggregate manifest describing extension sources.",
|
|
284
|
+
)
|
|
285
|
+
_build_list.add_argument(
|
|
286
|
+
"--branch",
|
|
287
|
+
default=None,
|
|
288
|
+
metavar="REV",
|
|
289
|
+
help="Fallback Git revision applied when entries lack branch.",
|
|
290
|
+
)
|
|
291
|
+
|
|
292
|
+
return parser
|
|
293
|
+
|
|
294
|
+
|
|
295
|
+
def main(argv: list[str] | None = None) -> int:
|
|
296
|
+
install_spawn_warning_format()
|
|
297
|
+
parser = build_parser()
|
|
298
|
+
args = parser.parse_args(argv)
|
|
299
|
+
target_root = Path.cwd().resolve()
|
|
300
|
+
|
|
301
|
+
try:
|
|
302
|
+
return _dispatch(args, target_root)
|
|
303
|
+
except SpawnError as e:
|
|
304
|
+
print(f"Error: {e}", file=sys.stderr)
|
|
305
|
+
return 1
|
|
306
|
+
except Exception as e:
|
|
307
|
+
print(f"Unexpected error: {e}", file=sys.stderr)
|
|
308
|
+
return 2
|
|
309
|
+
|
|
310
|
+
|
|
311
|
+
def _dispatch(args: argparse.Namespace, target_root: Path) -> int:
|
|
312
|
+
cmd = args.command
|
|
313
|
+
|
|
314
|
+
if cmd == "init":
|
|
315
|
+
with spawn_lock(target_root):
|
|
316
|
+
ll.init(target_root)
|
|
317
|
+
return 0
|
|
318
|
+
|
|
319
|
+
_require_init(target_root)
|
|
320
|
+
|
|
321
|
+
with spawn_lock(target_root):
|
|
322
|
+
if cmd == "rules":
|
|
323
|
+
return _dispatch_rules(args, target_root)
|
|
324
|
+
if cmd == "ide":
|
|
325
|
+
return _dispatch_ide(args, target_root)
|
|
326
|
+
if cmd == "extension":
|
|
327
|
+
return _dispatch_extension(args, target_root)
|
|
328
|
+
if cmd == "build":
|
|
329
|
+
return _dispatch_build(args, target_root)
|
|
330
|
+
|
|
331
|
+
return 1
|
|
332
|
+
|
|
333
|
+
|
|
334
|
+
def _dispatch_rules(args: argparse.Namespace, target_root: Path) -> int:
|
|
335
|
+
sub = args.rules_command
|
|
336
|
+
if sub == "refresh":
|
|
337
|
+
hl.refresh_rules_navigation(target_root)
|
|
338
|
+
return 0
|
|
339
|
+
return 1
|
|
340
|
+
|
|
341
|
+
|
|
342
|
+
def _dispatch_ide(args: argparse.Namespace, target_root: Path) -> int:
|
|
343
|
+
sub = args.ide_command
|
|
344
|
+
|
|
345
|
+
if sub == "list-supported-ides":
|
|
346
|
+
results = detect_supported_ides(target_root)
|
|
347
|
+
_print_yaml(results)
|
|
348
|
+
return 0
|
|
349
|
+
|
|
350
|
+
if sub == "list":
|
|
351
|
+
ides = ll.list_ides(target_root)
|
|
352
|
+
for ide in ides:
|
|
353
|
+
print(ide)
|
|
354
|
+
return 0
|
|
355
|
+
|
|
356
|
+
if sub == "add":
|
|
357
|
+
for ide in args.ides:
|
|
358
|
+
hl.add_ide(target_root, ide)
|
|
359
|
+
return 0
|
|
360
|
+
|
|
361
|
+
if sub == "remove":
|
|
362
|
+
for ide in args.ides:
|
|
363
|
+
hl.remove_ide(target_root, ide)
|
|
364
|
+
return 0
|
|
365
|
+
|
|
366
|
+
return 1
|
|
367
|
+
|
|
368
|
+
|
|
369
|
+
def _dispatch_extension(args: argparse.Namespace, target_root: Path) -> int:
|
|
370
|
+
sub = args.ext_command
|
|
371
|
+
|
|
372
|
+
if sub == "list":
|
|
373
|
+
for ext in ll.list_extensions(target_root):
|
|
374
|
+
print(ext)
|
|
375
|
+
return 0
|
|
376
|
+
|
|
377
|
+
if sub == "add":
|
|
378
|
+
dl.install_extension(target_root, args.path, args.branch)
|
|
379
|
+
return 0
|
|
380
|
+
|
|
381
|
+
if sub == "update":
|
|
382
|
+
hl.update_extension(target_root, args.extension_name)
|
|
383
|
+
return 0
|
|
384
|
+
|
|
385
|
+
if sub == "remove":
|
|
386
|
+
hl.remove_extension(target_root, args.extension_name)
|
|
387
|
+
return 0
|
|
388
|
+
|
|
389
|
+
if sub == "reinstall":
|
|
390
|
+
hl.reinstall_extension(target_root, args.extension_name)
|
|
391
|
+
return 0
|
|
392
|
+
|
|
393
|
+
if sub == "init":
|
|
394
|
+
hl.extension_init(Path(args.path).resolve(), args.name)
|
|
395
|
+
return 0
|
|
396
|
+
|
|
397
|
+
if sub == "check":
|
|
398
|
+
warn_list = hl.extension_check(Path(args.path).resolve(), strict=args.strict)
|
|
399
|
+
for w in warn_list:
|
|
400
|
+
print(f"Warning: {w}")
|
|
401
|
+
return 0
|
|
402
|
+
|
|
403
|
+
if sub == "from-rules":
|
|
404
|
+
hl.extension_from_rules(
|
|
405
|
+
args.source,
|
|
406
|
+
Path(args.output).resolve(),
|
|
407
|
+
args.name,
|
|
408
|
+
args.branch,
|
|
409
|
+
)
|
|
410
|
+
return 0
|
|
411
|
+
|
|
412
|
+
if sub == "healthcheck":
|
|
413
|
+
ok = hl.extension_healthcheck(target_root, args.extension_name)
|
|
414
|
+
return 0 if ok else 1
|
|
415
|
+
|
|
416
|
+
return 1
|
|
417
|
+
|
|
418
|
+
|
|
419
|
+
def _dispatch_build(args: argparse.Namespace, target_root: Path) -> int:
|
|
420
|
+
sub = args.build_command
|
|
421
|
+
|
|
422
|
+
if sub == "list":
|
|
423
|
+
exts = dl.list_build_extensions(args.path, args.branch)
|
|
424
|
+
_print_yaml(exts)
|
|
425
|
+
return 0
|
|
426
|
+
|
|
427
|
+
if sub == "install":
|
|
428
|
+
dl.install_build(target_root, args.path, args.branch)
|
|
429
|
+
return 0
|
|
430
|
+
|
|
431
|
+
return 1
|
|
432
|
+
|
|
433
|
+
|
|
434
|
+
def _print_yaml(data: object) -> None:
|
|
435
|
+
"""Serialize data to YAML. DetectResult dataclass values use utility.md output shape."""
|
|
436
|
+
from ruamel.yaml import YAML
|
|
437
|
+
|
|
438
|
+
from spawn_cli.io.yaml_io import configure_yaml_dump
|
|
439
|
+
|
|
440
|
+
def _to_serializable(obj: object) -> object:
|
|
441
|
+
if isinstance(obj, DetectResult):
|
|
442
|
+
return {
|
|
443
|
+
"used-in-repo": obj.used_in_repo,
|
|
444
|
+
"capabilities": obj.capabilities.to_dict(),
|
|
445
|
+
}
|
|
446
|
+
if isinstance(obj, dict):
|
|
447
|
+
return {k: _to_serializable(v) for k, v in obj.items()}
|
|
448
|
+
if isinstance(obj, list):
|
|
449
|
+
return [_to_serializable(i) for i in obj]
|
|
450
|
+
return obj
|
|
451
|
+
|
|
452
|
+
yaml = YAML()
|
|
453
|
+
configure_yaml_dump(yaml)
|
|
454
|
+
yaml.dump(_to_serializable(data), sys.stdout)
|
|
@@ -25,15 +25,10 @@ from spawn_cli.models.skill import SkillFileRef, SkillMetadata, SkillRawInfo
|
|
|
25
25
|
CANONICAL_IDE_KEYS: tuple[str, ...] = (
|
|
26
26
|
"cursor",
|
|
27
27
|
"codex",
|
|
28
|
-
"qoder",
|
|
29
28
|
"claude-code",
|
|
30
|
-
"qwen-code",
|
|
31
29
|
"windsurf",
|
|
32
30
|
"github-copilot",
|
|
33
|
-
"aider",
|
|
34
|
-
"zed",
|
|
35
31
|
"gemini-cli",
|
|
36
|
-
"devin",
|
|
37
32
|
)
|
|
38
33
|
|
|
39
34
|
SPAWN_GITIGNORE_START = "# spawn:start"
|
|
@@ -3,7 +3,7 @@ from __future__ import annotations
|
|
|
3
3
|
import warnings
|
|
4
4
|
from pathlib import Path
|
|
5
5
|
|
|
6
|
-
from .registry import DetectResult, IdeAdapter, IdeCapabilities
|
|
6
|
+
from .registry import DetectResult, IdeAdapter, IdeCapabilities
|
|
7
7
|
from spawn_cli.models.mcp import NormalizedMcp
|
|
8
8
|
from spawn_cli.models.skill import SkillMetadata
|
|
9
9
|
|
|
@@ -49,15 +49,4 @@ class StubAdapter(IdeAdapter):
|
|
|
49
49
|
return ""
|
|
50
50
|
|
|
51
51
|
|
|
52
|
-
_STUBS: list[tuple[str, IdeCapabilities]] = [
|
|
53
|
-
("qoder", IdeCapabilities("native", "project", "project", "agents-md")),
|
|
54
|
-
("qwen-code", IdeCapabilities("native", "project", "project", "qwen-md")),
|
|
55
|
-
("aider", IdeCapabilities("entry-only", "unsupported", "project", "conventions-md")),
|
|
56
|
-
("zed", IdeCapabilities("unsupported", "project", "unsupported", "agents-md")),
|
|
57
|
-
("devin", IdeCapabilities("native", "project", "unsupported", "agents-md")),
|
|
58
|
-
]
|
|
59
|
-
|
|
60
|
-
for _key, _caps in _STUBS:
|
|
61
|
-
register(StubAdapter(_key, _caps))
|
|
62
|
-
|
|
63
52
|
__all__ = ["StubAdapter"]
|
|
@@ -23,6 +23,15 @@ def test_spawn_help(capsys):
|
|
|
23
23
|
assert "spawn" in capsys.readouterr().out
|
|
24
24
|
|
|
25
25
|
|
|
26
|
+
def test_spawn_extension_add_help_shows_argument_descriptions(capsys):
|
|
27
|
+
with pytest.raises(SystemExit) as exc:
|
|
28
|
+
main(["extension", "add", "--help"])
|
|
29
|
+
assert exc.value.code == 0
|
|
30
|
+
out = capsys.readouterr().out
|
|
31
|
+
assert "--branch" in out
|
|
32
|
+
assert "Git" in out or "git" in out
|
|
33
|
+
|
|
34
|
+
|
|
26
35
|
def test_spawn_init(tmp_path, monkeypatch):
|
|
27
36
|
monkeypatch.chdir(tmp_path)
|
|
28
37
|
root = tmp_path.resolve()
|
|
@@ -1,243 +0,0 @@
|
|
|
1
|
-
from __future__ import annotations
|
|
2
|
-
|
|
3
|
-
import argparse
|
|
4
|
-
import sys
|
|
5
|
-
from pathlib import Path
|
|
6
|
-
|
|
7
|
-
from spawn_cli import __version__
|
|
8
|
-
from spawn_cli.core import download as dl
|
|
9
|
-
from spawn_cli.core import high_level as hl
|
|
10
|
-
from spawn_cli.core import low_level as ll
|
|
11
|
-
from spawn_cli.core.errors import SpawnError
|
|
12
|
-
from spawn_cli.ide.registry import DetectResult, detect_supported_ides
|
|
13
|
-
from spawn_cli.io.lock import spawn_lock
|
|
14
|
-
from spawn_cli.warnings_display import install_spawn_warning_format
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
def _require_init(target_root: Path) -> None:
|
|
18
|
-
if not (target_root / "spawn").is_dir():
|
|
19
|
-
raise SpawnError("need init before: run spawn init in this repository.")
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
def build_parser() -> argparse.ArgumentParser:
|
|
23
|
-
parser = argparse.ArgumentParser(prog="spawn", description="Spawn CLI")
|
|
24
|
-
parser.add_argument(
|
|
25
|
-
"--version",
|
|
26
|
-
action="version",
|
|
27
|
-
version=f"spawn {__version__}",
|
|
28
|
-
)
|
|
29
|
-
sub = parser.add_subparsers(dest="command", required=True)
|
|
30
|
-
|
|
31
|
-
sub.add_parser("init", help="Initialize spawn/ in the current repository")
|
|
32
|
-
|
|
33
|
-
rules_p = sub.add_parser("rules")
|
|
34
|
-
rules_sub = rules_p.add_subparsers(dest="rules_command", required=True)
|
|
35
|
-
rules_sub.add_parser("refresh", help="Sync spawn/rules/ into navigation.yaml")
|
|
36
|
-
|
|
37
|
-
ide_p = sub.add_parser("ide")
|
|
38
|
-
ide_sub = ide_p.add_subparsers(dest="ide_command", required=True)
|
|
39
|
-
_ide_add = ide_sub.add_parser("add")
|
|
40
|
-
_ide_add.add_argument("ides", nargs="+")
|
|
41
|
-
_ide_remove = ide_sub.add_parser("remove")
|
|
42
|
-
_ide_remove.add_argument("ides", nargs="+")
|
|
43
|
-
ide_sub.add_parser("list")
|
|
44
|
-
ide_sub.add_parser("list-supported-ides")
|
|
45
|
-
|
|
46
|
-
ext_p = sub.add_parser("extension")
|
|
47
|
-
ext_sub = ext_p.add_subparsers(dest="ext_command", required=True)
|
|
48
|
-
_ext_add = ext_sub.add_parser("add")
|
|
49
|
-
_ext_add.add_argument("path")
|
|
50
|
-
_ext_add.add_argument("--branch", default=None)
|
|
51
|
-
_ext_update = ext_sub.add_parser("update")
|
|
52
|
-
_ext_update.add_argument("extension_name")
|
|
53
|
-
_ext_remove = ext_sub.add_parser("remove")
|
|
54
|
-
_ext_remove.add_argument("extension_name")
|
|
55
|
-
_ext_reinstall = ext_sub.add_parser("reinstall")
|
|
56
|
-
_ext_reinstall.add_argument("extension_name")
|
|
57
|
-
ext_sub.add_parser("list")
|
|
58
|
-
_ext_init = ext_sub.add_parser("init")
|
|
59
|
-
_ext_init.add_argument("path", nargs="?", default=".")
|
|
60
|
-
_ext_init.add_argument("--name", required=True)
|
|
61
|
-
_ext_check = ext_sub.add_parser("check")
|
|
62
|
-
_ext_check.add_argument("path", nargs="?", default=".")
|
|
63
|
-
_ext_check.add_argument("--strict", action="store_true")
|
|
64
|
-
_ext_from_rules = ext_sub.add_parser("from-rules")
|
|
65
|
-
_ext_from_rules.add_argument("source")
|
|
66
|
-
_ext_from_rules.add_argument("--name", required=True)
|
|
67
|
-
_ext_from_rules.add_argument("--branch", default=None)
|
|
68
|
-
_ext_from_rules.add_argument("--output", default=".")
|
|
69
|
-
_ext_health = ext_sub.add_parser("healthcheck")
|
|
70
|
-
_ext_health.add_argument("extension_name")
|
|
71
|
-
|
|
72
|
-
build_p = sub.add_parser("build")
|
|
73
|
-
build_sub = build_p.add_subparsers(dest="build_command", required=True)
|
|
74
|
-
_build_install = build_sub.add_parser("install")
|
|
75
|
-
_build_install.add_argument("path")
|
|
76
|
-
_build_install.add_argument("--branch", default=None)
|
|
77
|
-
_build_list = build_sub.add_parser("list")
|
|
78
|
-
_build_list.add_argument("path")
|
|
79
|
-
_build_list.add_argument("--branch", default=None)
|
|
80
|
-
|
|
81
|
-
return parser
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
def main(argv: list[str] | None = None) -> int:
|
|
85
|
-
install_spawn_warning_format()
|
|
86
|
-
parser = build_parser()
|
|
87
|
-
args = parser.parse_args(argv)
|
|
88
|
-
target_root = Path.cwd().resolve()
|
|
89
|
-
|
|
90
|
-
try:
|
|
91
|
-
return _dispatch(args, target_root)
|
|
92
|
-
except SpawnError as e:
|
|
93
|
-
print(f"Error: {e}", file=sys.stderr)
|
|
94
|
-
return 1
|
|
95
|
-
except Exception as e:
|
|
96
|
-
print(f"Unexpected error: {e}", file=sys.stderr)
|
|
97
|
-
return 2
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
def _dispatch(args: argparse.Namespace, target_root: Path) -> int:
|
|
101
|
-
cmd = args.command
|
|
102
|
-
|
|
103
|
-
if cmd == "init":
|
|
104
|
-
with spawn_lock(target_root):
|
|
105
|
-
ll.init(target_root)
|
|
106
|
-
return 0
|
|
107
|
-
|
|
108
|
-
_require_init(target_root)
|
|
109
|
-
|
|
110
|
-
with spawn_lock(target_root):
|
|
111
|
-
if cmd == "rules":
|
|
112
|
-
return _dispatch_rules(args, target_root)
|
|
113
|
-
if cmd == "ide":
|
|
114
|
-
return _dispatch_ide(args, target_root)
|
|
115
|
-
if cmd == "extension":
|
|
116
|
-
return _dispatch_extension(args, target_root)
|
|
117
|
-
if cmd == "build":
|
|
118
|
-
return _dispatch_build(args, target_root)
|
|
119
|
-
|
|
120
|
-
return 1
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
def _dispatch_rules(args: argparse.Namespace, target_root: Path) -> int:
|
|
124
|
-
sub = args.rules_command
|
|
125
|
-
if sub == "refresh":
|
|
126
|
-
hl.refresh_rules_navigation(target_root)
|
|
127
|
-
return 0
|
|
128
|
-
return 1
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
def _dispatch_ide(args: argparse.Namespace, target_root: Path) -> int:
|
|
132
|
-
sub = args.ide_command
|
|
133
|
-
|
|
134
|
-
if sub == "list-supported-ides":
|
|
135
|
-
results = detect_supported_ides(target_root)
|
|
136
|
-
_print_yaml(results)
|
|
137
|
-
return 0
|
|
138
|
-
|
|
139
|
-
if sub == "list":
|
|
140
|
-
ides = ll.list_ides(target_root)
|
|
141
|
-
for ide in ides:
|
|
142
|
-
print(ide)
|
|
143
|
-
return 0
|
|
144
|
-
|
|
145
|
-
if sub == "add":
|
|
146
|
-
for ide in args.ides:
|
|
147
|
-
hl.add_ide(target_root, ide)
|
|
148
|
-
return 0
|
|
149
|
-
|
|
150
|
-
if sub == "remove":
|
|
151
|
-
for ide in args.ides:
|
|
152
|
-
hl.remove_ide(target_root, ide)
|
|
153
|
-
return 0
|
|
154
|
-
|
|
155
|
-
return 1
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
def _dispatch_extension(args: argparse.Namespace, target_root: Path) -> int:
|
|
159
|
-
sub = args.ext_command
|
|
160
|
-
|
|
161
|
-
if sub == "list":
|
|
162
|
-
for ext in ll.list_extensions(target_root):
|
|
163
|
-
print(ext)
|
|
164
|
-
return 0
|
|
165
|
-
|
|
166
|
-
if sub == "add":
|
|
167
|
-
dl.install_extension(target_root, args.path, args.branch)
|
|
168
|
-
return 0
|
|
169
|
-
|
|
170
|
-
if sub == "update":
|
|
171
|
-
hl.update_extension(target_root, args.extension_name)
|
|
172
|
-
return 0
|
|
173
|
-
|
|
174
|
-
if sub == "remove":
|
|
175
|
-
hl.remove_extension(target_root, args.extension_name)
|
|
176
|
-
return 0
|
|
177
|
-
|
|
178
|
-
if sub == "reinstall":
|
|
179
|
-
hl.reinstall_extension(target_root, args.extension_name)
|
|
180
|
-
return 0
|
|
181
|
-
|
|
182
|
-
if sub == "init":
|
|
183
|
-
hl.extension_init(Path(args.path).resolve(), args.name)
|
|
184
|
-
return 0
|
|
185
|
-
|
|
186
|
-
if sub == "check":
|
|
187
|
-
warn_list = hl.extension_check(Path(args.path).resolve(), strict=args.strict)
|
|
188
|
-
for w in warn_list:
|
|
189
|
-
print(f"Warning: {w}")
|
|
190
|
-
return 0
|
|
191
|
-
|
|
192
|
-
if sub == "from-rules":
|
|
193
|
-
hl.extension_from_rules(
|
|
194
|
-
args.source,
|
|
195
|
-
Path(args.output).resolve(),
|
|
196
|
-
args.name,
|
|
197
|
-
args.branch,
|
|
198
|
-
)
|
|
199
|
-
return 0
|
|
200
|
-
|
|
201
|
-
if sub == "healthcheck":
|
|
202
|
-
ok = hl.extension_healthcheck(target_root, args.extension_name)
|
|
203
|
-
return 0 if ok else 1
|
|
204
|
-
|
|
205
|
-
return 1
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
def _dispatch_build(args: argparse.Namespace, target_root: Path) -> int:
|
|
209
|
-
sub = args.build_command
|
|
210
|
-
|
|
211
|
-
if sub == "list":
|
|
212
|
-
exts = dl.list_build_extensions(args.path, args.branch)
|
|
213
|
-
_print_yaml(exts)
|
|
214
|
-
return 0
|
|
215
|
-
|
|
216
|
-
if sub == "install":
|
|
217
|
-
dl.install_build(target_root, args.path, args.branch)
|
|
218
|
-
return 0
|
|
219
|
-
|
|
220
|
-
return 1
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
def _print_yaml(data: object) -> None:
|
|
224
|
-
"""Serialize data to YAML. DetectResult dataclass values use utility.md output shape."""
|
|
225
|
-
from ruamel.yaml import YAML
|
|
226
|
-
|
|
227
|
-
from spawn_cli.io.yaml_io import configure_yaml_dump
|
|
228
|
-
|
|
229
|
-
def _to_serializable(obj: object) -> object:
|
|
230
|
-
if isinstance(obj, DetectResult):
|
|
231
|
-
return {
|
|
232
|
-
"used-in-repo": obj.used_in_repo,
|
|
233
|
-
"capabilities": obj.capabilities.to_dict(),
|
|
234
|
-
}
|
|
235
|
-
if isinstance(obj, dict):
|
|
236
|
-
return {k: _to_serializable(v) for k, v in obj.items()}
|
|
237
|
-
if isinstance(obj, list):
|
|
238
|
-
return [_to_serializable(i) for i in obj]
|
|
239
|
-
return obj
|
|
240
|
-
|
|
241
|
-
yaml = YAML()
|
|
242
|
-
configure_yaml_dump(yaml)
|
|
243
|
-
yaml.dump(_to_serializable(data), sys.stdout)
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|