playmaker-cli 0.7.2__tar.gz → 0.8.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 (36) hide show
  1. {playmaker_cli-0.7.2 → playmaker_cli-0.8.0}/CHANGELOG.md +14 -0
  2. {playmaker_cli-0.7.2 → playmaker_cli-0.8.0}/PKG-INFO +1 -1
  3. {playmaker_cli-0.7.2 → playmaker_cli-0.8.0}/pyproject.toml +1 -1
  4. {playmaker_cli-0.7.2 → playmaker_cli-0.8.0}/src/playmaker/quotas.py +117 -0
  5. {playmaker_cli-0.7.2 → playmaker_cli-0.8.0}/.gitignore +0 -0
  6. {playmaker_cli-0.7.2 → playmaker_cli-0.8.0}/LICENSE +0 -0
  7. {playmaker_cli-0.7.2 → playmaker_cli-0.8.0}/README.md +0 -0
  8. {playmaker_cli-0.7.2 → playmaker_cli-0.8.0}/skills/playmaker-coach/SKILL.md +0 -0
  9. {playmaker_cli-0.7.2 → playmaker_cli-0.8.0}/src/playmaker/__init__.py +0 -0
  10. {playmaker_cli-0.7.2 → playmaker_cli-0.8.0}/src/playmaker/__main__.py +0 -0
  11. {playmaker_cli-0.7.2 → playmaker_cli-0.8.0}/src/playmaker/agents/__init__.py +0 -0
  12. {playmaker_cli-0.7.2 → playmaker_cli-0.8.0}/src/playmaker/agents/agy.py +0 -0
  13. {playmaker_cli-0.7.2 → playmaker_cli-0.8.0}/src/playmaker/agents/base.py +0 -0
  14. {playmaker_cli-0.7.2 → playmaker_cli-0.8.0}/src/playmaker/agents/claude.py +0 -0
  15. {playmaker_cli-0.7.2 → playmaker_cli-0.8.0}/src/playmaker/agents/codex.py +0 -0
  16. {playmaker_cli-0.7.2 → playmaker_cli-0.8.0}/src/playmaker/agents/gemini.py +0 -0
  17. {playmaker_cli-0.7.2 → playmaker_cli-0.8.0}/src/playmaker/agents/opencode.py +0 -0
  18. {playmaker_cli-0.7.2 → playmaker_cli-0.8.0}/src/playmaker/cli.py +0 -0
  19. {playmaker_cli-0.7.2 → playmaker_cli-0.8.0}/src/playmaker/config.py +0 -0
  20. {playmaker_cli-0.7.2 → playmaker_cli-0.8.0}/src/playmaker/notify.py +0 -0
  21. {playmaker_cli-0.7.2 → playmaker_cli-0.8.0}/src/playmaker/registry.py +0 -0
  22. {playmaker_cli-0.7.2 → playmaker_cli-0.8.0}/src/playmaker/state.py +0 -0
  23. {playmaker_cli-0.7.2 → playmaker_cli-0.8.0}/src/playmaker/watcher.py +0 -0
  24. {playmaker_cli-0.7.2 → playmaker_cli-0.8.0}/tests/__init__.py +0 -0
  25. {playmaker_cli-0.7.2 → playmaker_cli-0.8.0}/tests/test_agy.py +0 -0
  26. {playmaker_cli-0.7.2 → playmaker_cli-0.8.0}/tests/test_batch.py +0 -0
  27. {playmaker_cli-0.7.2 → playmaker_cli-0.8.0}/tests/test_binary.py +0 -0
  28. {playmaker_cli-0.7.2 → playmaker_cli-0.8.0}/tests/test_claude.py +0 -0
  29. {playmaker_cli-0.7.2 → playmaker_cli-0.8.0}/tests/test_codex.py +0 -0
  30. {playmaker_cli-0.7.2 → playmaker_cli-0.8.0}/tests/test_opencode.py +0 -0
  31. {playmaker_cli-0.7.2 → playmaker_cli-0.8.0}/tests/test_permissions.py +0 -0
  32. {playmaker_cli-0.7.2 → playmaker_cli-0.8.0}/tests/test_quotas_antigravity.py +0 -0
  33. {playmaker_cli-0.7.2 → playmaker_cli-0.8.0}/tests/test_quotas_zai.py +0 -0
  34. {playmaker_cli-0.7.2 → playmaker_cli-0.8.0}/tests/test_registry.py +0 -0
  35. {playmaker_cli-0.7.2 → playmaker_cli-0.8.0}/tests/test_skill.py +0 -0
  36. {playmaker_cli-0.7.2 → playmaker_cli-0.8.0}/tests/test_state.py +0 -0
@@ -5,6 +5,20 @@ All notable changes to this project will be documented in this file.
5
5
  The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
6
6
  and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
7
7
 
8
+ ## [0.8.0] - 2026-08-18
9
+
10
+ ### Added
11
+
12
+ - **Ollama in the quota table.** `playmaker quotas` now carries an `ollama`
13
+ provider for work dispatched through `opencode -m ollama/<tag>`. It is an
14
+ availability signal rather than a quota: `ok` at 100% with the pulled chat
15
+ models listed when the daemon is up and a completion-capable model is
16
+ present; `unsupported` — naming the exact `ollama pull` to run — when Ollama
17
+ is down or holds only embedding models, so an idle daemon with
18
+ `nomic-embed-text` never reads as free 27B capacity. Models are classified by
19
+ `/api/show` `capabilities`, not by name. The renderer for the provider
20
+ shipped in 0.7.2 ahead of the probe.
21
+
8
22
  ## [0.7.2] - 2026-08-18
9
23
 
10
24
  ### Fixed
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.5
2
2
  Name: playmaker-cli
3
- Version: 0.7.2
3
+ Version: 0.8.0
4
4
  Summary: Playing-coach CLI for orchestrating Claude Code, Codex, Antigravity and opencode sub-agents in parallel.
5
5
  Project-URL: Homepage, https://github.com/vladsafedev/playmaker
6
6
  Project-URL: Repository, https://github.com/vladsafedev/playmaker
@@ -1,6 +1,6 @@
1
1
  [project]
2
2
  name = "playmaker-cli"
3
- version = "0.7.2"
3
+ version = "0.8.0"
4
4
  description = "Playing-coach CLI for orchestrating Claude Code, Codex, Antigravity and opencode sub-agents in parallel."
5
5
  readme = "README.md"
6
6
  requires-python = ">=3.11"
@@ -9,6 +9,8 @@ All providers go through OAuth Bearer tokens; no WebKit, no PTY.
9
9
  - Gemini (retired locally): same creds -> cloudcode-pa.googleapis.com
10
10
  - Z.ai (GLM, dispatched via opencode): API key in opencode's auth.json ->
11
11
  api.z.ai/api/monitor/usage/quota/limit
12
+ - Ollama (local, also via opencode): no quota to fetch — localhost:11434
13
+ /api/tags is an availability probe reporting unmetered capacity
12
14
  """
13
15
 
14
16
  from __future__ import annotations
@@ -1088,6 +1090,120 @@ def zai_probe() -> dict:
1088
1090
  }
1089
1091
 
1090
1092
 
1093
+ # ---- Ollama (local, unmetered) ----------------------------------------------
1094
+
1095
+ # Ollama's own API, not the OpenAI-compatible /v1 shim that opencode dispatches
1096
+ # through: /api/tags is the cheapest liveness check and names what is actually
1097
+ # pulled on this machine.
1098
+ def _ollama_base_url() -> str:
1099
+ """OLLAMA_HOST as the daemon itself reads it — the scheme is optional."""
1100
+ host = os.environ.get("OLLAMA_HOST", "").strip() or "127.0.0.1:11434"
1101
+ if not host.startswith(("http://", "https://")):
1102
+ host = f"http://{host}"
1103
+ return host.rstrip("/")
1104
+
1105
+
1106
+ def _ollama_can_chat(base: str, name: str) -> bool:
1107
+ """True if /api/show lists a text-generation capability for this model.
1108
+
1109
+ Unknown or unreadable capabilities count as chat-capable: an older daemon
1110
+ that predates the field should not hide every model it serves.
1111
+ """
1112
+ try:
1113
+ info = _http_json(
1114
+ f"{base}/api/show", method="POST", body={"model": name}, timeout=3.0
1115
+ )
1116
+ except (urllib.error.URLError, OSError, RuntimeError, json.JSONDecodeError):
1117
+ return True
1118
+ caps = info.get("capabilities")
1119
+ if not isinstance(caps, list):
1120
+ return True
1121
+ return "completion" in caps
1122
+
1123
+
1124
+ def ollama_probe() -> dict:
1125
+ """Local inference capacity, dispatched via `opencode -m ollama/<tag>`.
1126
+
1127
+ This is an availability signal, not a quota. Every other provider here
1128
+ reports a bucket that depletes and refills; local Ollama has no bucket —
1129
+ work sent to it costs nothing from any subscription pool. So the honest
1130
+ report is "100% left, never resets", and that is precisely its value to the
1131
+ coach: overflow capacity that absorbs junior-tier fan-out while the metered
1132
+ pools stay reserved for work that needs them.
1133
+
1134
+ Read the ceiling as hardware, not entitlement — and read it as narrow. Fan-out
1135
+ width is whatever OLLAMA_NUM_PARALLEL allows, and every concurrent slot wants
1136
+ its own KV cache out of the same unified memory the weights already sit in, so
1137
+ a 27B q4 on 36GB has room for very few. Treat this as a shallow queue next to
1138
+ the cloud providers' real parallelism, not as equivalent capacity.
1139
+
1140
+ `models` lists what is pulled so the coach routes to a tag that exists rather
1141
+ than guessing one — and the tag decides the engine, not just the weights:
1142
+ Ollama sends GGUF builds to llama.cpp and safetensors builds to MLX, which on
1143
+ Apple Silicon is the faster path.
1144
+ """
1145
+ base = _ollama_base_url()
1146
+ try:
1147
+ # A local daemon answers in milliseconds or not at all; this probe must
1148
+ # never be the slow one in refresh_all.
1149
+ resp = _http_json(f"{base}/api/tags", timeout=3.0)
1150
+ except (urllib.error.URLError, OSError, RuntimeError, json.JSONDecodeError) as exc:
1151
+ if shutil.which("ollama") is None:
1152
+ return {
1153
+ "status": "unsupported",
1154
+ "reason": "ollama not installed — https://ollama.com/download",
1155
+ }
1156
+ return {
1157
+ "status": "unsupported",
1158
+ "reason": (
1159
+ f"ollama daemon unreachable at {base} "
1160
+ f"({type(exc).__name__}) — start it with `ollama serve`"
1161
+ ),
1162
+ }
1163
+
1164
+ pulled = sorted(
1165
+ str(entry.get("name"))
1166
+ for entry in (resp.get("models") or [])
1167
+ if isinstance(entry, dict) and entry.get("name")
1168
+ )
1169
+
1170
+ # Only a model that can *chat* is dispatch capacity. Embedding-only models
1171
+ # (mxbai, nomic, …) live in the same store and would otherwise make an idle
1172
+ # Ollama look like a free 27B — the coach would route work at nothing.
1173
+ # /api/show reports `capabilities` (["completion", "tools", …] vs
1174
+ # ["embedding"]); one local POST per model, milliseconds each.
1175
+ models = [name for name in pulled if _ollama_can_chat(base, name)]
1176
+
1177
+ if not models:
1178
+ return {
1179
+ "status": "unsupported",
1180
+ "reason": (
1181
+ "ollama up, but no chat model pulled — connector is dormant. "
1182
+ "Activate with `ollama pull qwen3.8:27b-mlx` (~18GB, MLX engine); "
1183
+ "the opencode provider and this probe pick it up on the next refresh."
1184
+ + (f" Pulled but embedding-only: {', '.join(pulled)}." if pulled else "")
1185
+ ),
1186
+ }
1187
+
1188
+ return {
1189
+ "status": "ok",
1190
+ "account_email": None,
1191
+ "tier": "unmetered",
1192
+ "windows": [
1193
+ {
1194
+ "name": "Local",
1195
+ "pct_left": 100,
1196
+ "reset_at_iso": None,
1197
+ "reset_relative": None,
1198
+ "forecast": "Unmetered — bounded by memory, not entitlement",
1199
+ "reserve_pct": None,
1200
+ }
1201
+ ],
1202
+ "source": "local",
1203
+ "models": models,
1204
+ }
1205
+
1206
+
1091
1207
  # ---- Aggregator -------------------------------------------------------------
1092
1208
 
1093
1209
 
@@ -1098,6 +1214,7 @@ PROBES = {
1098
1214
  "claude": claude_probe,
1099
1215
  "agy": antigravity_probe,
1100
1216
  "zai": zai_probe,
1217
+ "ollama": ollama_probe,
1101
1218
  }
1102
1219
 
1103
1220
 
File without changes
File without changes
File without changes