devops-bot-sdk 1.4.119__tar.gz → 1.4.120__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 (89) hide show
  1. {devops_bot_sdk-1.4.119 → devops_bot_sdk-1.4.120}/PKG-INFO +1 -1
  2. {devops_bot_sdk-1.4.119 → devops_bot_sdk-1.4.120}/devops_bot_sdk.egg-info/PKG-INFO +1 -1
  3. {devops_bot_sdk-1.4.119 → devops_bot_sdk-1.4.120}/sdk/__init__.py +2 -2
  4. {devops_bot_sdk-1.4.119 → devops_bot_sdk-1.4.120}/sdk/config.py +69 -0
  5. {devops_bot_sdk-1.4.119 → devops_bot_sdk-1.4.120}/sdk/ui/api.py +61 -0
  6. {devops_bot_sdk-1.4.119 → devops_bot_sdk-1.4.120}/sdk/ui/ip_allowlist.py +16 -10
  7. {devops_bot_sdk-1.4.119 → devops_bot_sdk-1.4.120}/README.md +0 -0
  8. {devops_bot_sdk-1.4.119 → devops_bot_sdk-1.4.120}/devops_bot_sdk.egg-info/SOURCES.txt +0 -0
  9. {devops_bot_sdk-1.4.119 → devops_bot_sdk-1.4.120}/devops_bot_sdk.egg-info/dependency_links.txt +0 -0
  10. {devops_bot_sdk-1.4.119 → devops_bot_sdk-1.4.120}/devops_bot_sdk.egg-info/entry_points.txt +0 -0
  11. {devops_bot_sdk-1.4.119 → devops_bot_sdk-1.4.120}/devops_bot_sdk.egg-info/requires.txt +0 -0
  12. {devops_bot_sdk-1.4.119 → devops_bot_sdk-1.4.120}/devops_bot_sdk.egg-info/top_level.txt +0 -0
  13. {devops_bot_sdk-1.4.119 → devops_bot_sdk-1.4.120}/pyproject.toml +0 -0
  14. {devops_bot_sdk-1.4.119 → devops_bot_sdk-1.4.120}/sdk/agentd.py +0 -0
  15. {devops_bot_sdk-1.4.119 → devops_bot_sdk-1.4.120}/sdk/agents/__init__.py +0 -0
  16. {devops_bot_sdk-1.4.119 → devops_bot_sdk-1.4.120}/sdk/agents/builder.py +0 -0
  17. {devops_bot_sdk-1.4.119 → devops_bot_sdk-1.4.120}/sdk/agents/chat.py +0 -0
  18. {devops_bot_sdk-1.4.119 → devops_bot_sdk-1.4.120}/sdk/agents/connectors.py +0 -0
  19. {devops_bot_sdk-1.4.119 → devops_bot_sdk-1.4.120}/sdk/agents/engines/__init__.py +0 -0
  20. {devops_bot_sdk-1.4.119 → devops_bot_sdk-1.4.120}/sdk/agents/engines/claude_code.py +0 -0
  21. {devops_bot_sdk-1.4.119 → devops_bot_sdk-1.4.120}/sdk/agents/engines/http.py +0 -0
  22. {devops_bot_sdk-1.4.119 → devops_bot_sdk-1.4.120}/sdk/agents/engines/mcp.py +0 -0
  23. {devops_bot_sdk-1.4.119 → devops_bot_sdk-1.4.120}/sdk/agents/engines/n8n.py +0 -0
  24. {devops_bot_sdk-1.4.119 → devops_bot_sdk-1.4.120}/sdk/agents/engines/shell.py +0 -0
  25. {devops_bot_sdk-1.4.119 → devops_bot_sdk-1.4.120}/sdk/agents/integrations.py +0 -0
  26. {devops_bot_sdk-1.4.119 → devops_bot_sdk-1.4.120}/sdk/agents/inventory.py +0 -0
  27. {devops_bot_sdk-1.4.119 → devops_bot_sdk-1.4.120}/sdk/agents/library/__init__.py +0 -0
  28. {devops_bot_sdk-1.4.119 → devops_bot_sdk-1.4.120}/sdk/agents/manifest.py +0 -0
  29. {devops_bot_sdk-1.4.119 → devops_bot_sdk-1.4.120}/sdk/agents/prompt_studio.py +0 -0
  30. {devops_bot_sdk-1.4.119 → devops_bot_sdk-1.4.120}/sdk/agents/prompt_template.py +0 -0
  31. {devops_bot_sdk-1.4.119 → devops_bot_sdk-1.4.120}/sdk/agents/prompt_vars.py +0 -0
  32. {devops_bot_sdk-1.4.119 → devops_bot_sdk-1.4.120}/sdk/agents/run_log.py +0 -0
  33. {devops_bot_sdk-1.4.119 → devops_bot_sdk-1.4.120}/sdk/agents/secrets.py +0 -0
  34. {devops_bot_sdk-1.4.119 → devops_bot_sdk-1.4.120}/sdk/agents/usage.py +0 -0
  35. {devops_bot_sdk-1.4.119 → devops_bot_sdk-1.4.120}/sdk/agents/varstore.py +0 -0
  36. {devops_bot_sdk-1.4.119 → devops_bot_sdk-1.4.120}/sdk/cli.py +0 -0
  37. {devops_bot_sdk-1.4.119 → devops_bot_sdk-1.4.120}/sdk/client.py +0 -0
  38. {devops_bot_sdk-1.4.119 → devops_bot_sdk-1.4.120}/sdk/collectors/__init__.py +0 -0
  39. {devops_bot_sdk-1.4.119 → devops_bot_sdk-1.4.120}/sdk/collectors/files.py +0 -0
  40. {devops_bot_sdk-1.4.119 → devops_bot_sdk-1.4.120}/sdk/collectors/process.py +0 -0
  41. {devops_bot_sdk-1.4.119 → devops_bot_sdk-1.4.120}/sdk/collectors/screenshot.py +0 -0
  42. {devops_bot_sdk-1.4.119 → devops_bot_sdk-1.4.120}/sdk/crucial.py +0 -0
  43. {devops_bot_sdk-1.4.119 → devops_bot_sdk-1.4.120}/sdk/design_verify/__init__.py +0 -0
  44. {devops_bot_sdk-1.4.119 → devops_bot_sdk-1.4.120}/sdk/design_verify/assets.py +0 -0
  45. {devops_bot_sdk-1.4.119 → devops_bot_sdk-1.4.120}/sdk/design_verify/bootstrap.py +0 -0
  46. {devops_bot_sdk-1.4.119 → devops_bot_sdk-1.4.120}/sdk/design_verify/browser.py +0 -0
  47. {devops_bot_sdk-1.4.119 → devops_bot_sdk-1.4.120}/sdk/design_verify/compare.py +0 -0
  48. {devops_bot_sdk-1.4.119 → devops_bot_sdk-1.4.120}/sdk/design_verify/loop.py +0 -0
  49. {devops_bot_sdk-1.4.119 → devops_bot_sdk-1.4.120}/sdk/exceptions.py +0 -0
  50. {devops_bot_sdk-1.4.119 → devops_bot_sdk-1.4.120}/sdk/git_ops.py +0 -0
  51. {devops_bot_sdk-1.4.119 → devops_bot_sdk-1.4.120}/sdk/graphify.py +0 -0
  52. {devops_bot_sdk-1.4.119 → devops_bot_sdk-1.4.120}/sdk/hooks/__init__.py +0 -0
  53. {devops_bot_sdk-1.4.119 → devops_bot_sdk-1.4.120}/sdk/hooks/crucial_guard.py +0 -0
  54. {devops_bot_sdk-1.4.119 → devops_bot_sdk-1.4.120}/sdk/ipc/__init__.py +0 -0
  55. {devops_bot_sdk-1.4.119 → devops_bot_sdk-1.4.120}/sdk/ipc/electron_bridge.py +0 -0
  56. {devops_bot_sdk-1.4.119 → devops_bot_sdk-1.4.120}/sdk/ipc/handlers.py +0 -0
  57. {devops_bot_sdk-1.4.119 → devops_bot_sdk-1.4.120}/sdk/local_exec.py +0 -0
  58. {devops_bot_sdk-1.4.119 → devops_bot_sdk-1.4.120}/sdk/models/__init__.py +0 -0
  59. {devops_bot_sdk-1.4.119 → devops_bot_sdk-1.4.120}/sdk/models/envelope.py +0 -0
  60. {devops_bot_sdk-1.4.119 → devops_bot_sdk-1.4.120}/sdk/models/requests.py +0 -0
  61. {devops_bot_sdk-1.4.119 → devops_bot_sdk-1.4.120}/sdk/models/responses.py +0 -0
  62. {devops_bot_sdk-1.4.119 → devops_bot_sdk-1.4.120}/sdk/models/snapshots.py +0 -0
  63. {devops_bot_sdk-1.4.119 → devops_bot_sdk-1.4.120}/sdk/platform_compat.py +0 -0
  64. {devops_bot_sdk-1.4.119 → devops_bot_sdk-1.4.120}/sdk/py.typed +0 -0
  65. {devops_bot_sdk-1.4.119 → devops_bot_sdk-1.4.120}/sdk/run_auto.py +0 -0
  66. {devops_bot_sdk-1.4.119 → devops_bot_sdk-1.4.120}/sdk/sse.py +0 -0
  67. {devops_bot_sdk-1.4.119 → devops_bot_sdk-1.4.120}/sdk/test.py +0 -0
  68. {devops_bot_sdk-1.4.119 → devops_bot_sdk-1.4.120}/sdk/test_pipeline.py +0 -0
  69. {devops_bot_sdk-1.4.119 → devops_bot_sdk-1.4.120}/sdk/ui/__init__.py +0 -0
  70. {devops_bot_sdk-1.4.119 → devops_bot_sdk-1.4.120}/sdk/ui/dashboard.py +0 -0
  71. {devops_bot_sdk-1.4.119 → devops_bot_sdk-1.4.120}/sdk/ui/jobs.py +0 -0
  72. {devops_bot_sdk-1.4.119 → devops_bot_sdk-1.4.120}/sdk/ui/runner.py +0 -0
  73. {devops_bot_sdk-1.4.119 → devops_bot_sdk-1.4.120}/sdk/ui/security.py +0 -0
  74. {devops_bot_sdk-1.4.119 → devops_bot_sdk-1.4.120}/sdk/ui/server.py +0 -0
  75. {devops_bot_sdk-1.4.119 → devops_bot_sdk-1.4.120}/sdk/ui/service.py +0 -0
  76. {devops_bot_sdk-1.4.119 → devops_bot_sdk-1.4.120}/sdk/ui/setup_manager.py +0 -0
  77. {devops_bot_sdk-1.4.119 → devops_bot_sdk-1.4.120}/sdk/ui/static/agents.html +0 -0
  78. {devops_bot_sdk-1.4.119 → devops_bot_sdk-1.4.120}/sdk/ui/static/app.js +0 -0
  79. {devops_bot_sdk-1.4.119 → devops_bot_sdk-1.4.120}/sdk/ui/static/custom-agents.html +0 -0
  80. {devops_bot_sdk-1.4.119 → devops_bot_sdk-1.4.120}/sdk/ui/static/default-agents.html +0 -0
  81. {devops_bot_sdk-1.4.119 → devops_bot_sdk-1.4.120}/sdk/ui/static/index.html +0 -0
  82. {devops_bot_sdk-1.4.119 → devops_bot_sdk-1.4.120}/sdk/ui/static/runs.html +0 -0
  83. {devops_bot_sdk-1.4.119 → devops_bot_sdk-1.4.120}/sdk/ui/static/setup.html +0 -0
  84. {devops_bot_sdk-1.4.119 → devops_bot_sdk-1.4.120}/sdk/ui/static/styles.css +0 -0
  85. {devops_bot_sdk-1.4.119 → devops_bot_sdk-1.4.120}/sdk/ui/static/tickets.html +0 -0
  86. {devops_bot_sdk-1.4.119 → devops_bot_sdk-1.4.120}/sdk/updater.py +0 -0
  87. {devops_bot_sdk-1.4.119 → devops_bot_sdk-1.4.120}/setup.cfg +0 -0
  88. {devops_bot_sdk-1.4.119 → devops_bot_sdk-1.4.120}/tests/test_agent_chat_features.py +0 -0
  89. {devops_bot_sdk-1.4.119 → devops_bot_sdk-1.4.120}/tests/test_agents_connectors_builder.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: devops-bot-sdk
3
- Version: 1.4.119
3
+ Version: 1.4.120
4
4
  Summary: DevOps Bot Desktop SDK — thin client for the AgentOS Electron desktop app
5
5
  Author: noumanaziz2128
6
6
  License-Expression: LicenseRef-Proprietary
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: devops-bot-sdk
3
- Version: 1.4.119
3
+ Version: 1.4.120
4
4
  Summary: DevOps Bot Desktop SDK — thin client for the AgentOS Electron desktop app
5
5
  Author: noumanaziz2128
6
6
  License-Expression: LicenseRef-Proprietary
@@ -1,6 +1,6 @@
1
1
  """AgentOS Desktop SDK — thin HTTPS/SSE client for the Electron app.
2
2
 
3
- Version: 1.4.119
3
+ Version: 1.4.120
4
4
 
5
5
  Public surface:
6
6
  BackendClient.from_config() — create client from ~/.agentos/config.toml
@@ -30,7 +30,7 @@ Rules:
30
30
  - All data egress through submit_webhook only
31
31
  """
32
32
 
33
- __version__ = "1.4.119" # SINGLE SOURCE OF TRUTH — bump on every change; pyproject,
33
+ __version__ = "1.4.120" # SINGLE SOURCE OF TRUTH — bump on every change; pyproject,
34
34
  __author__ = "AgentOS" # sdk.client.SDK_VERSION and the UI server version all read this.
35
35
 
36
36
  from sdk.client import BackendClient
@@ -190,6 +190,75 @@ def get_allowed_ips() -> list[str]:
190
190
  return out
191
191
 
192
192
 
193
+ class PermanentAllowlistEntry(ValueError):
194
+ """Raised when trying to disable an IP that is permanently whitelisted via ``.env``."""
195
+
196
+
197
+ def permanent_allowed_ips() -> list[str]:
198
+ """IPs/CIDRs whitelisted **permanently** — from ``.env`` (``~/.agentos/.env`` or
199
+ CWD) or the ``AGENTOS_UI_ALLOWED_IPS`` process env var.
200
+
201
+ These are the fixed baseline: they are ALWAYS allowed and **cannot be disabled**
202
+ through the API — change them by editing ``.env`` and restarting.
203
+ """
204
+ out: list[str] = []
205
+ for src in (_dotenv_value("AGENTOS_UI_ALLOWED_IPS"),
206
+ os.environ.get("AGENTOS_UI_ALLOWED_IPS", "")):
207
+ for ip in _parse_ip_list(src):
208
+ if ip not in out:
209
+ out.append(ip)
210
+ return out
211
+
212
+
213
+ def runtime_allowed_ips() -> list[str]:
214
+ """IPs/CIDRs whitelisted **at runtime** (config.toml ``allowed_ips``) — added by
215
+ the whitelist endpoint and removable by the disable endpoint. They stay in the
216
+ allowlist until disabled."""
217
+ return _parse_ip_list(_load_raw().get("allowed_ips", ""))
218
+
219
+
220
+ def add_allowed_ip(ip: str) -> list[str]:
221
+ """Whitelist an IP/CIDR at RUNTIME (persisted to config.toml ``allowed_ips``).
222
+
223
+ Idempotent. The middleware re-reads the allowlist per request, so the IP is
224
+ honoured immediately (no restart) and stays until disabled. An IP already
225
+ permanently whitelisted via ``.env`` is left as-is (no runtime duplicate).
226
+ Returns the merged allowlist.
227
+ """
228
+ ip = (ip or "").strip()
229
+ if not ip:
230
+ raise ValueError("ip is required")
231
+ if ip in permanent_allowed_ips(): # already permanent — nothing to add
232
+ return get_allowed_ips()
233
+ raw = _load_raw() # preserves token_enc / base_url / etc.
234
+ entries = _parse_ip_list(raw.get("allowed_ips", ""))
235
+ if ip not in entries:
236
+ entries.append(ip)
237
+ raw["allowed_ips"] = ", ".join(entries)
238
+ _save_raw(raw)
239
+ return get_allowed_ips()
240
+
241
+
242
+ def remove_allowed_ip(ip: str) -> list[str]:
243
+ """Disable a RUNTIME-whitelisted IP/CIDR (remove from config.toml ``allowed_ips``).
244
+
245
+ Raises :class:`PermanentAllowlistEntry` if the IP is permanently whitelisted via
246
+ ``.env`` — those are never removable through the API (edit ``.env`` instead).
247
+ Returns the merged allowlist.
248
+ """
249
+ ip = (ip or "").strip()
250
+ if ip in permanent_allowed_ips():
251
+ raise PermanentAllowlistEntry(
252
+ f"{ip} is permanently whitelisted via .env (AGENTOS_UI_ALLOWED_IPS) — "
253
+ "it cannot be disabled through the API; edit .env and restart to change it."
254
+ )
255
+ raw = _load_raw()
256
+ entries = [x for x in _parse_ip_list(raw.get("allowed_ips", "")) if x != ip]
257
+ raw["allowed_ips"] = ", ".join(entries)
258
+ _save_raw(raw)
259
+ return get_allowed_ips()
260
+
261
+
193
262
  def get_branch_name_template() -> str | None:
194
263
  """Org-wide branch-naming convention, if configured in config.toml.
195
264
 
@@ -59,6 +59,67 @@ async def status() -> dict:
59
59
  }
60
60
 
61
61
 
62
+ # ── IP allowlist management ─────────────────────────────────────────────────
63
+ # Whitelist a client IP for THIS SDK. A whitelisted IP passes the IP gate for
64
+ # EVERY path (all /api/ui/* endpoints + the UI + /docs); data endpoints still
65
+ # additionally require the co_ token. Persisted to config.toml `allowed_ips`; the
66
+ # middleware re-reads it per request so a new IP works immediately (no restart).
67
+ # The backend proxies here (POST /api/v1/sdk/whitelist-ip) to open access for an IP.
68
+
69
+ def _valid_ip_or_cidr(value: str) -> bool:
70
+ import ipaddress
71
+ try:
72
+ ipaddress.ip_network(value, strict=False)
73
+ return True
74
+ except ValueError:
75
+ return False
76
+
77
+
78
+ @router.get("/allowlist", dependencies=[_auth])
79
+ async def get_allowlist() -> dict:
80
+ """This SDK's allowlist, split into permanent vs runtime.
81
+
82
+ - ``permanent`` — from ``.env`` (AGENTOS_UI_ALLOWED_IPS); always allowed, not
83
+ disableable via the API.
84
+ - ``runtime`` — whitelisted on request; stay until disabled.
85
+ (loopback + this host's own IPs are always allowed, implicitly.)
86
+ """
87
+ from sdk.config import get_allowed_ips, permanent_allowed_ips, runtime_allowed_ips
88
+ return {
89
+ "allowed_ips": get_allowed_ips(),
90
+ "permanent": permanent_allowed_ips(),
91
+ "runtime": runtime_allowed_ips(),
92
+ }
93
+
94
+
95
+ @router.post("/allowlist", dependencies=[_auth])
96
+ async def add_to_allowlist(body: dict) -> dict:
97
+ """Whitelist an IP/CIDR for this SDK at runtime. Body: ``{ip}``. Idempotent.
98
+
99
+ Opens the IP gate for that client across every path (all endpoints + UI + docs)
100
+ until it's disabled. Effective immediately (the middleware re-reads per request).
101
+ """
102
+ from sdk.config import add_allowed_ip
103
+
104
+ ip = str(body.get("ip", "")).strip()
105
+ if not ip:
106
+ raise HTTPException(status_code=400, detail="ip is required.")
107
+ if not _valid_ip_or_cidr(ip):
108
+ raise HTTPException(status_code=400, detail=f"invalid IP or CIDR: {ip!r}")
109
+ return {"added": ip, "allowed_ips": add_allowed_ip(ip)}
110
+
111
+
112
+ @router.delete("/allowlist", dependencies=[_auth])
113
+ async def remove_from_allowlist(ip: str = Query(...)) -> dict:
114
+ """Disable a runtime-whitelisted IP/CIDR. Permanent (``.env``) entries → 400."""
115
+ from sdk.config import remove_allowed_ip, PermanentAllowlistEntry
116
+
117
+ try:
118
+ return {"removed": ip, "allowed_ips": remove_allowed_ip(ip)}
119
+ except PermanentAllowlistEntry as exc:
120
+ raise HTTPException(status_code=400, detail=str(exc))
121
+
122
+
62
123
  @router.post("/configure", dependencies=[_auth])
63
124
  async def configure(body: dict) -> dict:
64
125
  from sdk import config as sdk_config
@@ -71,18 +71,24 @@ class IPAllowlistMiddleware(BaseHTTPMiddleware):
71
71
 
72
72
  def __init__(self, app, allowed: list[str] | None = None) -> None:
73
73
  super().__init__(app)
74
- # Resolve the allowlist once at startup. Callers can pass an explicit list
75
- # (tests); otherwise we read env + config.toml.
76
- if allowed is None:
74
+ # `allowed` set explicitly (tests) fixed list. Otherwise the configured
75
+ # allowlist is read FRESH per request (see `_configured_nets`) so an IP
76
+ # whitelisted at runtime via /api/ui/allowlist takes effect immediately —
77
+ # no restart. This host's own IPs are static, so compute them once.
78
+ self._explicit = allowed
79
+ self._local_nets = [n for n in (_parse(a) for a in _local_ips()) if n is not None]
80
+ self._trust_xff = os.environ.get("AGENTOS_UI_TRUST_FORWARDED_FOR", "") in ("1", "true", "TRUE", "yes")
81
+
82
+ def _configured_nets(self) -> list:
83
+ if self._explicit is not None:
84
+ entries = self._explicit
85
+ else:
77
86
  try:
78
87
  from sdk.config import get_allowed_ips
79
- allowed = get_allowed_ips()
88
+ entries = get_allowed_ips()
80
89
  except Exception: # noqa: BLE001 — never let config break the server
81
- allowed = []
82
- # Configured allowlist + this host's own IPs, all as networks.
83
- entries = list(allowed) + list(_local_ips())
84
- self._networks = [n for n in (_parse(a) for a in entries) if n is not None]
85
- self._trust_xff = os.environ.get("AGENTOS_UI_TRUST_FORWARDED_FOR", "") in ("1", "true", "TRUE", "yes")
90
+ entries = []
91
+ return [n for n in (_parse(a) for a in entries) if n is not None]
86
92
 
87
93
  def _client_ip(self, request: Request) -> str:
88
94
  if self._trust_xff:
@@ -98,7 +104,7 @@ class IPAllowlistMiddleware(BaseHTTPMiddleware):
98
104
  return False
99
105
  if _loopback(ip):
100
106
  return True
101
- return any(ip in net for net in self._networks)
107
+ return any(ip in net for net in (self._local_nets + self._configured_nets()))
102
108
 
103
109
  async def dispatch(self, request: Request, call_next):
104
110
  # Guard EVERY path — nothing on this server is public.