least-cost-capable 0.1.1__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.
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 ALTA VISTA360, S.L.U.
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
@@ -0,0 +1,57 @@
1
+ Metadata-Version: 2.4
2
+ Name: least-cost-capable
3
+ Version: 0.1.1
4
+ Summary: Portable Agent Skill and helper that picks the lowest declared cost rank among catalogued models with the required capabilities. No provider calls.
5
+ Author: ALTA VISTA360, S.L.U.
6
+ License-Expression: MIT
7
+ Project-URL: Homepage, https://github.com/kanibal-studio/least-cost-capable
8
+ Project-URL: Issues, https://github.com/kanibal-studio/least-cost-capable/issues
9
+ Project-URL: PyPI, https://pypi.org/project/least-cost-capable/
10
+ Keywords: agent-skills,ai-agents,llm,model-routing,cost-optimization,claude-code
11
+ Classifier: Development Status :: 4 - Beta
12
+ Classifier: Intended Audience :: Developers
13
+ Classifier: Operating System :: OS Independent
14
+ Classifier: Programming Language :: Python :: 3
15
+ Classifier: Programming Language :: Python :: 3 :: Only
16
+ Classifier: Programming Language :: Python :: 3.10
17
+ Classifier: Programming Language :: Python :: 3.11
18
+ Classifier: Programming Language :: Python :: 3.12
19
+ Classifier: Programming Language :: Python :: 3.13
20
+ Classifier: Programming Language :: Python :: 3.14
21
+ Classifier: Topic :: Scientific/Engineering :: Artificial Intelligence
22
+ Requires-Python: >=3.10
23
+ Description-Content-Type: text/markdown
24
+ License-File: LICENSE
25
+ Provides-Extra: dev
26
+ Requires-Dist: pytest>=8; extra == "dev"
27
+ Dynamic: license-file
28
+
29
+ # least-cost-capable
30
+
31
+ A portable Agent Skill and small Python helper for comparing declared relative cost ranks among agent models in a supplied catalogue. It checks task capability, availability, authorization, and whether cost evidence is comparable. It does not contact model providers, spend credits, launch agents, or guarantee actual savings or dispatch.
32
+
33
+ Run with Python 3.10 or newer:
34
+
35
+ ```sh
36
+ python scripts/select_model.py examples/catalogue.json
37
+ ```
38
+
39
+ The example selects `standard-code`: the lower-ranked candidate lacks debugging capability, and the unavailable candidate is excluded. Tests cover mismatched input/output cost metrics, unknown evidence, duplicate IDs, and invalid catalogue roots. Invalid JSON or malformed catalogue data exits with a named command-line validation error and no selection.
40
+
41
+ ## Use with Claude Code
42
+
43
+ Install it as a personal skill, available in every project:
44
+
45
+ ```sh
46
+ git clone https://github.com/kanibal-studio/least-cost-capable ~/.claude/skills/least-cost-capable
47
+ ```
48
+
49
+ Or copy this folder into a project's `.claude/skills/least-cost-capable/` to share it with the repository. Claude Code reads `SKILL.md` from that folder, and the helper stays at `scripts/select_model.py` inside it. Describe the task, give Claude a catalogue in the format of `examples/catalogue.json` (or the facts to build one), and ask it to use the least-cost-capable skill. The skill reports a choice from the catalogue's facts only; it does not switch models, change settings or launch agents.
50
+
51
+ Created by ALTA VISTA360, S.L.U. (`kanibal-studio`).
52
+
53
+ - [Altavista360](https://altavista360.com/)
54
+ - [Altavista Conseil](https://altavista-conseil.com/)
55
+ - [Kanibal Studio](https://infos.kanibal-studio.com/)
56
+
57
+ Licensed under the [MIT License](LICENSE).
@@ -0,0 +1,29 @@
1
+ # least-cost-capable
2
+
3
+ A portable Agent Skill and small Python helper for comparing declared relative cost ranks among agent models in a supplied catalogue. It checks task capability, availability, authorization, and whether cost evidence is comparable. It does not contact model providers, spend credits, launch agents, or guarantee actual savings or dispatch.
4
+
5
+ Run with Python 3.10 or newer:
6
+
7
+ ```sh
8
+ python scripts/select_model.py examples/catalogue.json
9
+ ```
10
+
11
+ The example selects `standard-code`: the lower-ranked candidate lacks debugging capability, and the unavailable candidate is excluded. Tests cover mismatched input/output cost metrics, unknown evidence, duplicate IDs, and invalid catalogue roots. Invalid JSON or malformed catalogue data exits with a named command-line validation error and no selection.
12
+
13
+ ## Use with Claude Code
14
+
15
+ Install it as a personal skill, available in every project:
16
+
17
+ ```sh
18
+ git clone https://github.com/kanibal-studio/least-cost-capable ~/.claude/skills/least-cost-capable
19
+ ```
20
+
21
+ Or copy this folder into a project's `.claude/skills/least-cost-capable/` to share it with the repository. Claude Code reads `SKILL.md` from that folder, and the helper stays at `scripts/select_model.py` inside it. Describe the task, give Claude a catalogue in the format of `examples/catalogue.json` (or the facts to build one), and ask it to use the least-cost-capable skill. The skill reports a choice from the catalogue's facts only; it does not switch models, change settings or launch agents.
22
+
23
+ Created by ALTA VISTA360, S.L.U. (`kanibal-studio`).
24
+
25
+ - [Altavista360](https://altavista360.com/)
26
+ - [Altavista Conseil](https://altavista-conseil.com/)
27
+ - [Kanibal Studio](https://infos.kanibal-studio.com/)
28
+
29
+ Licensed under the [MIT License](LICENSE).
@@ -0,0 +1,57 @@
1
+ Metadata-Version: 2.4
2
+ Name: least-cost-capable
3
+ Version: 0.1.1
4
+ Summary: Portable Agent Skill and helper that picks the lowest declared cost rank among catalogued models with the required capabilities. No provider calls.
5
+ Author: ALTA VISTA360, S.L.U.
6
+ License-Expression: MIT
7
+ Project-URL: Homepage, https://github.com/kanibal-studio/least-cost-capable
8
+ Project-URL: Issues, https://github.com/kanibal-studio/least-cost-capable/issues
9
+ Project-URL: PyPI, https://pypi.org/project/least-cost-capable/
10
+ Keywords: agent-skills,ai-agents,llm,model-routing,cost-optimization,claude-code
11
+ Classifier: Development Status :: 4 - Beta
12
+ Classifier: Intended Audience :: Developers
13
+ Classifier: Operating System :: OS Independent
14
+ Classifier: Programming Language :: Python :: 3
15
+ Classifier: Programming Language :: Python :: 3 :: Only
16
+ Classifier: Programming Language :: Python :: 3.10
17
+ Classifier: Programming Language :: Python :: 3.11
18
+ Classifier: Programming Language :: Python :: 3.12
19
+ Classifier: Programming Language :: Python :: 3.13
20
+ Classifier: Programming Language :: Python :: 3.14
21
+ Classifier: Topic :: Scientific/Engineering :: Artificial Intelligence
22
+ Requires-Python: >=3.10
23
+ Description-Content-Type: text/markdown
24
+ License-File: LICENSE
25
+ Provides-Extra: dev
26
+ Requires-Dist: pytest>=8; extra == "dev"
27
+ Dynamic: license-file
28
+
29
+ # least-cost-capable
30
+
31
+ A portable Agent Skill and small Python helper for comparing declared relative cost ranks among agent models in a supplied catalogue. It checks task capability, availability, authorization, and whether cost evidence is comparable. It does not contact model providers, spend credits, launch agents, or guarantee actual savings or dispatch.
32
+
33
+ Run with Python 3.10 or newer:
34
+
35
+ ```sh
36
+ python scripts/select_model.py examples/catalogue.json
37
+ ```
38
+
39
+ The example selects `standard-code`: the lower-ranked candidate lacks debugging capability, and the unavailable candidate is excluded. Tests cover mismatched input/output cost metrics, unknown evidence, duplicate IDs, and invalid catalogue roots. Invalid JSON or malformed catalogue data exits with a named command-line validation error and no selection.
40
+
41
+ ## Use with Claude Code
42
+
43
+ Install it as a personal skill, available in every project:
44
+
45
+ ```sh
46
+ git clone https://github.com/kanibal-studio/least-cost-capable ~/.claude/skills/least-cost-capable
47
+ ```
48
+
49
+ Or copy this folder into a project's `.claude/skills/least-cost-capable/` to share it with the repository. Claude Code reads `SKILL.md` from that folder, and the helper stays at `scripts/select_model.py` inside it. Describe the task, give Claude a catalogue in the format of `examples/catalogue.json` (or the facts to build one), and ask it to use the least-cost-capable skill. The skill reports a choice from the catalogue's facts only; it does not switch models, change settings or launch agents.
50
+
51
+ Created by ALTA VISTA360, S.L.U. (`kanibal-studio`).
52
+
53
+ - [Altavista360](https://altavista360.com/)
54
+ - [Altavista Conseil](https://altavista-conseil.com/)
55
+ - [Kanibal Studio](https://infos.kanibal-studio.com/)
56
+
57
+ Licensed under the [MIT License](LICENSE).
@@ -0,0 +1,12 @@
1
+ LICENSE
2
+ README.md
3
+ pyproject.toml
4
+ least_cost_capable.egg-info/PKG-INFO
5
+ least_cost_capable.egg-info/SOURCES.txt
6
+ least_cost_capable.egg-info/dependency_links.txt
7
+ least_cost_capable.egg-info/entry_points.txt
8
+ least_cost_capable.egg-info/requires.txt
9
+ least_cost_capable.egg-info/top_level.txt
10
+ scripts/__init__.py
11
+ scripts/select_model.py
12
+ tests/test_select_model.py
@@ -0,0 +1,2 @@
1
+ [console_scripts]
2
+ least-cost-capable = least_cost_capable.select_model:main
@@ -0,0 +1,3 @@
1
+
2
+ [dev]
3
+ pytest>=8
@@ -0,0 +1 @@
1
+ least_cost_capable
@@ -0,0 +1,52 @@
1
+ [build-system]
2
+ requires = ["setuptools>=77"]
3
+ build-backend = "setuptools.build_meta"
4
+
5
+ [project]
6
+ name = "least-cost-capable"
7
+ dynamic = ["version"]
8
+ description = "Portable Agent Skill and helper that picks the lowest declared cost rank among catalogued models with the required capabilities. No provider calls."
9
+ readme = "README.md"
10
+ requires-python = ">=3.10"
11
+ license = "MIT"
12
+ license-files = ["LICENSE"]
13
+ authors = [{ name = "ALTA VISTA360, S.L.U." }]
14
+ keywords = [
15
+ "agent-skills",
16
+ "ai-agents",
17
+ "llm",
18
+ "model-routing",
19
+ "cost-optimization",
20
+ "claude-code",
21
+ ]
22
+ classifiers = [
23
+ "Development Status :: 4 - Beta",
24
+ "Intended Audience :: Developers",
25
+ "Operating System :: OS Independent",
26
+ "Programming Language :: Python :: 3",
27
+ "Programming Language :: Python :: 3 :: Only",
28
+ "Programming Language :: Python :: 3.10",
29
+ "Programming Language :: Python :: 3.11",
30
+ "Programming Language :: Python :: 3.12",
31
+ "Programming Language :: Python :: 3.13",
32
+ "Programming Language :: Python :: 3.14",
33
+ "Topic :: Scientific/Engineering :: Artificial Intelligence",
34
+ ]
35
+
36
+ [project.optional-dependencies]
37
+ dev = ["pytest>=8"]
38
+
39
+ [project.scripts]
40
+ least-cost-capable = "least_cost_capable.select_model:main"
41
+
42
+ [project.urls]
43
+ Homepage = "https://github.com/kanibal-studio/least-cost-capable"
44
+ Issues = "https://github.com/kanibal-studio/least-cost-capable/issues"
45
+ PyPI = "https://pypi.org/project/least-cost-capable/"
46
+
47
+ [tool.setuptools]
48
+ packages = ["least_cost_capable"]
49
+ package-dir = { least_cost_capable = "scripts" }
50
+
51
+ [tool.setuptools.dynamic]
52
+ version = { attr = "least_cost_capable.__version__" }
@@ -0,0 +1,3 @@
1
+ """least-cost-capable: compare declared cost ranks among capable catalogued models."""
2
+
3
+ __version__ = "0.1.1"
@@ -0,0 +1,116 @@
1
+ #!/usr/bin/env python3
2
+ """Choose a least-cost capable model from an explicit JSON catalogue."""
3
+ import argparse
4
+ import json
5
+ import math
6
+ from pathlib import Path
7
+
8
+
9
+ def select(catalogue):
10
+ if not isinstance(catalogue, dict):
11
+ raise ValueError("catalogue root must be a JSON object")
12
+ required = catalogue.get("required_capabilities")
13
+ models = catalogue.get("models")
14
+ if not isinstance(required, list) or not all(isinstance(x, str) for x in required):
15
+ raise ValueError("required_capabilities must be a list of strings")
16
+ if not isinstance(models, list):
17
+ raise ValueError("models must be a list")
18
+ if any(not isinstance(m, dict) for m in models):
19
+ raise ValueError("each models entry must be an object")
20
+ if any(not isinstance(m.get("id"), str) or not m["id"].strip() for m in models):
21
+ raise ValueError("each model must have a non-empty string id")
22
+ ids = [m["id"] for m in models]
23
+ if len(set(ids)) != len(ids):
24
+ raise ValueError("model ids must be unique")
25
+
26
+ exclusions, unresolved, capable = [], [], []
27
+ for m in models:
28
+ reasons = []
29
+ questions = []
30
+ if m.get("available") is False:
31
+ reasons.append("unavailable")
32
+ elif m.get("available") is not True:
33
+ questions.append("availability unknown")
34
+ if m.get("authorized") is False:
35
+ reasons.append("unauthorized")
36
+ elif m.get("authorized") is not True:
37
+ questions.append("authorization unknown")
38
+
39
+ caps = m.get("capabilities")
40
+ complete = m.get("capabilities_complete")
41
+ if caps is not None and (not isinstance(caps, list)
42
+ or not all(isinstance(c, str) for c in caps)):
43
+ raise ValueError("capabilities must be null or a list of strings")
44
+ if complete is not True:
45
+ questions.append("capability list not confirmed complete")
46
+ elif caps is None:
47
+ questions.append("capabilities unknown")
48
+ elif not set(required).issubset(set(caps)):
49
+ reasons.append("required capability absent from complete list")
50
+
51
+ if reasons:
52
+ exclusions.append({"id": m["id"], "reasons": reasons})
53
+ if questions:
54
+ unresolved.append({"id": m["id"], "questions": questions})
55
+ if reasons or questions:
56
+ continue
57
+ capable.append(m)
58
+
59
+ if not capable:
60
+ return {"status": "needs_information" if unresolved else "no_candidate_in_catalogue",
61
+ "selected": None,
62
+ "reason": ("No eligible capable model is established in the supplied catalogue; "
63
+ "this does not prove none exists outside it."),
64
+ "unresolved": unresolved, "excluded": exclusions}
65
+
66
+ def comparable_cost(m):
67
+ cost = m.get("cost")
68
+ if not isinstance(cost, dict):
69
+ return False
70
+ fields = ("metric", "unit", "workload", "window", "source", "as_of", "relative_rank")
71
+ if not all(field in cost for field in fields):
72
+ return False
73
+ return (all(isinstance(cost[field], str) and cost[field].strip()
74
+ for field in fields[:-1])
75
+ and isinstance(cost["relative_rank"], (int, float))
76
+ and not isinstance(cost["relative_rank"], bool)
77
+ and math.isfinite(cost["relative_rank"]))
78
+
79
+ missing = [m["id"] for m in capable if not comparable_cost(m)]
80
+ if missing:
81
+ return {"status": "needs_information", "selected": None,
82
+ "reason": ("Cost evidence must declare metric, unit, workload, window, source, "
83
+ "as_of, and a finite relative_rank."),
84
+ "missing_cost": missing, "unresolved": unresolved, "excluded": exclusions}
85
+
86
+ signatures = {(m["cost"]["metric"], m["cost"]["unit"],
87
+ m["cost"]["workload"], m["cost"]["window"],
88
+ m["cost"]["source"], m["cost"]["as_of"]) for m in capable}
89
+ if len(signatures) != 1:
90
+ return {"status": "needs_information", "selected": None,
91
+ "reason": ("Capable candidates have different cost metrics, units, workloads, "
92
+ "windows, sources, or as_of dates."),
93
+ "unresolved": unresolved, "excluded": exclusions}
94
+ winner = min(capable, key=lambda m: m["cost"]["relative_rank"])
95
+ return {"status": "selected", "selected": winner["id"],
96
+ "comparison": {key: winner["cost"][key] for key in
97
+ ("metric", "unit", "workload", "window", "source", "as_of")},
98
+ "supplied_relative_rank": winner["cost"]["relative_rank"],
99
+ "reason": ("Lowest supplied rank for the declared matching cost comparison; "
100
+ "this is not a claim of real-world savings or a dispatch guarantee."),
101
+ "unresolved": unresolved, "excluded": exclusions}
102
+
103
+
104
+ def main():
105
+ parser = argparse.ArgumentParser(description=__doc__)
106
+ parser.add_argument("catalogue", type=Path, help="Path to a JSON catalogue")
107
+ args = parser.parse_args()
108
+ try:
109
+ result = select(json.loads(args.catalogue.read_text(encoding="utf-8")))
110
+ except (OSError, json.JSONDecodeError, ValueError) as exc:
111
+ parser.error(str(exc))
112
+ print(json.dumps(result, ensure_ascii=False, indent=2))
113
+
114
+
115
+ if __name__ == "__main__":
116
+ main()
@@ -0,0 +1,4 @@
1
+ [egg_info]
2
+ tag_build =
3
+ tag_date = 0
4
+
@@ -0,0 +1,105 @@
1
+ import importlib.util
2
+ from pathlib import Path
3
+
4
+ import pytest
5
+
6
+
7
+ SCRIPT = Path(__file__).parents[1] / "scripts/select_model.py"
8
+ SPEC = importlib.util.spec_from_file_location("select_model", SCRIPT)
9
+ module = importlib.util.module_from_spec(SPEC)
10
+ SPEC.loader.exec_module(module)
11
+
12
+
13
+ def model(identifier, *, capabilities, metric="API input-token cost",
14
+ unit="USD per million input tokens", workload="same task",
15
+ window="last 7 days", rank=1, available=True, authorized=True,
16
+ capabilities_complete=True, source="synthetic", as_of="2026-10-11"):
17
+ return {"id": identifier, "capabilities": capabilities,
18
+ "capabilities_complete": capabilities_complete,
19
+ "cost": {"metric": metric, "unit": unit, "workload": workload,
20
+ "window": window, "relative_rank": rank,
21
+ "source": source, "as_of": as_of},
22
+ "available": available, "authorized": authorized}
23
+
24
+
25
+ def test_selects_lowest_comparable_capable_and_excludes_unavailable():
26
+ result = module.select({"required_capabilities": ["code", "debug"], "models": [
27
+ model("too-weak", capabilities=["code"], rank=0),
28
+ model("standard", capabilities=["code", "debug"], rank=2),
29
+ model("unavailable", capabilities=["code", "debug"], rank=0, available=False),
30
+ model("costlier", capabilities=["code", "debug"], rank=5),
31
+ ]})
32
+ assert result["status"] == "selected"
33
+ assert result["selected"] == "standard"
34
+
35
+
36
+ def test_refuses_to_compare_api_input_and_output_cost():
37
+ result = module.select({"required_capabilities": ["code"], "models": [
38
+ model("input", capabilities=["code"], metric="API input-token cost", rank=1),
39
+ model("output", capabilities=["code"], metric="API output-token cost", rank=0),
40
+ ]})
41
+ assert result["status"] == "needs_information"
42
+ assert result["selected"] is None
43
+
44
+
45
+ def test_missing_cost_for_capable_candidate_stays_unknown():
46
+ unknown = model("unknown", capabilities=["code"])
47
+ unknown.pop("cost")
48
+ result = module.select({"required_capabilities": ["code"], "models": [
49
+ model("priced", capabilities=["code"], rank=2), unknown,
50
+ ]})
51
+ assert result["status"] == "needs_information"
52
+ assert result["missing_cost"] == ["unknown"]
53
+
54
+
55
+ def test_unknown_availability_authorization_or_capabilities_remains_unresolved():
56
+ result = module.select({"required_capabilities": ["code"], "models": [
57
+ model("availability-unknown", capabilities=["code"], available=None),
58
+ model("authorization-unknown", capabilities=["code"], authorized=None),
59
+ model("capabilities-incomplete", capabilities=["code"], capabilities_complete=False),
60
+ ]})
61
+ assert result["status"] == "needs_information"
62
+ assert result["selected"] is None
63
+ assert {item["id"] for item in result["unresolved"]} == {
64
+ "availability-unknown", "authorization-unknown", "capabilities-incomplete"}
65
+
66
+
67
+ def test_known_exclusion_is_reported_alongside_other_unknown_states():
68
+ result = module.select({"required_capabilities": ["code"], "models": [
69
+ model("unknown-and-forbidden", capabilities=["code"], authorized=False, available=None),
70
+ model("unavailable-but-authorization-unknown", capabilities=["code"],
71
+ available=False, authorized=None),
72
+ ]})
73
+ excluded = {item["id"]: item["reasons"] for item in result["excluded"]}
74
+ unresolved = {item["id"]: item["questions"] for item in result["unresolved"]}
75
+ assert "unauthorized" in excluded["unknown-and-forbidden"]
76
+ assert "availability unknown" in unresolved["unknown-and-forbidden"]
77
+ assert "unavailable" in excluded["unavailable-but-authorization-unknown"]
78
+ assert "authorization unknown" in unresolved["unavailable-but-authorization-unknown"]
79
+
80
+
81
+ def test_duplicate_ids_are_rejected():
82
+ repeated = model("same", capabilities=["code"])
83
+ try:
84
+ module.select({"required_capabilities": ["code"], "models": [repeated, dict(repeated)]})
85
+ except ValueError as exc:
86
+ assert "unique" in str(exc)
87
+ else:
88
+ raise AssertionError("duplicate model ids must fail closed")
89
+
90
+
91
+ @pytest.mark.parametrize("catalogue", [None, [], "not-an-object"])
92
+ def test_invalid_catalogue_root_returns_named_error(catalogue):
93
+ with pytest.raises(ValueError, match="catalogue root must be a JSON object"):
94
+ module.select(catalogue)
95
+
96
+
97
+ def test_no_capability_candidate_only_claims_catalogue_scope_and_reports_exclusions():
98
+ result = module.select({"required_capabilities": ["debug"], "models": [
99
+ model("weak", capabilities=["code"]),
100
+ model("off", capabilities=["debug"], available=False),
101
+ ]})
102
+ assert result["status"] == "no_candidate_in_catalogue"
103
+ assert "outside it" in result["reason"]
104
+ assert {tuple(x["reasons"]) for x in result["excluded"]} == {
105
+ ("required capability absent from complete list",), ("unavailable",)}