substrate-setup 0.2.1__tar.gz → 0.3.0__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 (41) hide show
  1. {substrate_setup-0.2.1 → substrate_setup-0.3.0}/.gitignore +77 -74
  2. substrate_setup-0.3.0/PKG-INFO +81 -0
  3. substrate_setup-0.3.0/README.md +51 -0
  4. {substrate_setup-0.2.1 → substrate_setup-0.3.0}/pyproject.toml +63 -62
  5. {substrate_setup-0.2.1 → substrate_setup-0.3.0}/scripts/lint_no_app_import.sh +10 -10
  6. {substrate_setup-0.2.1 → substrate_setup-0.3.0}/scripts/regenerate_fallback_catalog.py +68 -68
  7. {substrate_setup-0.2.1 → substrate_setup-0.3.0}/substrate_setup/__init__.py +6 -6
  8. {substrate_setup-0.2.1 → substrate_setup-0.3.0}/substrate_setup/__main__.py +5 -5
  9. {substrate_setup-0.2.1 → substrate_setup-0.3.0}/substrate_setup/agents/__init__.py +24 -20
  10. {substrate_setup-0.2.1 → substrate_setup-0.3.0}/substrate_setup/agents/aider.py +114 -37
  11. {substrate_setup-0.2.1 → substrate_setup-0.3.0}/substrate_setup/agents/base.py +56 -56
  12. substrate_setup-0.3.0/substrate_setup/agents/claude_code.py +339 -0
  13. substrate_setup-0.3.0/substrate_setup/agents/codex.py +345 -0
  14. {substrate_setup-0.2.1 → substrate_setup-0.3.0}/substrate_setup/agents/continue_dev.py +5 -1
  15. {substrate_setup-0.2.1 → substrate_setup-0.3.0}/substrate_setup/agents/cursor.py +94 -90
  16. {substrate_setup-0.2.1 → substrate_setup-0.3.0}/substrate_setup/agents/hermes.py +126 -10
  17. {substrate_setup-0.2.1 → substrate_setup-0.3.0}/substrate_setup/backup.py +32 -32
  18. {substrate_setup-0.2.1 → substrate_setup-0.3.0}/substrate_setup/catalog.py +115 -109
  19. {substrate_setup-0.2.1 → substrate_setup-0.3.0}/substrate_setup/cli.py +249 -225
  20. {substrate_setup-0.2.1 → substrate_setup-0.3.0}/substrate_setup/credentials.py +82 -82
  21. {substrate_setup-0.2.1 → substrate_setup-0.3.0}/substrate_setup/data/fallback_catalog.json +343 -343
  22. {substrate_setup-0.2.1 → substrate_setup-0.3.0}/substrate_setup/markers.py +27 -27
  23. {substrate_setup-0.2.1 → substrate_setup-0.3.0}/tests/agents/test_aider.py +97 -11
  24. substrate_setup-0.3.0/tests/agents/test_claude_code.py +262 -0
  25. substrate_setup-0.3.0/tests/agents/test_codex.py +278 -0
  26. {substrate_setup-0.2.1 → substrate_setup-0.3.0}/tests/agents/test_continue_dev.py +27 -0
  27. {substrate_setup-0.2.1 → substrate_setup-0.3.0}/tests/agents/test_cursor.py +111 -87
  28. {substrate_setup-0.2.1 → substrate_setup-0.3.0}/tests/agents/test_hermes.py +109 -1
  29. substrate_setup-0.3.0/tests/conftest.py +19 -0
  30. {substrate_setup-0.2.1 → substrate_setup-0.3.0}/tests/test_backup.py +38 -38
  31. {substrate_setup-0.2.1 → substrate_setup-0.3.0}/tests/test_catalog.py +95 -95
  32. {substrate_setup-0.2.1 → substrate_setup-0.3.0}/tests/test_cli.py +226 -175
  33. {substrate_setup-0.2.1 → substrate_setup-0.3.0}/tests/test_credentials.py +80 -80
  34. {substrate_setup-0.2.1 → substrate_setup-0.3.0}/tests/test_e2e.py +152 -111
  35. {substrate_setup-0.2.1 → substrate_setup-0.3.0}/tests/test_markers.py +45 -45
  36. substrate_setup-0.2.1/PKG-INFO +0 -40
  37. substrate_setup-0.2.1/README.md +0 -11
  38. substrate_setup-0.2.1/tests/conftest.py +0 -1
  39. substrate_setup-0.2.1/uv.lock +0 -341
  40. {substrate_setup-0.2.1 → substrate_setup-0.3.0}/tests/__init__.py +0 -0
  41. {substrate_setup-0.2.1 → substrate_setup-0.3.0}/tests/agents/__init__.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,81 @@
1
+ Metadata-Version: 2.4
2
+ Name: substrate-setup
3
+ Version: 0.3.0
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
+ Requires-Dist: tomli-w>=1.2
24
+ Provides-Extra: dev
25
+ Requires-Dist: mypy<2,>=1.13; extra == 'dev'
26
+ Requires-Dist: pytest-httpx>=0.30; extra == 'dev'
27
+ Requires-Dist: pytest>=8.0; extra == 'dev'
28
+ Requires-Dist: ruff<1,>=0.7; extra == 'dev'
29
+ Description-Content-Type: text/markdown
30
+
31
+ # substrate-setup
32
+
33
+ One-shot configurator that points local coding agents at a Substrate gateway.
34
+
35
+ ## Install
36
+
37
+ `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:
38
+
39
+ ```bash
40
+ # Option A — uv (recommended; downloads Python 3.12 on demand if missing)
41
+ uv tool install substrate-setup
42
+
43
+ # Option B — pipx
44
+ pipx install substrate-setup
45
+ ```
46
+
47
+ Don't have `uv`? Install it once with:
48
+
49
+ ```bash
50
+ curl -LsSf https://astral.sh/uv/install.sh | sh # macOS / Linux
51
+ # or on Windows:
52
+ powershell -ExecutionPolicy ByPass -c "irm https://astral.sh/uv/install.ps1 | iex"
53
+ ```
54
+
55
+ > **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.
56
+
57
+ ## Use
58
+
59
+ ```bash
60
+ export SUBSTRATE_API_KEY="sk-substrate-..." # or be prompted
61
+
62
+ substrate-setup configure # detect installed agents and wire them up
63
+ substrate-setup verify # read-only: confirm everything points at the gateway
64
+ substrate-setup remove # strip the substrate-managed entries
65
+ substrate-setup --help
66
+ ```
67
+
68
+ Supported agents: `hermes`, `cursor`, `aider`, `continue`, `claude-code`, `codex`.
69
+
70
+ Subset with `--agents-only hermes,aider`. Preview without writing: `--dry-run`. Override the gateway base URL: `--base-url https://your-gateway.example.com`.
71
+
72
+ ### Per-agent catalog UX (0.3.0+)
73
+
74
+ | Agent | How it learns about Substrate's models |
75
+ |---|---|
76
+ | `hermes` | Live URL fetch via `model_catalog.providers.substrate.url`. Picker shows all chat-capable Substrate models, refreshed on Hermes' 24h TTL. |
77
+ | `cursor` | Walkthrough printed after configure — copy the base URL, key, and model ids into Cursor's Settings → Models. |
78
+ | `aider` | `~/.aider.model.metadata.json` written with one entry per chat-capable Substrate model. Use `--model openai/<id>` to switch. |
79
+ | `continue` | All chat-capable Substrate models written as separate `models:` entries in `~/.continue/config.yaml`. |
80
+ | `claude-code` | `~/.claude/settings.json` env block (`ANTHROPIC_BASE_URL`, `ANTHROPIC_AUTH_TOKEN`, `ANTHROPIC_MODEL`). The `/model` picker stays opus/sonnet/haiku — use `claude --model openai/<id>` for any other Substrate model. **Note:** requires Substrate's `/v1/messages` endpoint, which is gated behind a Fly feature flag — until it's flipped, requests return 404. |
81
+ | `codex` | `~/.codex/config.toml` `[model_providers.substrate]` block with `wire_api = "responses"`. Set `SUBSTRATE_API_KEY` in your shell rc. **Note:** requires Substrate's `/v1/responses` endpoint (gateway protocol adapters Phase 2 — not yet started). |
@@ -0,0 +1,51 @@
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`, `claude-code`, `codex`.
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`.
41
+
42
+ ### Per-agent catalog UX (0.3.0+)
43
+
44
+ | Agent | How it learns about Substrate's models |
45
+ |---|---|
46
+ | `hermes` | Live URL fetch via `model_catalog.providers.substrate.url`. Picker shows all chat-capable Substrate models, refreshed on Hermes' 24h TTL. |
47
+ | `cursor` | Walkthrough printed after configure — copy the base URL, key, and model ids into Cursor's Settings → Models. |
48
+ | `aider` | `~/.aider.model.metadata.json` written with one entry per chat-capable Substrate model. Use `--model openai/<id>` to switch. |
49
+ | `continue` | All chat-capable Substrate models written as separate `models:` entries in `~/.continue/config.yaml`. |
50
+ | `claude-code` | `~/.claude/settings.json` env block (`ANTHROPIC_BASE_URL`, `ANTHROPIC_AUTH_TOKEN`, `ANTHROPIC_MODEL`). The `/model` picker stays opus/sonnet/haiku — use `claude --model openai/<id>` for any other Substrate model. **Note:** requires Substrate's `/v1/messages` endpoint, which is gated behind a Fly feature flag — until it's flipped, requests return 404. |
51
+ | `codex` | `~/.codex/config.toml` `[model_providers.substrate]` block with `wire_api = "responses"`. Set `SUBSTRATE_API_KEY` in your shell rc. **Note:** requires Substrate's `/v1/responses` endpoint (gateway protocol adapters Phase 2 — not yet started). |
@@ -1,62 +1,63 @@
1
- [build-system]
2
- requires = ["hatchling"]
3
- build-backend = "hatchling.build"
4
-
5
- [project]
6
- name = "substrate-setup"
7
- version = "0.2.1"
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.3.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
+ "tomli-w>=1.2",
29
+ ]
30
+
31
+ [project.urls]
32
+ Homepage = "https://github.com/FrankXiaA/substrate-solutions"
33
+ Source = "https://github.com/FrankXiaA/substrate-solutions/tree/main/substrate-api/substrate_setup"
34
+ Issues = "https://github.com/FrankXiaA/substrate-solutions/issues"
35
+
36
+ [project.optional-dependencies]
37
+ dev = [
38
+ "pytest>=8.0",
39
+ "pytest-httpx>=0.30",
40
+ "ruff>=0.7,<1",
41
+ "mypy>=1.13,<2",
42
+ ]
43
+
44
+ [project.scripts]
45
+ substrate-setup = "substrate_setup.cli:main"
46
+
47
+ [tool.hatch.build.targets.wheel]
48
+ packages = ["substrate_setup"]
49
+
50
+ [tool.pytest.ini_options]
51
+ testpaths = ["tests"]
52
+
53
+ [tool.mypy]
54
+ python_version = "3.12"
55
+ strict = true
56
+ ignore_missing_imports = true
57
+ packages = ["substrate_setup"]
58
+
59
+ [[tool.mypy.overrides]]
60
+ module = "tests.*"
61
+ disallow_untyped_defs = false
62
+ disallow_incomplete_defs = false
63
+ 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.1"
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.3.0"
@@ -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,24 @@
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.claude_code import ClaudeCodeAgent
10
+ from substrate_setup.agents.codex import CodexAgent
11
+ from substrate_setup.agents.continue_dev import ContinueAgent
12
+ from substrate_setup.agents.cursor import CursorAgent
13
+ from substrate_setup.agents.hermes import HermesAgent
14
+
15
+ ALL_AGENTS: list[Agent] = [
16
+ HermesAgent(),
17
+ CursorAgent(),
18
+ AiderAgent(),
19
+ ContinueAgent(),
20
+ ClaudeCodeAgent(),
21
+ CodexAgent(),
22
+ ]
23
+
24
+ AGENT_NAMES: list[str] = [a.name for a in ALL_AGENTS]