spawn-cli 0.1.6__tar.gz → 0.1.7__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.6/src/spawn_cli.egg-info → spawn_cli-0.1.7}/PKG-INFO +1 -1
- {spawn_cli-0.1.6 → spawn_cli-0.1.7}/pyproject.toml +1 -1
- {spawn_cli-0.1.6 → spawn_cli-0.1.7}/src/spawn_cli/cli.py +12 -0
- {spawn_cli-0.1.6 → spawn_cli-0.1.7}/src/spawn_cli/core/high_level.py +31 -0
- {spawn_cli-0.1.6 → spawn_cli-0.1.7}/src/spawn_cli/core/low_level.py +43 -3
- {spawn_cli-0.1.6 → spawn_cli-0.1.7/src/spawn_cli.egg-info}/PKG-INFO +1 -1
- {spawn_cli-0.1.6 → spawn_cli-0.1.7}/tests/test_cli.py +22 -0
- {spawn_cli-0.1.6 → spawn_cli-0.1.7}/LICENSE +0 -0
- {spawn_cli-0.1.6 → spawn_cli-0.1.7}/README.md +0 -0
- {spawn_cli-0.1.6 → spawn_cli-0.1.7}/setup.cfg +0 -0
- {spawn_cli-0.1.6 → spawn_cli-0.1.7}/src/spawn_cli/__init__.py +0 -0
- {spawn_cli-0.1.6 → spawn_cli-0.1.7}/src/spawn_cli/core/__init__.py +0 -0
- {spawn_cli-0.1.6 → spawn_cli-0.1.7}/src/spawn_cli/core/download.py +0 -0
- {spawn_cli-0.1.6 → spawn_cli-0.1.7}/src/spawn_cli/core/errors.py +0 -0
- {spawn_cli-0.1.6 → spawn_cli-0.1.7}/src/spawn_cli/core/scripts.py +0 -0
- {spawn_cli-0.1.6 → spawn_cli-0.1.7}/src/spawn_cli/errors.py +0 -0
- {spawn_cli-0.1.6 → spawn_cli-0.1.7}/src/spawn_cli/ide/__init__.py +0 -0
- {spawn_cli-0.1.6 → spawn_cli-0.1.7}/src/spawn_cli/ide/_helpers.py +0 -0
- {spawn_cli-0.1.6 → spawn_cli-0.1.7}/src/spawn_cli/ide/_stub.py +0 -0
- {spawn_cli-0.1.6 → spawn_cli-0.1.7}/src/spawn_cli/ide/_vacancy.py +0 -0
- {spawn_cli-0.1.6 → spawn_cli-0.1.7}/src/spawn_cli/ide/claude_code.py +0 -0
- {spawn_cli-0.1.6 → spawn_cli-0.1.7}/src/spawn_cli/ide/codex.py +0 -0
- {spawn_cli-0.1.6 → spawn_cli-0.1.7}/src/spawn_cli/ide/cursor.py +0 -0
- {spawn_cli-0.1.6 → spawn_cli-0.1.7}/src/spawn_cli/ide/gemini_cli.py +0 -0
- {spawn_cli-0.1.6 → spawn_cli-0.1.7}/src/spawn_cli/ide/github_copilot.py +0 -0
- {spawn_cli-0.1.6 → spawn_cli-0.1.7}/src/spawn_cli/ide/registry.py +0 -0
- {spawn_cli-0.1.6 → spawn_cli-0.1.7}/src/spawn_cli/ide/windsurf.py +0 -0
- {spawn_cli-0.1.6 → spawn_cli-0.1.7}/src/spawn_cli/io/__init__.py +0 -0
- {spawn_cli-0.1.6 → spawn_cli-0.1.7}/src/spawn_cli/io/json_io.py +0 -0
- {spawn_cli-0.1.6 → spawn_cli-0.1.7}/src/spawn_cli/io/lock.py +0 -0
- {spawn_cli-0.1.6 → spawn_cli-0.1.7}/src/spawn_cli/io/paths.py +0 -0
- {spawn_cli-0.1.6 → spawn_cli-0.1.7}/src/spawn_cli/io/text_io.py +0 -0
- {spawn_cli-0.1.6 → spawn_cli-0.1.7}/src/spawn_cli/io/toml_io.py +0 -0
- {spawn_cli-0.1.6 → spawn_cli-0.1.7}/src/spawn_cli/io/yaml_io.py +0 -0
- {spawn_cli-0.1.6 → spawn_cli-0.1.7}/src/spawn_cli/models/__init__.py +0 -0
- {spawn_cli-0.1.6 → spawn_cli-0.1.7}/src/spawn_cli/models/config.py +0 -0
- {spawn_cli-0.1.6 → spawn_cli-0.1.7}/src/spawn_cli/models/mcp.py +0 -0
- {spawn_cli-0.1.6 → spawn_cli-0.1.7}/src/spawn_cli/models/metadata.py +0 -0
- {spawn_cli-0.1.6 → spawn_cli-0.1.7}/src/spawn_cli/models/navigation.py +0 -0
- {spawn_cli-0.1.6 → spawn_cli-0.1.7}/src/spawn_cli/models/skill.py +0 -0
- {spawn_cli-0.1.6 → spawn_cli-0.1.7}/src/spawn_cli/resources/__init__.py +0 -0
- {spawn_cli-0.1.6 → spawn_cli-0.1.7}/src/spawn_cli/resources/default_core_config.yaml +0 -0
- {spawn_cli-0.1.6 → spawn_cli-0.1.7}/src/spawn_cli/warnings_display.py +0 -0
- {spawn_cli-0.1.6 → spawn_cli-0.1.7}/src/spawn_cli.egg-info/SOURCES.txt +0 -0
- {spawn_cli-0.1.6 → spawn_cli-0.1.7}/src/spawn_cli.egg-info/dependency_links.txt +0 -0
- {spawn_cli-0.1.6 → spawn_cli-0.1.7}/src/spawn_cli.egg-info/entry_points.txt +0 -0
- {spawn_cli-0.1.6 → spawn_cli-0.1.7}/src/spawn_cli.egg-info/requires.txt +0 -0
- {spawn_cli-0.1.6 → spawn_cli-0.1.7}/src/spawn_cli.egg-info/top_level.txt +0 -0
- {spawn_cli-0.1.6 → spawn_cli-0.1.7}/tests/test_warnings_display.py +0 -0
|
@@ -40,6 +40,15 @@ def build_parser() -> argparse.ArgumentParser:
|
|
|
40
40
|
),
|
|
41
41
|
)
|
|
42
42
|
|
|
43
|
+
sub.add_parser(
|
|
44
|
+
"refresh",
|
|
45
|
+
help="Sync core config from defaults and rebuild IDE renders",
|
|
46
|
+
description=(
|
|
47
|
+
"Merge bundled defaults into spawn/.core/config.yaml, then refresh skills, MCP, "
|
|
48
|
+
"agent ignore, navigation, gitignore metadata, and IDE entry points."
|
|
49
|
+
),
|
|
50
|
+
)
|
|
51
|
+
|
|
43
52
|
rules_p = sub.add_parser(
|
|
44
53
|
"rules",
|
|
45
54
|
help="Rebuild navigation mandatory reads from spawn/rules/",
|
|
@@ -321,6 +330,9 @@ def _dispatch(args: argparse.Namespace, target_root: Path) -> int:
|
|
|
321
330
|
with spawn_lock(target_root):
|
|
322
331
|
if cmd == "rules":
|
|
323
332
|
return _dispatch_rules(args, target_root)
|
|
333
|
+
if cmd == "refresh":
|
|
334
|
+
hl.refresh_repository(target_root)
|
|
335
|
+
return 0
|
|
324
336
|
if cmd == "ide":
|
|
325
337
|
return _dispatch_ide(args, target_root)
|
|
326
338
|
if cmd == "extension":
|
|
@@ -222,6 +222,36 @@ def refresh_rules_navigation(target_root: Path) -> None:
|
|
|
222
222
|
ll.save_rules_navigation(target_root)
|
|
223
223
|
|
|
224
224
|
|
|
225
|
+
def refresh_repository(target_root: Path) -> None:
|
|
226
|
+
"""Sync core config from package defaults and rebuild all IDE-facing metadata."""
|
|
227
|
+
_require_init(target_root)
|
|
228
|
+
ll.merge_core_config_from_defaults(target_root)
|
|
229
|
+
needs_skill = _any_extension_has_skill_files(target_root)
|
|
230
|
+
needs_mcp = _any_extension_has_mcp_servers(target_root)
|
|
231
|
+
for ide in ll.list_ides(target_root):
|
|
232
|
+
_warn_capability_gaps(
|
|
233
|
+
ide,
|
|
234
|
+
ide_get(ide).detect(target_root).capabilities,
|
|
235
|
+
needs_skill_render=needs_skill,
|
|
236
|
+
needs_mcp_merge=needs_mcp,
|
|
237
|
+
)
|
|
238
|
+
merged_any = False
|
|
239
|
+
for ide in ll.list_ides(target_root):
|
|
240
|
+
for ext in ll.list_extensions(target_root):
|
|
241
|
+
names = refresh_mcp(target_root, ide, ext, emit_mcp_merged_notice=False)
|
|
242
|
+
if names:
|
|
243
|
+
merged_any = True
|
|
244
|
+
_refresh_skills_all_extensions_for_ide(target_root, ide)
|
|
245
|
+
for ide in ll.list_ides(target_root):
|
|
246
|
+
refresh_agent_ignore(target_root, ide)
|
|
247
|
+
if merged_any:
|
|
248
|
+
print(MCP_MERGED_NOTICE)
|
|
249
|
+
refresh_gitignore(target_root)
|
|
250
|
+
refresh_navigation(target_root)
|
|
251
|
+
for ide in ll.list_ides(target_root):
|
|
252
|
+
refresh_entry_point(target_root, ide)
|
|
253
|
+
|
|
254
|
+
|
|
225
255
|
def _refresh_extension_core(target_root: Path, extension: str) -> None:
|
|
226
256
|
needs_skill = _any_extension_has_skill_files(target_root)
|
|
227
257
|
needs_mcp = bool(ll.list_mcp(target_root, extension).servers)
|
|
@@ -536,6 +566,7 @@ __all__ = [
|
|
|
536
566
|
"refresh_gitignore",
|
|
537
567
|
"refresh_mcp",
|
|
538
568
|
"refresh_navigation",
|
|
569
|
+
"refresh_repository",
|
|
539
570
|
"refresh_rules_navigation",
|
|
540
571
|
"refresh_skills",
|
|
541
572
|
"reinstall_extension",
|
|
@@ -18,7 +18,7 @@ from spawn_cli.io.json_io import load_json
|
|
|
18
18
|
from spawn_cli.io.paths import ensure_dir
|
|
19
19
|
from spawn_cli.io.text_io import read_lines, write_lines
|
|
20
20
|
from spawn_cli.io.yaml_io import configure_yaml_dump, load_yaml, save_yaml
|
|
21
|
-
from spawn_cli.models.config import ExtensionConfig, FileMode, IdeList, ReadFlag
|
|
21
|
+
from spawn_cli.models.config import CoreConfig, ExtensionConfig, FileMode, IdeList, ReadFlag
|
|
22
22
|
from spawn_cli.models.mcp import McpCapabilities, McpEnvVar, McpServer, McpTransport, NormalizedMcp
|
|
23
23
|
from spawn_cli.models.skill import SkillFileRef, SkillMetadata, SkillRawInfo
|
|
24
24
|
|
|
@@ -451,8 +451,6 @@ def get_navigation_metadata(target_root: Path, extension: str) -> dict:
|
|
|
451
451
|
|
|
452
452
|
def get_core_agent_ignore(target_root: Path) -> list[str]:
|
|
453
453
|
path = _spawn(target_root) / ".core" / "config.yaml"
|
|
454
|
-
from spawn_cli.models.config import CoreConfig
|
|
455
|
-
|
|
456
454
|
data = load_yaml(path)
|
|
457
455
|
if not data:
|
|
458
456
|
return []
|
|
@@ -460,6 +458,47 @@ def get_core_agent_ignore(target_root: Path) -> list[str]:
|
|
|
460
458
|
return list(core.agent_ignore)
|
|
461
459
|
|
|
462
460
|
|
|
461
|
+
def merge_core_config_from_defaults(target_root: Path) -> None:
|
|
462
|
+
"""Merge bundled default core config into ``spawn/.core/config.yaml``.
|
|
463
|
+
|
|
464
|
+
``version`` becomes the bundled default. ``agent-ignore`` is the bundled
|
|
465
|
+
list (in resource order) followed by any existing patterns not present in
|
|
466
|
+
the default set.
|
|
467
|
+
"""
|
|
468
|
+
path = _spawn(target_root) / ".core" / "config.yaml"
|
|
469
|
+
if not path.is_file():
|
|
470
|
+
raise SpawnError("spawn/.core/config.yaml is missing; run spawn init.")
|
|
471
|
+
bundled_txt = (
|
|
472
|
+
resources.files("spawn_cli.resources")
|
|
473
|
+
.joinpath("default_core_config.yaml")
|
|
474
|
+
.read_text(encoding="utf-8")
|
|
475
|
+
)
|
|
476
|
+
yparse = YAML(typ="safe")
|
|
477
|
+
raw_default = yparse.load(bundled_txt)
|
|
478
|
+
if not isinstance(raw_default, dict):
|
|
479
|
+
raise SpawnError("internal error: bundled default core config is not a mapping")
|
|
480
|
+
try:
|
|
481
|
+
default_core = CoreConfig.model_validate(raw_default)
|
|
482
|
+
except Exception as e:
|
|
483
|
+
raise SpawnError(f"internal error: bundled default core config invalid: {e}") from e
|
|
484
|
+
raw_existing = load_yaml(path)
|
|
485
|
+
if not raw_existing:
|
|
486
|
+
raise SpawnError(
|
|
487
|
+
"spawn/.core/config.yaml is empty or invalid; restore it or run spawn init again."
|
|
488
|
+
)
|
|
489
|
+
try:
|
|
490
|
+
current = CoreConfig.model_validate(raw_existing)
|
|
491
|
+
except Exception as e:
|
|
492
|
+
raise SpawnError(f"invalid spawn/.core/config.yaml: {e}") from e
|
|
493
|
+
default_set = set(default_core.agent_ignore)
|
|
494
|
+
merged_ignore = list(default_core.agent_ignore)
|
|
495
|
+
for g in current.agent_ignore:
|
|
496
|
+
if g not in default_set:
|
|
497
|
+
merged_ignore.append(g)
|
|
498
|
+
out = CoreConfig(version=default_core.version, agent_ignore=merged_ignore)
|
|
499
|
+
save_yaml(path, out.model_dump(by_alias=True))
|
|
500
|
+
|
|
501
|
+
|
|
463
502
|
def get_ext_agent_ignore(target_root: Path, extension: str) -> list[str]:
|
|
464
503
|
cfg = _load_ext_config(target_root, extension)
|
|
465
504
|
return list(cfg.agent_ignore)
|
|
@@ -890,6 +929,7 @@ __all__ = [
|
|
|
890
929
|
"list_ides",
|
|
891
930
|
"list_mcp",
|
|
892
931
|
"list_skills",
|
|
932
|
+
"merge_core_config_from_defaults",
|
|
893
933
|
"METADATA_TEMP_MAX_AGE_SECONDS",
|
|
894
934
|
"normalize_skill_name",
|
|
895
935
|
"prune_metadata_temp",
|
|
@@ -88,6 +88,28 @@ def test_spawn_rules_refresh(tmp_path, monkeypatch):
|
|
|
88
88
|
mock_refresh.assert_called_once_with(root)
|
|
89
89
|
|
|
90
90
|
|
|
91
|
+
def test_spawn_refresh(tmp_path, monkeypatch):
|
|
92
|
+
monkeypatch.chdir(tmp_path)
|
|
93
|
+
from spawn_cli.core import low_level as ll
|
|
94
|
+
|
|
95
|
+
ll.init(tmp_path)
|
|
96
|
+
root = tmp_path.resolve()
|
|
97
|
+
mock_repo = MagicMock()
|
|
98
|
+
|
|
99
|
+
with patch("spawn_cli.cli.spawn_lock", _noop_lock), patch(
|
|
100
|
+
"spawn_cli.cli.hl.refresh_repository", mock_repo
|
|
101
|
+
):
|
|
102
|
+
assert main(["refresh"]) == 0
|
|
103
|
+
|
|
104
|
+
mock_repo.assert_called_once_with(root)
|
|
105
|
+
|
|
106
|
+
|
|
107
|
+
def test_spawn_refresh_need_init(tmp_path, monkeypatch, capsys):
|
|
108
|
+
monkeypatch.chdir(tmp_path)
|
|
109
|
+
assert main(["refresh"]) == 1
|
|
110
|
+
assert "need init before" in capsys.readouterr().err
|
|
111
|
+
|
|
112
|
+
|
|
91
113
|
def test_spawn_ide_list_supported_ides(tmp_path, monkeypatch, capsys):
|
|
92
114
|
monkeypatch.chdir(tmp_path)
|
|
93
115
|
(tmp_path / "spawn").mkdir()
|
|
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
|