spawn-cli 0.1.4__tar.gz → 0.1.6__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.4/src/spawn_cli.egg-info → spawn_cli-0.1.6}/PKG-INFO +3 -3
- {spawn_cli-0.1.4 → spawn_cli-0.1.6}/README.md +2 -2
- {spawn_cli-0.1.4 → spawn_cli-0.1.6}/pyproject.toml +1 -1
- {spawn_cli-0.1.4 → spawn_cli-0.1.6}/src/spawn_cli/core/high_level.py +103 -17
- spawn_cli-0.1.6/src/spawn_cli/resources/default_core_config.yaml +5 -0
- {spawn_cli-0.1.4 → spawn_cli-0.1.6/src/spawn_cli.egg-info}/PKG-INFO +3 -3
- spawn_cli-0.1.4/src/spawn_cli/resources/default_core_config.yaml +0 -5
- {spawn_cli-0.1.4 → spawn_cli-0.1.6}/LICENSE +0 -0
- {spawn_cli-0.1.4 → spawn_cli-0.1.6}/setup.cfg +0 -0
- {spawn_cli-0.1.4 → spawn_cli-0.1.6}/src/spawn_cli/__init__.py +0 -0
- {spawn_cli-0.1.4 → spawn_cli-0.1.6}/src/spawn_cli/cli.py +0 -0
- {spawn_cli-0.1.4 → spawn_cli-0.1.6}/src/spawn_cli/core/__init__.py +0 -0
- {spawn_cli-0.1.4 → spawn_cli-0.1.6}/src/spawn_cli/core/download.py +0 -0
- {spawn_cli-0.1.4 → spawn_cli-0.1.6}/src/spawn_cli/core/errors.py +0 -0
- {spawn_cli-0.1.4 → spawn_cli-0.1.6}/src/spawn_cli/core/low_level.py +0 -0
- {spawn_cli-0.1.4 → spawn_cli-0.1.6}/src/spawn_cli/core/scripts.py +0 -0
- {spawn_cli-0.1.4 → spawn_cli-0.1.6}/src/spawn_cli/errors.py +0 -0
- {spawn_cli-0.1.4 → spawn_cli-0.1.6}/src/spawn_cli/ide/__init__.py +0 -0
- {spawn_cli-0.1.4 → spawn_cli-0.1.6}/src/spawn_cli/ide/_helpers.py +0 -0
- {spawn_cli-0.1.4 → spawn_cli-0.1.6}/src/spawn_cli/ide/_stub.py +0 -0
- {spawn_cli-0.1.4 → spawn_cli-0.1.6}/src/spawn_cli/ide/_vacancy.py +0 -0
- {spawn_cli-0.1.4 → spawn_cli-0.1.6}/src/spawn_cli/ide/claude_code.py +0 -0
- {spawn_cli-0.1.4 → spawn_cli-0.1.6}/src/spawn_cli/ide/codex.py +0 -0
- {spawn_cli-0.1.4 → spawn_cli-0.1.6}/src/spawn_cli/ide/cursor.py +0 -0
- {spawn_cli-0.1.4 → spawn_cli-0.1.6}/src/spawn_cli/ide/gemini_cli.py +0 -0
- {spawn_cli-0.1.4 → spawn_cli-0.1.6}/src/spawn_cli/ide/github_copilot.py +0 -0
- {spawn_cli-0.1.4 → spawn_cli-0.1.6}/src/spawn_cli/ide/registry.py +0 -0
- {spawn_cli-0.1.4 → spawn_cli-0.1.6}/src/spawn_cli/ide/windsurf.py +0 -0
- {spawn_cli-0.1.4 → spawn_cli-0.1.6}/src/spawn_cli/io/__init__.py +0 -0
- {spawn_cli-0.1.4 → spawn_cli-0.1.6}/src/spawn_cli/io/json_io.py +0 -0
- {spawn_cli-0.1.4 → spawn_cli-0.1.6}/src/spawn_cli/io/lock.py +0 -0
- {spawn_cli-0.1.4 → spawn_cli-0.1.6}/src/spawn_cli/io/paths.py +0 -0
- {spawn_cli-0.1.4 → spawn_cli-0.1.6}/src/spawn_cli/io/text_io.py +0 -0
- {spawn_cli-0.1.4 → spawn_cli-0.1.6}/src/spawn_cli/io/toml_io.py +0 -0
- {spawn_cli-0.1.4 → spawn_cli-0.1.6}/src/spawn_cli/io/yaml_io.py +0 -0
- {spawn_cli-0.1.4 → spawn_cli-0.1.6}/src/spawn_cli/models/__init__.py +0 -0
- {spawn_cli-0.1.4 → spawn_cli-0.1.6}/src/spawn_cli/models/config.py +0 -0
- {spawn_cli-0.1.4 → spawn_cli-0.1.6}/src/spawn_cli/models/mcp.py +0 -0
- {spawn_cli-0.1.4 → spawn_cli-0.1.6}/src/spawn_cli/models/metadata.py +0 -0
- {spawn_cli-0.1.4 → spawn_cli-0.1.6}/src/spawn_cli/models/navigation.py +0 -0
- {spawn_cli-0.1.4 → spawn_cli-0.1.6}/src/spawn_cli/models/skill.py +0 -0
- {spawn_cli-0.1.4 → spawn_cli-0.1.6}/src/spawn_cli/resources/__init__.py +0 -0
- {spawn_cli-0.1.4 → spawn_cli-0.1.6}/src/spawn_cli/warnings_display.py +0 -0
- {spawn_cli-0.1.4 → spawn_cli-0.1.6}/src/spawn_cli.egg-info/SOURCES.txt +0 -0
- {spawn_cli-0.1.4 → spawn_cli-0.1.6}/src/spawn_cli.egg-info/dependency_links.txt +0 -0
- {spawn_cli-0.1.4 → spawn_cli-0.1.6}/src/spawn_cli.egg-info/entry_points.txt +0 -0
- {spawn_cli-0.1.4 → spawn_cli-0.1.6}/src/spawn_cli.egg-info/requires.txt +0 -0
- {spawn_cli-0.1.4 → spawn_cli-0.1.6}/src/spawn_cli.egg-info/top_level.txt +0 -0
- {spawn_cli-0.1.4 → spawn_cli-0.1.6}/tests/test_cli.py +0 -0
- {spawn_cli-0.1.4 → spawn_cli-0.1.6}/tests/test_warnings_display.py +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: spawn-cli
|
|
3
|
-
Version: 0.1.
|
|
3
|
+
Version: 0.1.6
|
|
4
4
|
Summary: A minimal installable Python CLI package.
|
|
5
5
|
License: MIT
|
|
6
6
|
Classifier: License :: OSI Approved :: MIT License
|
|
@@ -155,12 +155,12 @@ The **`--branch`** flag applies when the **build source** is Git (clone revision
|
|
|
155
155
|
|
|
156
156
|
### Example extension
|
|
157
157
|
|
|
158
|
-
|
|
158
|
+
[spawn-ext-spectask](https://github.com/noant/spawn-ext-spectask) is a concrete extension you can install with `spawn extension add` as shown above.
|
|
159
159
|
|
|
160
160
|
---
|
|
161
161
|
|
|
162
162
|
- **Python:** 3.10+ (see `pyproject.toml`).
|
|
163
|
-
- **Deep dives:**
|
|
163
|
+
- **Deep dives:** [spec/design/user-guide.md](spec/design/user-guide.md) and [spec/design/utility.md](spec/design/utility.md).
|
|
164
164
|
|
|
165
165
|
## How to create an extension
|
|
166
166
|
|
|
@@ -138,12 +138,12 @@ The **`--branch`** flag applies when the **build source** is Git (clone revision
|
|
|
138
138
|
|
|
139
139
|
### Example extension
|
|
140
140
|
|
|
141
|
-
|
|
141
|
+
[spawn-ext-spectask](https://github.com/noant/spawn-ext-spectask) is a concrete extension you can install with `spawn extension add` as shown above.
|
|
142
142
|
|
|
143
143
|
---
|
|
144
144
|
|
|
145
145
|
- **Python:** 3.10+ (see `pyproject.toml`).
|
|
146
|
-
- **Deep dives:**
|
|
146
|
+
- **Deep dives:** [spec/design/user-guide.md](spec/design/user-guide.md) and [spec/design/utility.md](spec/design/utility.md).
|
|
147
147
|
|
|
148
148
|
## How to create an extension
|
|
149
149
|
|
|
@@ -9,7 +9,7 @@ from spawn_cli.core import download as dl
|
|
|
9
9
|
from spawn_cli.core import low_level as ll
|
|
10
10
|
from spawn_cli.core import scripts
|
|
11
11
|
from spawn_cli.core.errors import SpawnError, SpawnWarning
|
|
12
|
-
from spawn_cli.ide.registry import get as ide_get
|
|
12
|
+
from spawn_cli.ide.registry import IdeCapabilities, get as ide_get
|
|
13
13
|
from spawn_cli.io.json_io import load_json
|
|
14
14
|
from spawn_cli.io.paths import ensure_dir, safe_path
|
|
15
15
|
from spawn_cli.io.yaml_io import load_yaml, save_yaml
|
|
@@ -21,6 +21,37 @@ Read every file listed under `read-required`.
|
|
|
21
21
|
Inspect `read-contextual` descriptions and read only files relevant to the current task.
|
|
22
22
|
"""
|
|
23
23
|
|
|
24
|
+
MCP_MERGED_NOTICE = (
|
|
25
|
+
"MCP was merged for this workspace; you may need to press Enable in your IDE MCP UI."
|
|
26
|
+
)
|
|
27
|
+
|
|
28
|
+
|
|
29
|
+
def _any_extension_has_skill_files(target_root: Path) -> bool:
|
|
30
|
+
return any(ll.list_skills(target_root, ext) for ext in ll.list_extensions(target_root))
|
|
31
|
+
|
|
32
|
+
|
|
33
|
+
def _any_extension_has_mcp_servers(target_root: Path) -> bool:
|
|
34
|
+
return any(ll.list_mcp(target_root, ext).servers for ext in ll.list_extensions(target_root))
|
|
35
|
+
|
|
36
|
+
|
|
37
|
+
def _warn_capability_gaps(
|
|
38
|
+
ide_key: str,
|
|
39
|
+
caps: IdeCapabilities,
|
|
40
|
+
*,
|
|
41
|
+
needs_skill_render: bool,
|
|
42
|
+
needs_mcp_merge: bool,
|
|
43
|
+
) -> None:
|
|
44
|
+
if needs_skill_render and caps.skills == "unsupported":
|
|
45
|
+
warnings.warn(
|
|
46
|
+
f"IDE {ide_key!r} does not support skills; skills were skipped",
|
|
47
|
+
SpawnWarning,
|
|
48
|
+
)
|
|
49
|
+
if needs_mcp_merge and caps.mcp in ("unsupported", "external"):
|
|
50
|
+
warnings.warn(
|
|
51
|
+
f"IDE {ide_key!r} has limited MCP support ({caps.mcp})",
|
|
52
|
+
SpawnWarning,
|
|
53
|
+
)
|
|
54
|
+
|
|
24
55
|
|
|
25
56
|
def _require_init(target_root: Path) -> None:
|
|
26
57
|
if not (target_root / "spawn").is_dir():
|
|
@@ -85,6 +116,16 @@ def refresh_skills(target_root: Path, ide: str, extension: str) -> None:
|
|
|
85
116
|
ll.save_skills_rendered(target_root, ide, extension, rendered)
|
|
86
117
|
|
|
87
118
|
|
|
119
|
+
def _refresh_skills_all_extensions_for_ide(target_root: Path, ide: str) -> None:
|
|
120
|
+
"""Re-render every extension's skills on *ide*.
|
|
121
|
+
|
|
122
|
+
Skill metadata merges global reads from all installed extensions; peers must
|
|
123
|
+
be rebuilt when any extension's global read set changes.
|
|
124
|
+
"""
|
|
125
|
+
for ext in ll.list_extensions(target_root):
|
|
126
|
+
refresh_skills(target_root, ide, ext)
|
|
127
|
+
|
|
128
|
+
|
|
88
129
|
def remove_skills(target_root: Path, ide: str, extension: str) -> None:
|
|
89
130
|
_require_init(target_root)
|
|
90
131
|
prior = ll.get_rendered_skills(target_root, ide, extension)
|
|
@@ -92,7 +133,20 @@ def remove_skills(target_root: Path, ide: str, extension: str) -> None:
|
|
|
92
133
|
ll.save_skills_rendered(target_root, ide, extension, [])
|
|
93
134
|
|
|
94
135
|
|
|
95
|
-
def refresh_mcp(
|
|
136
|
+
def refresh_mcp(
|
|
137
|
+
target_root: Path,
|
|
138
|
+
ide: str,
|
|
139
|
+
extension: str,
|
|
140
|
+
*,
|
|
141
|
+
emit_mcp_merged_notice: bool = True,
|
|
142
|
+
) -> list[str]:
|
|
143
|
+
"""Merge extension MCP into the IDE project config.
|
|
144
|
+
|
|
145
|
+
Persisted rendered server names from ``adapter.add_mcp`` are returned.
|
|
146
|
+
When ``emit_mcp_merged_notice`` is true and that list is non-empty, prints
|
|
147
|
+
``MCP_MERGED_NOTICE`` once to stdout (callers that batch refreshes, e.g.
|
|
148
|
+
``add_ide``, should pass ``emit_mcp_merged_notice=False`` and print once).
|
|
149
|
+
"""
|
|
96
150
|
_require_init(target_root)
|
|
97
151
|
prior = ll.get_rendered_mcp(target_root, ide, extension)
|
|
98
152
|
ll.validate_rendered_identity(target_root)
|
|
@@ -101,6 +155,9 @@ def refresh_mcp(target_root: Path, ide: str, extension: str) -> None:
|
|
|
101
155
|
adapter.remove_mcp(target_root, prior)
|
|
102
156
|
new_names = adapter.add_mcp(target_root, nm)
|
|
103
157
|
ll.save_mcp_rendered(target_root, ide, extension, new_names)
|
|
158
|
+
if new_names and emit_mcp_merged_notice:
|
|
159
|
+
print(MCP_MERGED_NOTICE)
|
|
160
|
+
return new_names
|
|
104
161
|
|
|
105
162
|
|
|
106
163
|
def remove_mcp(target_root: Path, ide: str, extension: str) -> None:
|
|
@@ -116,8 +173,24 @@ def refresh_entry_point(target_root: Path, ide: str) -> None:
|
|
|
116
173
|
|
|
117
174
|
|
|
118
175
|
def refresh_extension_for_ide(target_root: Path, ide: str, extension: str) -> None:
|
|
176
|
+
"""Merge *extension* MCP on *ide* and re-render skills for every extension.
|
|
177
|
+
|
|
178
|
+
Mandatory reads in rendered skills are merged from all extensions'
|
|
179
|
+
``globalRead`` metadata, so updating one extension can require re-rendering
|
|
180
|
+
skills owned by other extensions even when only this extension's MCP changed.
|
|
181
|
+
"""
|
|
182
|
+
needs_skill = _any_extension_has_skill_files(target_root)
|
|
183
|
+
needs_mcp = bool(ll.list_mcp(target_root, extension).servers)
|
|
184
|
+
adapter = ide_get(ide)
|
|
185
|
+
dr = adapter.detect(target_root)
|
|
186
|
+
_warn_capability_gaps(
|
|
187
|
+
ide,
|
|
188
|
+
dr.capabilities,
|
|
189
|
+
needs_skill_render=needs_skill,
|
|
190
|
+
needs_mcp_merge=needs_mcp,
|
|
191
|
+
)
|
|
119
192
|
refresh_mcp(target_root, ide, extension)
|
|
120
|
-
|
|
193
|
+
_refresh_skills_all_extensions_for_ide(target_root, ide)
|
|
121
194
|
refresh_agent_ignore(target_root, ide)
|
|
122
195
|
|
|
123
196
|
|
|
@@ -150,9 +223,19 @@ def refresh_rules_navigation(target_root: Path) -> None:
|
|
|
150
223
|
|
|
151
224
|
|
|
152
225
|
def _refresh_extension_core(target_root: Path, extension: str) -> None:
|
|
226
|
+
needs_skill = _any_extension_has_skill_files(target_root)
|
|
227
|
+
needs_mcp = bool(ll.list_mcp(target_root, extension).servers)
|
|
228
|
+
for ide in ll.list_ides(target_root):
|
|
229
|
+
adapter = ide_get(ide)
|
|
230
|
+
_warn_capability_gaps(
|
|
231
|
+
ide,
|
|
232
|
+
adapter.detect(target_root).capabilities,
|
|
233
|
+
needs_skill_render=needs_skill,
|
|
234
|
+
needs_mcp_merge=needs_mcp,
|
|
235
|
+
)
|
|
153
236
|
for ide in ll.list_ides(target_root):
|
|
154
237
|
refresh_mcp(target_root, ide, extension)
|
|
155
|
-
|
|
238
|
+
_refresh_skills_all_extensions_for_ide(target_root, ide)
|
|
156
239
|
for ide in ll.list_ides(target_root):
|
|
157
240
|
refresh_agent_ignore(target_root, ide)
|
|
158
241
|
refresh_gitignore(target_root)
|
|
@@ -194,6 +277,8 @@ def remove_extension(target_root: Path, extension: str) -> None:
|
|
|
194
277
|
for ide in ll.list_ides(target_root):
|
|
195
278
|
refresh_agent_ignore(target_root, ide)
|
|
196
279
|
refresh_entry_point(target_root, ide)
|
|
280
|
+
for ide in ll.list_ides(target_root):
|
|
281
|
+
_refresh_skills_all_extensions_for_ide(target_root, ide)
|
|
197
282
|
scripts.run_after_uninstall_from_snapshot(target_root, extension, after_spec)
|
|
198
283
|
|
|
199
284
|
|
|
@@ -397,21 +482,21 @@ def add_ide(target_root: Path, ide: str) -> None:
|
|
|
397
482
|
ll.add_ide_to_list(target_root, ide)
|
|
398
483
|
adapter = ide_get(ide)
|
|
399
484
|
dr = adapter.detect(target_root)
|
|
400
|
-
|
|
401
|
-
|
|
402
|
-
|
|
403
|
-
|
|
404
|
-
|
|
405
|
-
|
|
406
|
-
if caps.mcp in ("unsupported", "external"):
|
|
407
|
-
warnings.warn(
|
|
408
|
-
f"IDE {ide!r} has limited MCP support ({caps.mcp})",
|
|
409
|
-
SpawnWarning,
|
|
410
|
-
)
|
|
485
|
+
_warn_capability_gaps(
|
|
486
|
+
ide,
|
|
487
|
+
dr.capabilities,
|
|
488
|
+
needs_skill_render=_any_extension_has_skill_files(target_root),
|
|
489
|
+
needs_mcp_merge=_any_extension_has_mcp_servers(target_root),
|
|
490
|
+
)
|
|
411
491
|
refresh_entry_point(target_root, ide)
|
|
492
|
+
merged_any = False
|
|
412
493
|
for ext in ll.list_extensions(target_root):
|
|
413
|
-
refresh_mcp(target_root, ide, ext)
|
|
414
|
-
|
|
494
|
+
new_names = refresh_mcp(target_root, ide, ext, emit_mcp_merged_notice=False)
|
|
495
|
+
if new_names:
|
|
496
|
+
merged_any = True
|
|
497
|
+
_refresh_skills_all_extensions_for_ide(target_root, ide)
|
|
498
|
+
if merged_any:
|
|
499
|
+
print(MCP_MERGED_NOTICE)
|
|
415
500
|
refresh_agent_ignore(target_root, ide)
|
|
416
501
|
|
|
417
502
|
|
|
@@ -435,6 +520,7 @@ def install_extension(target_root: Path, path: str, branch: str | None = None) -
|
|
|
435
520
|
|
|
436
521
|
|
|
437
522
|
__all__ = [
|
|
523
|
+
"MCP_MERGED_NOTICE",
|
|
438
524
|
"SPAWN_ENTRY_POINT_PROMPT",
|
|
439
525
|
"_refresh_extension_core",
|
|
440
526
|
"add_ide",
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: spawn-cli
|
|
3
|
-
Version: 0.1.
|
|
3
|
+
Version: 0.1.6
|
|
4
4
|
Summary: A minimal installable Python CLI package.
|
|
5
5
|
License: MIT
|
|
6
6
|
Classifier: License :: OSI Approved :: MIT License
|
|
@@ -155,12 +155,12 @@ The **`--branch`** flag applies when the **build source** is Git (clone revision
|
|
|
155
155
|
|
|
156
156
|
### Example extension
|
|
157
157
|
|
|
158
|
-
|
|
158
|
+
[spawn-ext-spectask](https://github.com/noant/spawn-ext-spectask) is a concrete extension you can install with `spawn extension add` as shown above.
|
|
159
159
|
|
|
160
160
|
---
|
|
161
161
|
|
|
162
162
|
- **Python:** 3.10+ (see `pyproject.toml`).
|
|
163
|
-
- **Deep dives:**
|
|
163
|
+
- **Deep dives:** [spec/design/user-guide.md](spec/design/user-guide.md) and [spec/design/utility.md](spec/design/utility.md).
|
|
164
164
|
|
|
165
165
|
## How to create an extension
|
|
166
166
|
|
|
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
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|