substrate-setup 0.2.0__tar.gz → 0.2.2__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 (37) hide show
  1. {substrate_setup-0.2.0 → substrate_setup-0.2.2}/.gitignore +77 -74
  2. substrate_setup-0.2.2/PKG-INFO +69 -0
  3. substrate_setup-0.2.2/README.md +40 -0
  4. {substrate_setup-0.2.0 → substrate_setup-0.2.2}/pyproject.toml +62 -62
  5. {substrate_setup-0.2.0 → substrate_setup-0.2.2}/scripts/lint_no_app_import.sh +10 -10
  6. {substrate_setup-0.2.0 → substrate_setup-0.2.2}/scripts/regenerate_fallback_catalog.py +68 -68
  7. {substrate_setup-0.2.0 → substrate_setup-0.2.2}/substrate_setup/__init__.py +6 -6
  8. {substrate_setup-0.2.0 → substrate_setup-0.2.2}/substrate_setup/__main__.py +5 -5
  9. {substrate_setup-0.2.0 → substrate_setup-0.2.2}/substrate_setup/agents/__init__.py +20 -20
  10. {substrate_setup-0.2.0 → substrate_setup-0.2.2}/substrate_setup/agents/aider.py +37 -7
  11. {substrate_setup-0.2.0 → substrate_setup-0.2.2}/substrate_setup/agents/base.py +56 -56
  12. {substrate_setup-0.2.0 → substrate_setup-0.2.2}/substrate_setup/agents/cursor.py +90 -90
  13. {substrate_setup-0.2.0 → substrate_setup-0.2.2}/substrate_setup/agents/hermes.py +50 -13
  14. {substrate_setup-0.2.0 → substrate_setup-0.2.2}/substrate_setup/backup.py +32 -32
  15. {substrate_setup-0.2.0 → substrate_setup-0.2.2}/substrate_setup/catalog.py +109 -109
  16. {substrate_setup-0.2.0 → substrate_setup-0.2.2}/substrate_setup/cli.py +249 -211
  17. {substrate_setup-0.2.0 → substrate_setup-0.2.2}/substrate_setup/credentials.py +82 -82
  18. {substrate_setup-0.2.0 → substrate_setup-0.2.2}/substrate_setup/data/fallback_catalog.json +343 -343
  19. {substrate_setup-0.2.0 → substrate_setup-0.2.2}/substrate_setup/markers.py +27 -27
  20. {substrate_setup-0.2.0 → substrate_setup-0.2.2}/tests/agents/test_aider.py +54 -0
  21. {substrate_setup-0.2.0 → substrate_setup-0.2.2}/tests/agents/test_cursor.py +87 -87
  22. {substrate_setup-0.2.0 → substrate_setup-0.2.2}/tests/agents/test_hermes.py +69 -0
  23. {substrate_setup-0.2.0 → substrate_setup-0.2.2}/tests/conftest.py +1 -1
  24. {substrate_setup-0.2.0 → substrate_setup-0.2.2}/tests/test_backup.py +38 -38
  25. {substrate_setup-0.2.0 → substrate_setup-0.2.2}/tests/test_catalog.py +95 -95
  26. substrate_setup-0.2.2/tests/test_cli.py +246 -0
  27. {substrate_setup-0.2.0 → substrate_setup-0.2.2}/tests/test_credentials.py +80 -80
  28. {substrate_setup-0.2.0 → substrate_setup-0.2.2}/tests/test_e2e.py +111 -111
  29. {substrate_setup-0.2.0 → substrate_setup-0.2.2}/tests/test_markers.py +45 -45
  30. {substrate_setup-0.2.0 → substrate_setup-0.2.2}/uv.lock +1 -1
  31. substrate_setup-0.2.0/PKG-INFO +0 -40
  32. substrate_setup-0.2.0/README.md +0 -11
  33. substrate_setup-0.2.0/tests/test_cli.py +0 -118
  34. {substrate_setup-0.2.0 → substrate_setup-0.2.2}/substrate_setup/agents/continue_dev.py +0 -0
  35. {substrate_setup-0.2.0 → substrate_setup-0.2.2}/tests/__init__.py +0 -0
  36. {substrate_setup-0.2.0 → substrate_setup-0.2.2}/tests/agents/__init__.py +0 -0
  37. {substrate_setup-0.2.0 → substrate_setup-0.2.2}/tests/agents/test_continue_dev.py +0 -0
@@ -1,74 +1,77 @@
1
- # Secrets
2
- .env
3
- .env.local
4
- .env.*.local
5
- *.secret
6
- secrets/
7
- credentials.txt
8
- credentials.*.txt
9
-
10
- # Python
11
- __pycache__/
12
- *.py[cod]
13
- *$py.class
14
- *.so
15
- .Python
16
- .venv/
17
- venv/
18
- env/
19
- .pytest_cache/
20
- .mypy_cache/
21
- .ruff_cache/
22
- *.egg-info/
23
- dist/
24
- build/
25
-
26
- # Node / Next.js
27
- node_modules/
28
- .next/
29
- .vercel/
30
- .turbo/
31
-
32
- # CodeGraph (local-only code knowledge graph index)
33
- .codegraph/
34
- out/
35
- *.tsbuildinfo
36
- .npm/
37
- .yarn/
38
-
39
- # IDE
40
- .vscode/
41
- .idea/
42
- *.swp
43
- *.swo
44
-
45
- # OS
46
- .DS_Store
47
- Thumbs.db
48
- desktop.ini
49
-
50
- # Logs
51
- *.log
52
- logs/
53
-
54
- # Local databases
55
- *.db
56
- *.sqlite
57
- *.sqlite3
58
-
59
- # Editors
60
- *~
61
- .#*
62
-
63
- # Coverage
64
- .coverage
65
- htmlcov/
66
- coverage.xml
67
- *.cover
68
-
69
- # Claude Code project state (if any project-local)
70
- .claude/
71
-
72
- # Misc
73
- *.bak
74
- *.tmp
1
+ # Secrets
2
+ .env
3
+ .env.local
4
+ .env.*.local
5
+ *.secret
6
+ secrets/
7
+ credentials.txt
8
+ credentials.*.txt
9
+
10
+ # Python
11
+ __pycache__/
12
+ *.py[cod]
13
+ *$py.class
14
+ *.so
15
+ .Python
16
+ .venv/
17
+ venv/
18
+ env/
19
+ .pytest_cache/
20
+ .mypy_cache/
21
+ .ruff_cache/
22
+ *.egg-info/
23
+ dist/
24
+ build/
25
+
26
+ # Node / Next.js
27
+ node_modules/
28
+ .next/
29
+ .vercel/
30
+ .turbo/
31
+
32
+ # CodeGraph (local-only code knowledge graph index)
33
+ .codegraph/
34
+ out/
35
+ *.tsbuildinfo
36
+ .npm/
37
+ .yarn/
38
+
39
+ # IDE
40
+ .vscode/
41
+ .idea/
42
+ *.swp
43
+ *.swo
44
+
45
+ # OS
46
+ .DS_Store
47
+ Thumbs.db
48
+ desktop.ini
49
+
50
+ # Logs
51
+ *.log
52
+ logs/
53
+
54
+ # Local databases
55
+ *.db
56
+ *.sqlite
57
+ *.sqlite3
58
+
59
+ # Editors
60
+ *~
61
+ .#*
62
+
63
+ # Coverage
64
+ .coverage
65
+ htmlcov/
66
+ coverage.xml
67
+ *.cover
68
+
69
+ # Claude Code project state (if any project-local)
70
+ .claude/
71
+
72
+ # Misc
73
+ *.bak
74
+ *.tmp
75
+
76
+ # brainstorming companion working dir
77
+ .superpowers/
@@ -0,0 +1,69 @@
1
+ Metadata-Version: 2.4
2
+ Name: substrate-setup
3
+ Version: 0.2.2
4
+ Summary: One-shot local configurator for coding agents against a Substrate gateway
5
+ Project-URL: Homepage, https://github.com/FrankXiaA/substrate-solutions
6
+ Project-URL: Source, https://github.com/FrankXiaA/substrate-solutions/tree/main/substrate-api/substrate_setup
7
+ Project-URL: Issues, https://github.com/FrankXiaA/substrate-solutions/issues
8
+ Author: Substrate Solutions
9
+ License: MIT
10
+ Keywords: aider,continue,gateway,hermes,llm,openai-compatible,substrate
11
+ Classifier: Development Status :: 4 - Beta
12
+ Classifier: Environment :: Console
13
+ Classifier: Intended Audience :: Developers
14
+ Classifier: License :: OSI Approved :: MIT License
15
+ Classifier: Operating System :: OS Independent
16
+ Classifier: Programming Language :: Python :: 3 :: Only
17
+ Classifier: Programming Language :: Python :: 3.12
18
+ Classifier: Topic :: Software Development :: Code Generators
19
+ Classifier: Topic :: Utilities
20
+ Requires-Python: >=3.12
21
+ Requires-Dist: httpx>=0.27
22
+ Requires-Dist: ruamel-yaml>=0.18
23
+ Provides-Extra: dev
24
+ Requires-Dist: mypy<2,>=1.13; extra == 'dev'
25
+ Requires-Dist: pytest-httpx>=0.30; extra == 'dev'
26
+ Requires-Dist: pytest>=8.0; extra == 'dev'
27
+ Requires-Dist: ruff<1,>=0.7; extra == 'dev'
28
+ Description-Content-Type: text/markdown
29
+
30
+ # substrate-setup
31
+
32
+ One-shot configurator that points local coding agents at a Substrate gateway.
33
+
34
+ ## Install
35
+
36
+ `substrate-setup` requires Python 3.12+. The installers below pick the right interpreter automatically — you don't need a Python 3.12 already on your machine:
37
+
38
+ ```bash
39
+ # Option A — uv (recommended; downloads Python 3.12 on demand if missing)
40
+ uv tool install substrate-setup
41
+
42
+ # Option B — pipx
43
+ pipx install substrate-setup
44
+ ```
45
+
46
+ Don't have `uv`? Install it once with:
47
+
48
+ ```bash
49
+ curl -LsSf https://astral.sh/uv/install.sh | sh # macOS / Linux
50
+ # or on Windows:
51
+ powershell -ExecutionPolicy ByPass -c "irm https://astral.sh/uv/install.ps1 | iex"
52
+ ```
53
+
54
+ > **Why not `pip install substrate-setup`?** It works only if the `pip` on your PATH is bound to a Python 3.12+ interpreter. Anaconda's default `pip` (Python 3.9) is the common pitfall — PyPI hides every release from it with `Requires-Python >=3.12` and the error message is unhelpful. `uv` and `pipx` both create an isolated 3.12 venv for the tool, so they sidestep the issue entirely.
55
+
56
+ ## Use
57
+
58
+ ```bash
59
+ export SUBSTRATE_API_KEY="sk-substrate-..." # or be prompted
60
+
61
+ substrate-setup configure # detect installed agents and wire them up
62
+ substrate-setup verify # read-only: confirm everything points at the gateway
63
+ substrate-setup remove # strip the substrate-managed entries
64
+ substrate-setup --help
65
+ ```
66
+
67
+ Supported agents: `hermes`, `cursor`, `aider`, `continue`.
68
+
69
+ Subset with `--agents-only hermes,aider`. Preview without writing: `--dry-run`. Override the gateway base URL: `--base-url https://your-gateway.example.com`.
@@ -0,0 +1,40 @@
1
+ # substrate-setup
2
+
3
+ One-shot configurator that points local coding agents at a Substrate gateway.
4
+
5
+ ## Install
6
+
7
+ `substrate-setup` requires Python 3.12+. The installers below pick the right interpreter automatically — you don't need a Python 3.12 already on your machine:
8
+
9
+ ```bash
10
+ # Option A — uv (recommended; downloads Python 3.12 on demand if missing)
11
+ uv tool install substrate-setup
12
+
13
+ # Option B — pipx
14
+ pipx install substrate-setup
15
+ ```
16
+
17
+ Don't have `uv`? Install it once with:
18
+
19
+ ```bash
20
+ curl -LsSf https://astral.sh/uv/install.sh | sh # macOS / Linux
21
+ # or on Windows:
22
+ powershell -ExecutionPolicy ByPass -c "irm https://astral.sh/uv/install.ps1 | iex"
23
+ ```
24
+
25
+ > **Why not `pip install substrate-setup`?** It works only if the `pip` on your PATH is bound to a Python 3.12+ interpreter. Anaconda's default `pip` (Python 3.9) is the common pitfall — PyPI hides every release from it with `Requires-Python >=3.12` and the error message is unhelpful. `uv` and `pipx` both create an isolated 3.12 venv for the tool, so they sidestep the issue entirely.
26
+
27
+ ## Use
28
+
29
+ ```bash
30
+ export SUBSTRATE_API_KEY="sk-substrate-..." # or be prompted
31
+
32
+ substrate-setup configure # detect installed agents and wire them up
33
+ substrate-setup verify # read-only: confirm everything points at the gateway
34
+ substrate-setup remove # strip the substrate-managed entries
35
+ substrate-setup --help
36
+ ```
37
+
38
+ Supported agents: `hermes`, `cursor`, `aider`, `continue`.
39
+
40
+ Subset with `--agents-only hermes,aider`. Preview without writing: `--dry-run`. Override the gateway base URL: `--base-url https://your-gateway.example.com`.
@@ -1,62 +1,62 @@
1
- [build-system]
2
- requires = ["hatchling"]
3
- build-backend = "hatchling.build"
4
-
5
- [project]
6
- name = "substrate-setup"
7
- version = "0.2.0"
8
- description = "One-shot local configurator for coding agents against a Substrate gateway"
9
- readme = "README.md"
10
- requires-python = ">=3.12"
11
- authors = [{ name = "Substrate Solutions" }]
12
- license = { text = "MIT" }
13
- keywords = ["substrate", "llm", "gateway", "aider", "continue", "hermes", "openai-compatible"]
14
- classifiers = [
15
- "Development Status :: 4 - Beta",
16
- "Environment :: Console",
17
- "Intended Audience :: Developers",
18
- "License :: OSI Approved :: MIT License",
19
- "Operating System :: OS Independent",
20
- "Programming Language :: Python :: 3 :: Only",
21
- "Programming Language :: Python :: 3.12",
22
- "Topic :: Software Development :: Code Generators",
23
- "Topic :: Utilities",
24
- ]
25
- dependencies = [
26
- "httpx>=0.27",
27
- "ruamel.yaml>=0.18",
28
- ]
29
-
30
- [project.urls]
31
- Homepage = "https://github.com/FrankXiaA/substrate-solutions"
32
- Source = "https://github.com/FrankXiaA/substrate-solutions/tree/main/substrate-api/substrate_setup"
33
- Issues = "https://github.com/FrankXiaA/substrate-solutions/issues"
34
-
35
- [project.optional-dependencies]
36
- dev = [
37
- "pytest>=8.0",
38
- "pytest-httpx>=0.30",
39
- "ruff>=0.7,<1",
40
- "mypy>=1.13,<2",
41
- ]
42
-
43
- [project.scripts]
44
- substrate-setup = "substrate_setup.cli:main"
45
-
46
- [tool.hatch.build.targets.wheel]
47
- packages = ["substrate_setup"]
48
-
49
- [tool.pytest.ini_options]
50
- testpaths = ["tests"]
51
-
52
- [tool.mypy]
53
- python_version = "3.12"
54
- strict = true
55
- ignore_missing_imports = true
56
- packages = ["substrate_setup"]
57
-
58
- [[tool.mypy.overrides]]
59
- module = "tests.*"
60
- disallow_untyped_defs = false
61
- disallow_incomplete_defs = false
62
- check_untyped_defs = false
1
+ [build-system]
2
+ requires = ["hatchling"]
3
+ build-backend = "hatchling.build"
4
+
5
+ [project]
6
+ name = "substrate-setup"
7
+ version = "0.2.2"
8
+ description = "One-shot local configurator for coding agents against a Substrate gateway"
9
+ readme = "README.md"
10
+ requires-python = ">=3.12"
11
+ authors = [{ name = "Substrate Solutions" }]
12
+ license = { text = "MIT" }
13
+ keywords = ["substrate", "llm", "gateway", "aider", "continue", "hermes", "openai-compatible"]
14
+ classifiers = [
15
+ "Development Status :: 4 - Beta",
16
+ "Environment :: Console",
17
+ "Intended Audience :: Developers",
18
+ "License :: OSI Approved :: MIT License",
19
+ "Operating System :: OS Independent",
20
+ "Programming Language :: Python :: 3 :: Only",
21
+ "Programming Language :: Python :: 3.12",
22
+ "Topic :: Software Development :: Code Generators",
23
+ "Topic :: Utilities",
24
+ ]
25
+ dependencies = [
26
+ "httpx>=0.27",
27
+ "ruamel.yaml>=0.18",
28
+ ]
29
+
30
+ [project.urls]
31
+ Homepage = "https://github.com/FrankXiaA/substrate-solutions"
32
+ Source = "https://github.com/FrankXiaA/substrate-solutions/tree/main/substrate-api/substrate_setup"
33
+ Issues = "https://github.com/FrankXiaA/substrate-solutions/issues"
34
+
35
+ [project.optional-dependencies]
36
+ dev = [
37
+ "pytest>=8.0",
38
+ "pytest-httpx>=0.30",
39
+ "ruff>=0.7,<1",
40
+ "mypy>=1.13,<2",
41
+ ]
42
+
43
+ [project.scripts]
44
+ substrate-setup = "substrate_setup.cli:main"
45
+
46
+ [tool.hatch.build.targets.wheel]
47
+ packages = ["substrate_setup"]
48
+
49
+ [tool.pytest.ini_options]
50
+ testpaths = ["tests"]
51
+
52
+ [tool.mypy]
53
+ python_version = "3.12"
54
+ strict = true
55
+ ignore_missing_imports = true
56
+ packages = ["substrate_setup"]
57
+
58
+ [[tool.mypy.overrides]]
59
+ module = "tests.*"
60
+ disallow_untyped_defs = false
61
+ disallow_incomplete_defs = false
62
+ check_untyped_defs = false
@@ -1,10 +1,10 @@
1
- #!/usr/bin/env bash
2
- # CI lint: substrate_setup/ MUST NOT import anything from app/.
3
- # The two packages share a source tree, not a dependency graph.
4
- set -euo pipefail
5
- PKG_DIR="$(cd "$(dirname "$0")/.." && pwd)"
6
- if grep -RIn --include='*.py' '^from app\b\|^import app\b' "$PKG_DIR/substrate_setup"; then
7
- echo "ERROR: substrate_setup/ must not import from app/. See package boundary rule in spec."
8
- exit 1
9
- fi
10
- echo "OK: no app/ imports in substrate_setup/"
1
+ #!/usr/bin/env bash
2
+ # CI lint: substrate_setup/ MUST NOT import anything from app/.
3
+ # The two packages share a source tree, not a dependency graph.
4
+ set -euo pipefail
5
+ PKG_DIR="$(cd "$(dirname "$0")/.." && pwd)"
6
+ if grep -RIn --include='*.py' '^from app\b\|^import app\b' "$PKG_DIR/substrate_setup"; then
7
+ echo "ERROR: substrate_setup/ must not import from app/. See package boundary rule in spec."
8
+ exit 1
9
+ fi
10
+ echo "OK: no app/ imports in substrate_setup/"
@@ -1,68 +1,68 @@
1
- """Regenerate the bundled fallback catalog snapshot.
2
-
3
- Run manually before each PyPI release of substrate-setup. NOT run by CI cron —
4
- a transient production glitch should NEVER be baked into the next release.
5
-
6
- Usage:
7
- SUBSTRATE_API_KEY=sk-substrate-... \
8
- python scripts/regenerate_fallback_catalog.py [--base-url https://...]
9
-
10
- Writes substrate_setup/data/fallback_catalog.json. Applies a sanity check
11
- (>= 5 models, all five expected providers present) before writing.
12
- """
13
- from __future__ import annotations
14
-
15
- import argparse
16
- import json
17
- import os
18
- import sys
19
- from pathlib import Path
20
-
21
- import httpx
22
-
23
- EXPECTED_PROVIDERS = {"anthropic", "openai", "google", "deepseek", "moonshot"}
24
- DEFAULT_BASE_URL = "https://substrate-solutions-api.fly.dev/v1"
25
- TARGET = Path(__file__).resolve().parents[1] / "substrate_setup" / "data" / "fallback_catalog.json"
26
-
27
-
28
- def main(argv: list[str] | None = None) -> int:
29
- parser = argparse.ArgumentParser()
30
- parser.add_argument("--base-url", default=DEFAULT_BASE_URL)
31
- args = parser.parse_args(argv)
32
-
33
- api_key = os.environ.get("SUBSTRATE_API_KEY")
34
- if not api_key:
35
- print("ERROR: SUBSTRATE_API_KEY env var required.", file=sys.stderr)
36
- return 1
37
-
38
- # Tolerate --base-url with or without a trailing /v1 (same convention as
39
- # substrate_setup.catalog.fetch_catalog).
40
- normalized = args.base_url.rstrip("/")
41
- if normalized.endswith("/v1"):
42
- normalized = normalized[:-3]
43
-
44
- resp = httpx.get(
45
- f"{normalized}/v1/models",
46
- headers={"Authorization": f"Bearer {api_key}"},
47
- timeout=15.0,
48
- )
49
- resp.raise_for_status()
50
- payload = resp.json()
51
-
52
- data = payload.get("data") or []
53
- providers = {item.get("owned_by") for item in data}
54
- if len(data) < 5:
55
- print(f"REFUSED: only {len(data)} models in catalog (expected >= 5).", file=sys.stderr)
56
- return 2
57
- if not EXPECTED_PROVIDERS.issubset(providers):
58
- missing = EXPECTED_PROVIDERS - providers
59
- print(f"REFUSED: missing providers {missing}.", file=sys.stderr)
60
- return 2
61
-
62
- TARGET.write_text(json.dumps(payload, indent=2) + "\n", encoding="utf-8")
63
- print(f"Wrote {len(data)} models to {TARGET}")
64
- return 0
65
-
66
-
67
- if __name__ == "__main__":
68
- raise SystemExit(main())
1
+ """Regenerate the bundled fallback catalog snapshot.
2
+
3
+ Run manually before each PyPI release of substrate-setup. NOT run by CI cron —
4
+ a transient production glitch should NEVER be baked into the next release.
5
+
6
+ Usage:
7
+ SUBSTRATE_API_KEY=sk-substrate-... \
8
+ python scripts/regenerate_fallback_catalog.py [--base-url https://...]
9
+
10
+ Writes substrate_setup/data/fallback_catalog.json. Applies a sanity check
11
+ (>= 5 models, all five expected providers present) before writing.
12
+ """
13
+ from __future__ import annotations
14
+
15
+ import argparse
16
+ import json
17
+ import os
18
+ import sys
19
+ from pathlib import Path
20
+
21
+ import httpx
22
+
23
+ EXPECTED_PROVIDERS = {"anthropic", "openai", "google", "deepseek", "moonshot"}
24
+ DEFAULT_BASE_URL = "https://substrate-solutions-api.fly.dev/v1"
25
+ TARGET = Path(__file__).resolve().parents[1] / "substrate_setup" / "data" / "fallback_catalog.json"
26
+
27
+
28
+ def main(argv: list[str] | None = None) -> int:
29
+ parser = argparse.ArgumentParser()
30
+ parser.add_argument("--base-url", default=DEFAULT_BASE_URL)
31
+ args = parser.parse_args(argv)
32
+
33
+ api_key = os.environ.get("SUBSTRATE_API_KEY")
34
+ if not api_key:
35
+ print("ERROR: SUBSTRATE_API_KEY env var required.", file=sys.stderr)
36
+ return 1
37
+
38
+ # Tolerate --base-url with or without a trailing /v1 (same convention as
39
+ # substrate_setup.catalog.fetch_catalog).
40
+ normalized = args.base_url.rstrip("/")
41
+ if normalized.endswith("/v1"):
42
+ normalized = normalized[:-3]
43
+
44
+ resp = httpx.get(
45
+ f"{normalized}/v1/models",
46
+ headers={"Authorization": f"Bearer {api_key}"},
47
+ timeout=15.0,
48
+ )
49
+ resp.raise_for_status()
50
+ payload = resp.json()
51
+
52
+ data = payload.get("data") or []
53
+ providers = {item.get("owned_by") for item in data}
54
+ if len(data) < 5:
55
+ print(f"REFUSED: only {len(data)} models in catalog (expected >= 5).", file=sys.stderr)
56
+ return 2
57
+ if not EXPECTED_PROVIDERS.issubset(providers):
58
+ missing = EXPECTED_PROVIDERS - providers
59
+ print(f"REFUSED: missing providers {missing}.", file=sys.stderr)
60
+ return 2
61
+
62
+ TARGET.write_text(json.dumps(payload, indent=2) + "\n", encoding="utf-8")
63
+ print(f"Wrote {len(data)} models to {TARGET}")
64
+ return 0
65
+
66
+
67
+ if __name__ == "__main__":
68
+ raise SystemExit(main())
@@ -1,6 +1,6 @@
1
- """substrate-setup — one-shot configurator for coding agents.
2
-
3
- See https://github.com/FrankXiaA/substrate-solutions for the gateway it
4
- configures against.
5
- """
6
- __version__ = "0.2.0"
1
+ """substrate-setup — one-shot configurator for coding agents.
2
+
3
+ See https://github.com/FrankXiaA/substrate-solutions for the gateway it
4
+ configures against.
5
+ """
6
+ __version__ = "0.2.2"
@@ -1,5 +1,5 @@
1
- """Module entry point: ``python -m substrate_setup``."""
2
- from substrate_setup.cli import main
3
-
4
- if __name__ == "__main__":
5
- raise SystemExit(main())
1
+ """Module entry point: ``python -m substrate_setup``."""
2
+ from substrate_setup.cli import main
3
+
4
+ if __name__ == "__main__":
5
+ raise SystemExit(main())
@@ -1,20 +1,20 @@
1
- """Agent registry. Imports each agent module; exposes the list.
2
-
3
- Order of this list is the order they print in the summary.
4
- """
5
- from __future__ import annotations
6
-
7
- from substrate_setup.agents.aider import AiderAgent
8
- from substrate_setup.agents.base import Agent
9
- from substrate_setup.agents.continue_dev import ContinueAgent
10
- from substrate_setup.agents.cursor import CursorAgent
11
- from substrate_setup.agents.hermes import HermesAgent
12
-
13
- ALL_AGENTS: list[Agent] = [
14
- HermesAgent(),
15
- CursorAgent(),
16
- AiderAgent(),
17
- ContinueAgent(),
18
- ]
19
-
20
- AGENT_NAMES: list[str] = [a.name for a in ALL_AGENTS]
1
+ """Agent registry. Imports each agent module; exposes the list.
2
+
3
+ Order of this list is the order they print in the summary.
4
+ """
5
+ from __future__ import annotations
6
+
7
+ from substrate_setup.agents.aider import AiderAgent
8
+ from substrate_setup.agents.base import Agent
9
+ from substrate_setup.agents.continue_dev import ContinueAgent
10
+ from substrate_setup.agents.cursor import CursorAgent
11
+ from substrate_setup.agents.hermes import HermesAgent
12
+
13
+ ALL_AGENTS: list[Agent] = [
14
+ HermesAgent(),
15
+ CursorAgent(),
16
+ AiderAgent(),
17
+ ContinueAgent(),
18
+ ]
19
+
20
+ AGENT_NAMES: list[str] = [a.name for a in ALL_AGENTS]
@@ -120,6 +120,30 @@ def _user_has_meaningful_keys(payload: dict[str, Any]) -> bool:
120
120
  return False
121
121
 
122
122
 
123
+ def _existing_matches_substrate_shape(
124
+ payload: dict[str, Any], ctx: ConfigureContext
125
+ ) -> bool:
126
+ """True if existing top-level values look substrate-shaped already.
127
+
128
+ Specifically:
129
+ - openai-api-base matches ctx.base_url
130
+ - openai-api-key starts with "sk-substrate-" (the user may have
131
+ rotated keys since, so we don't require an exact match)
132
+
133
+ Lets us silently take ownership of configs that were either manually
134
+ configured to match our schema or written by an earlier substrate-setup
135
+ version that didn't drop the marker. Without this auto-claim, every
136
+ user upgrading from 0.1 (or who manually fixed their config) sees a
137
+ refusal ERROR on first 0.2.x run.
138
+ """
139
+ if payload.get("openai-api-base") != ctx.base_url:
140
+ return False
141
+ api_key = payload.get("openai-api-key")
142
+ if not isinstance(api_key, str) or not api_key.startswith("sk-substrate-"):
143
+ return False
144
+ return True
145
+
146
+
123
147
  def _pick_default_model(catalog: list[Any]) -> str:
124
148
  if not catalog:
125
149
  raise ValueError("Cannot pick default model from empty catalog")
@@ -140,13 +164,19 @@ class AiderAgent(Agent):
140
164
  payload = _load_yaml(conf)
141
165
 
142
166
  if not _has_substrate_marker(payload) and _user_has_meaningful_keys(payload):
143
- return AgentResult(
144
- ResultStatus.ERROR,
145
- "Aider ~/.aider.conf.yml has user-owned openai-api-base / "
146
- "openai-api-key / model keys but no substrate-setup marker; "
147
- "refused to overwrite. To take over, remove those keys (or "
148
- "the whole file) and re-run substrate-setup.",
149
- )
167
+ if _existing_matches_substrate_shape(payload, ctx):
168
+ # Shape already matches us -- silently take ownership.
169
+ # Fall through to the normal write below, which adds the
170
+ # marker and refreshes our 3 keys.
171
+ pass
172
+ else:
173
+ return AgentResult(
174
+ ResultStatus.ERROR,
175
+ "Aider ~/.aider.conf.yml has user-owned openai-api-base / "
176
+ "openai-api-key / model keys but no substrate-setup marker; "
177
+ "refused to overwrite. To take over, remove those keys (or "
178
+ "the whole file) and re-run substrate-setup.",
179
+ )
150
180
 
151
181
  if conf.exists():
152
182
  backup = backup_once(conf)