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.
Files changed (50) hide show
  1. {spawn_cli-0.1.4/src/spawn_cli.egg-info → spawn_cli-0.1.6}/PKG-INFO +3 -3
  2. {spawn_cli-0.1.4 → spawn_cli-0.1.6}/README.md +2 -2
  3. {spawn_cli-0.1.4 → spawn_cli-0.1.6}/pyproject.toml +1 -1
  4. {spawn_cli-0.1.4 → spawn_cli-0.1.6}/src/spawn_cli/core/high_level.py +103 -17
  5. spawn_cli-0.1.6/src/spawn_cli/resources/default_core_config.yaml +5 -0
  6. {spawn_cli-0.1.4 → spawn_cli-0.1.6/src/spawn_cli.egg-info}/PKG-INFO +3 -3
  7. spawn_cli-0.1.4/src/spawn_cli/resources/default_core_config.yaml +0 -5
  8. {spawn_cli-0.1.4 → spawn_cli-0.1.6}/LICENSE +0 -0
  9. {spawn_cli-0.1.4 → spawn_cli-0.1.6}/setup.cfg +0 -0
  10. {spawn_cli-0.1.4 → spawn_cli-0.1.6}/src/spawn_cli/__init__.py +0 -0
  11. {spawn_cli-0.1.4 → spawn_cli-0.1.6}/src/spawn_cli/cli.py +0 -0
  12. {spawn_cli-0.1.4 → spawn_cli-0.1.6}/src/spawn_cli/core/__init__.py +0 -0
  13. {spawn_cli-0.1.4 → spawn_cli-0.1.6}/src/spawn_cli/core/download.py +0 -0
  14. {spawn_cli-0.1.4 → spawn_cli-0.1.6}/src/spawn_cli/core/errors.py +0 -0
  15. {spawn_cli-0.1.4 → spawn_cli-0.1.6}/src/spawn_cli/core/low_level.py +0 -0
  16. {spawn_cli-0.1.4 → spawn_cli-0.1.6}/src/spawn_cli/core/scripts.py +0 -0
  17. {spawn_cli-0.1.4 → spawn_cli-0.1.6}/src/spawn_cli/errors.py +0 -0
  18. {spawn_cli-0.1.4 → spawn_cli-0.1.6}/src/spawn_cli/ide/__init__.py +0 -0
  19. {spawn_cli-0.1.4 → spawn_cli-0.1.6}/src/spawn_cli/ide/_helpers.py +0 -0
  20. {spawn_cli-0.1.4 → spawn_cli-0.1.6}/src/spawn_cli/ide/_stub.py +0 -0
  21. {spawn_cli-0.1.4 → spawn_cli-0.1.6}/src/spawn_cli/ide/_vacancy.py +0 -0
  22. {spawn_cli-0.1.4 → spawn_cli-0.1.6}/src/spawn_cli/ide/claude_code.py +0 -0
  23. {spawn_cli-0.1.4 → spawn_cli-0.1.6}/src/spawn_cli/ide/codex.py +0 -0
  24. {spawn_cli-0.1.4 → spawn_cli-0.1.6}/src/spawn_cli/ide/cursor.py +0 -0
  25. {spawn_cli-0.1.4 → spawn_cli-0.1.6}/src/spawn_cli/ide/gemini_cli.py +0 -0
  26. {spawn_cli-0.1.4 → spawn_cli-0.1.6}/src/spawn_cli/ide/github_copilot.py +0 -0
  27. {spawn_cli-0.1.4 → spawn_cli-0.1.6}/src/spawn_cli/ide/registry.py +0 -0
  28. {spawn_cli-0.1.4 → spawn_cli-0.1.6}/src/spawn_cli/ide/windsurf.py +0 -0
  29. {spawn_cli-0.1.4 → spawn_cli-0.1.6}/src/spawn_cli/io/__init__.py +0 -0
  30. {spawn_cli-0.1.4 → spawn_cli-0.1.6}/src/spawn_cli/io/json_io.py +0 -0
  31. {spawn_cli-0.1.4 → spawn_cli-0.1.6}/src/spawn_cli/io/lock.py +0 -0
  32. {spawn_cli-0.1.4 → spawn_cli-0.1.6}/src/spawn_cli/io/paths.py +0 -0
  33. {spawn_cli-0.1.4 → spawn_cli-0.1.6}/src/spawn_cli/io/text_io.py +0 -0
  34. {spawn_cli-0.1.4 → spawn_cli-0.1.6}/src/spawn_cli/io/toml_io.py +0 -0
  35. {spawn_cli-0.1.4 → spawn_cli-0.1.6}/src/spawn_cli/io/yaml_io.py +0 -0
  36. {spawn_cli-0.1.4 → spawn_cli-0.1.6}/src/spawn_cli/models/__init__.py +0 -0
  37. {spawn_cli-0.1.4 → spawn_cli-0.1.6}/src/spawn_cli/models/config.py +0 -0
  38. {spawn_cli-0.1.4 → spawn_cli-0.1.6}/src/spawn_cli/models/mcp.py +0 -0
  39. {spawn_cli-0.1.4 → spawn_cli-0.1.6}/src/spawn_cli/models/metadata.py +0 -0
  40. {spawn_cli-0.1.4 → spawn_cli-0.1.6}/src/spawn_cli/models/navigation.py +0 -0
  41. {spawn_cli-0.1.4 → spawn_cli-0.1.6}/src/spawn_cli/models/skill.py +0 -0
  42. {spawn_cli-0.1.4 → spawn_cli-0.1.6}/src/spawn_cli/resources/__init__.py +0 -0
  43. {spawn_cli-0.1.4 → spawn_cli-0.1.6}/src/spawn_cli/warnings_display.py +0 -0
  44. {spawn_cli-0.1.4 → spawn_cli-0.1.6}/src/spawn_cli.egg-info/SOURCES.txt +0 -0
  45. {spawn_cli-0.1.4 → spawn_cli-0.1.6}/src/spawn_cli.egg-info/dependency_links.txt +0 -0
  46. {spawn_cli-0.1.4 → spawn_cli-0.1.6}/src/spawn_cli.egg-info/entry_points.txt +0 -0
  47. {spawn_cli-0.1.4 → spawn_cli-0.1.6}/src/spawn_cli.egg-info/requires.txt +0 -0
  48. {spawn_cli-0.1.4 → spawn_cli-0.1.6}/src/spawn_cli.egg-info/top_level.txt +0 -0
  49. {spawn_cli-0.1.4 → spawn_cli-0.1.6}/tests/test_cli.py +0 -0
  50. {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.4
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
- `[spawn-ext-spectask](https://github.com/noant/spawn-ext-spectask)` is a concrete extension you can install with `spawn extension add` as shown above.
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:** `[spec/design/user-guide.md](spec/design/user-guide.md)` and `[spec/design/utility.md](spec/design/utility.md)`.
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
- `[spawn-ext-spectask](https://github.com/noant/spawn-ext-spectask)` is a concrete extension you can install with `spawn extension add` as shown above.
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:** `[spec/design/user-guide.md](spec/design/user-guide.md)` and `[spec/design/utility.md](spec/design/utility.md)`.
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
 
@@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"
4
4
 
5
5
  [project]
6
6
  name = "spawn-cli"
7
- version = "0.1.4"
7
+ version = "0.1.6"
8
8
  description = "A minimal installable Python CLI package."
9
9
  readme = "README.md"
10
10
  requires-python = ">=3.10"
@@ -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(target_root: Path, ide: str, extension: str) -> None:
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
- refresh_skills(target_root, ide, extension)
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
- refresh_skills(target_root, ide, extension)
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
- caps = dr.capabilities
401
- if caps.skills == "unsupported":
402
- warnings.warn(
403
- f"IDE {ide!r} does not support skills; skills were skipped",
404
- SpawnWarning,
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
- refresh_skills(target_root, ide, ext)
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",
@@ -0,0 +1,5 @@
1
+ version: "0.1.0"
2
+ agent-ignore:
3
+ - spawn/.core/**
4
+ - spawn/.metadata/**
5
+ - spawn/.extend/**
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: spawn-cli
3
- Version: 0.1.4
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
- `[spawn-ext-spectask](https://github.com/noant/spawn-ext-spectask)` is a concrete extension you can install with `spawn extension add` as shown above.
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:** `[spec/design/user-guide.md](spec/design/user-guide.md)` and `[spec/design/utility.md](spec/design/utility.md)`.
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
 
@@ -1,5 +0,0 @@
1
- version: "0.1.0"
2
- agent-ignore:
3
- - spawn/**
4
- - "!spawn/navigation.yaml"
5
- - "!spawn/rules/**"
File without changes
File without changes
File without changes