collab-runtime 0.9.2__tar.gz → 0.9.3__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.
- {collab_runtime-0.9.2/collab_runtime.egg-info → collab_runtime-0.9.3}/PKG-INFO +1 -1
- {collab_runtime-0.9.2 → collab_runtime-0.9.3}/collab/dashboard_server.py +4 -2
- collab_runtime-0.9.3/collab/env_secrets.py +45 -0
- {collab_runtime-0.9.2 → collab_runtime-0.9.3}/collab/live_locks_watcher.py +31 -24
- {collab_runtime-0.9.2 → collab_runtime-0.9.3}/collab/lock_client.py +656 -35
- collab_runtime-0.9.3/collab/path_filter.py +174 -0
- {collab_runtime-0.9.2 → collab_runtime-0.9.3}/collab/safe_subprocess.py +10 -1
- {collab_runtime-0.9.2 → collab_runtime-0.9.3/collab_runtime.egg-info}/PKG-INFO +1 -1
- {collab_runtime-0.9.2 → collab_runtime-0.9.3}/collab_runtime.egg-info/SOURCES.txt +2 -0
- {collab_runtime-0.9.2 → collab_runtime-0.9.3}/pyproject.toml +1 -1
- {collab_runtime-0.9.2 → collab_runtime-0.9.3}/LICENSE +0 -0
- {collab_runtime-0.9.2 → collab_runtime-0.9.3}/README.md +0 -0
- {collab_runtime-0.9.2 → collab_runtime-0.9.3}/collab/__init__.py +0 -0
- {collab_runtime-0.9.2 → collab_runtime-0.9.3}/collab/__main__.py +0 -0
- {collab_runtime-0.9.2 → collab_runtime-0.9.3}/collab/agent_hooks.py +0 -0
- {collab_runtime-0.9.2 → collab_runtime-0.9.3}/collab/agent_identity.py +0 -0
- {collab_runtime-0.9.2 → collab_runtime-0.9.3}/collab/dashboard/dashboard-charts.js +0 -0
- {collab_runtime-0.9.2 → collab_runtime-0.9.3}/collab/dashboard/dashboard-filters.js +0 -0
- {collab_runtime-0.9.2 → collab_runtime-0.9.3}/collab/dashboard/dashboard-format.js +0 -0
- {collab_runtime-0.9.2 → collab_runtime-0.9.3}/collab/dashboard/index.html +0 -0
- {collab_runtime-0.9.2 → collab_runtime-0.9.3}/collab/errors.py +0 -0
- {collab_runtime-0.9.2 → collab_runtime-0.9.3}/collab/githooks.py +0 -0
- {collab_runtime-0.9.2 → collab_runtime-0.9.3}/collab/hook_templates/commit-msg +0 -0
- {collab_runtime-0.9.2 → collab_runtime-0.9.3}/collab/hook_templates/post-checkout +0 -0
- {collab_runtime-0.9.2 → collab_runtime-0.9.3}/collab/hook_templates/post-commit +0 -0
- {collab_runtime-0.9.2 → collab_runtime-0.9.3}/collab/hook_templates/post-merge +0 -0
- {collab_runtime-0.9.2 → collab_runtime-0.9.3}/collab/hook_templates/pre-commit +0 -0
- {collab_runtime-0.9.2 → collab_runtime-0.9.3}/collab/hook_templates/pre-push +0 -0
- {collab_runtime-0.9.2 → collab_runtime-0.9.3}/collab/logging_config.py +0 -0
- {collab_runtime-0.9.2 → collab_runtime-0.9.3}/collab/main.py +0 -0
- {collab_runtime-0.9.2 → collab_runtime-0.9.3}/collab/overlap.py +0 -0
- {collab_runtime-0.9.2 → collab_runtime-0.9.3}/collab/platform_probe.py +0 -0
- {collab_runtime-0.9.2 → collab_runtime-0.9.3}/collab/pr_overlap.py +0 -0
- {collab_runtime-0.9.2 → collab_runtime-0.9.3}/collab/subprocess_bridge.py +0 -0
- {collab_runtime-0.9.2 → collab_runtime-0.9.3}/collab_runtime.egg-info/dependency_links.txt +0 -0
- {collab_runtime-0.9.2 → collab_runtime-0.9.3}/collab_runtime.egg-info/entry_points.txt +0 -0
- {collab_runtime-0.9.2 → collab_runtime-0.9.3}/collab_runtime.egg-info/requires.txt +0 -0
- {collab_runtime-0.9.2 → collab_runtime-0.9.3}/collab_runtime.egg-info/top_level.txt +0 -0
- {collab_runtime-0.9.2 → collab_runtime-0.9.3}/docs/pypi/README.md +0 -0
- {collab_runtime-0.9.2 → collab_runtime-0.9.3}/setup.cfg +0 -0
|
@@ -230,9 +230,11 @@ def load_runtime_supabase_config(project_root: str) -> dict[str, Any]:
|
|
|
230
230
|
val = os.getenv(name)
|
|
231
231
|
return val
|
|
232
232
|
|
|
233
|
+
from .env_secrets import effective_anon_key, effective_service_role_key
|
|
234
|
+
|
|
233
235
|
url = pick("SUPABASE_URL") or ""
|
|
234
|
-
anon = pick("SUPABASE_ANON_KEY") or ""
|
|
235
|
-
service = pick("SUPABASE_SERVICE_ROLE_KEY")
|
|
236
|
+
anon = effective_anon_key(pick("SUPABASE_ANON_KEY")) or ""
|
|
237
|
+
service = effective_service_role_key(pick("SUPABASE_SERVICE_ROLE_KEY"))
|
|
236
238
|
user = _resolve_developer_name(project_root, file_vals)
|
|
237
239
|
from . import agent_identity
|
|
238
240
|
|
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
"""Resolve effective Supabase credentials from environment variables."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
import os
|
|
6
|
+
import re
|
|
7
|
+
from typing import Optional
|
|
8
|
+
|
|
9
|
+
# Placeholder patterns copied from scripts/setup.ps1 (Test-IsPlaceholderValue).
|
|
10
|
+
_PLACEHOLDER_ENV_RE = re.compile(
|
|
11
|
+
r"^(?:your[_-]|your[_-]?project|example|CHANGE_ME|change[_-]?me|"
|
|
12
|
+
r"replace[_-]?me|TODO|<team-)",
|
|
13
|
+
re.IGNORECASE,
|
|
14
|
+
)
|
|
15
|
+
|
|
16
|
+
|
|
17
|
+
def is_placeholder_env_value(value: Optional[str]) -> bool:
|
|
18
|
+
"""Return True when *value* is unset or still a template placeholder."""
|
|
19
|
+
if value is None:
|
|
20
|
+
return True
|
|
21
|
+
stripped = value.strip()
|
|
22
|
+
if not stripped:
|
|
23
|
+
return True
|
|
24
|
+
return bool(_PLACEHOLDER_ENV_RE.match(stripped))
|
|
25
|
+
|
|
26
|
+
|
|
27
|
+
def effective_env_secret(value: Optional[str]) -> Optional[str]:
|
|
28
|
+
"""Return a usable secret or None when *value* is blank/placeholder."""
|
|
29
|
+
if is_placeholder_env_value(value):
|
|
30
|
+
return None
|
|
31
|
+
return value.strip() if value else None
|
|
32
|
+
|
|
33
|
+
|
|
34
|
+
def effective_service_role_key(raw: Optional[str] = None) -> Optional[str]:
|
|
35
|
+
"""Service role key for API calls, or None when placeholder/unset."""
|
|
36
|
+
if raw is None:
|
|
37
|
+
raw = os.getenv("SUPABASE_SERVICE_ROLE_KEY")
|
|
38
|
+
return effective_env_secret(raw)
|
|
39
|
+
|
|
40
|
+
|
|
41
|
+
def effective_anon_key(raw: Optional[str] = None) -> Optional[str]:
|
|
42
|
+
"""Anon key for API calls, or None when placeholder/unset."""
|
|
43
|
+
if raw is None:
|
|
44
|
+
raw = os.getenv("SUPABASE_ANON_KEY")
|
|
45
|
+
return effective_env_secret(raw)
|
|
@@ -28,7 +28,8 @@ from typing import Any, Callable, Optional, Protocol, cast
|
|
|
28
28
|
|
|
29
29
|
from dotenv import load_dotenv
|
|
30
30
|
|
|
31
|
-
from . import agent_identity, platform_probe, safe_subprocess
|
|
31
|
+
from . import agent_identity, path_filter, platform_probe, safe_subprocess
|
|
32
|
+
from .env_secrets import effective_anon_key, effective_service_role_key
|
|
32
33
|
|
|
33
34
|
# NOTE: do NOT import collab-local modules before the runtime root and sys.path
|
|
34
35
|
# setup is complete. The import for `logging_config` is moved
|
|
@@ -487,21 +488,13 @@ def _min_auto_lock_hold_seconds() -> int:
|
|
|
487
488
|
|
|
488
489
|
|
|
489
490
|
def _should_ignore_path(path: str) -> bool:
|
|
490
|
-
"""Return True for paths the watcher should skip.
|
|
491
|
-
|
|
492
|
-
|
|
493
|
-
|
|
494
|
-
|
|
495
|
-
|
|
496
|
-
|
|
497
|
-
norm == "instance"
|
|
498
|
-
or norm.startswith("instance/")
|
|
499
|
-
or norm.endswith("/instance")
|
|
500
|
-
or "/instance/" in norm
|
|
501
|
-
):
|
|
502
|
-
return True
|
|
503
|
-
# Do not ignore runtime-relative project paths here.
|
|
504
|
-
return False
|
|
491
|
+
"""Return True for paths the watcher should skip.
|
|
492
|
+
|
|
493
|
+
Delegates to :func:`collab.path_filter.should_ignore_lock_path`, which also
|
|
494
|
+
honors ``COLLAB_LOCK_IGNORE`` and a project ``.collabignore`` so transient
|
|
495
|
+
scratch files never produce short-lived locks (#170).
|
|
496
|
+
"""
|
|
497
|
+
return path_filter.should_ignore_lock_path(path, _PROJECT_ROOT)
|
|
505
498
|
|
|
506
499
|
|
|
507
500
|
def _color(text: str, color: str) -> str:
|
|
@@ -734,6 +727,19 @@ def _process_new_files(client, branch: str, new_files: set[str]) -> None:
|
|
|
734
727
|
data[0].get("agent_label"),
|
|
735
728
|
data[0].get("agent_kind"),
|
|
736
729
|
)
|
|
730
|
+
# #172: a conflict against your OWN developer id is never a
|
|
731
|
+
# cross-developer merge risk. Under sticky attribution (#169) this
|
|
732
|
+
# only happens for same-developer CROSS-AGENT edits, which the
|
|
733
|
+
# per-poll watcher would otherwise spam as WARNING + desktop
|
|
734
|
+
# notification. Log it once at DEBUG and skip the notification.
|
|
735
|
+
if str(owner) == DEVELOPER_ID:
|
|
736
|
+
logger.debug(
|
|
737
|
+
"Self-lock on %s held by %s (same developer) — "
|
|
738
|
+
"no cross-developer conflict; not renewing this poll.",
|
|
739
|
+
fp,
|
|
740
|
+
owner_display,
|
|
741
|
+
)
|
|
742
|
+
continue
|
|
737
743
|
_active_conflicts.add(fp)
|
|
738
744
|
msg = (
|
|
739
745
|
f"CONFLICT: {fp} is locked by {owner_display} -- "
|
|
@@ -901,8 +907,8 @@ def _start_dashboard_server() -> str | None:
|
|
|
901
907
|
|
|
902
908
|
injected = {
|
|
903
909
|
"url": SUPABASE_URL or "",
|
|
904
|
-
"anonKey": SUPABASE_ANON_KEY or "",
|
|
905
|
-
"serviceKey": SUPABASE_SERVICE_ROLE_KEY
|
|
910
|
+
"anonKey": effective_anon_key(SUPABASE_ANON_KEY) or "",
|
|
911
|
+
"serviceKey": effective_service_role_key(SUPABASE_SERVICE_ROLE_KEY),
|
|
906
912
|
"user": DEVELOPER_ID or "",
|
|
907
913
|
}
|
|
908
914
|
url, _html_path = prepare_dashboard_server(
|
|
@@ -1441,11 +1447,10 @@ def _graceful_shutdown() -> None:
|
|
|
1441
1447
|
_shutdown_done = True
|
|
1442
1448
|
|
|
1443
1449
|
dev_id = DEVELOPER_ID
|
|
1444
|
-
|
|
1450
|
+
anon_key = effective_anon_key(SUPABASE_ANON_KEY)
|
|
1451
|
+
if dev_id and SUPABASE_URL and anon_key and create_client is not None:
|
|
1445
1452
|
try:
|
|
1446
|
-
client = cast(Callable[..., Any], create_client)(
|
|
1447
|
-
SUPABASE_URL, SUPABASE_ANON_KEY
|
|
1448
|
-
)
|
|
1453
|
+
client = cast(Callable[..., Any], create_client)(SUPABASE_URL, anon_key)
|
|
1449
1454
|
|
|
1450
1455
|
# Determine which files are still in progress
|
|
1451
1456
|
# (dirty OR committed-but-unpushed)
|
|
@@ -1913,7 +1918,7 @@ def main() -> None:
|
|
|
1913
1918
|
)
|
|
1914
1919
|
args = parser.parse_args()
|
|
1915
1920
|
|
|
1916
|
-
if not SUPABASE_URL or not SUPABASE_ANON_KEY:
|
|
1921
|
+
if not SUPABASE_URL or not effective_anon_key(SUPABASE_ANON_KEY):
|
|
1917
1922
|
logger.error(
|
|
1918
1923
|
"Missing SUPABASE_URL or SUPABASE_ANON_KEY in .env.\n"
|
|
1919
1924
|
"See .env.example for setup."
|
|
@@ -2047,7 +2052,9 @@ def main() -> None:
|
|
|
2047
2052
|
sys.exit(1)
|
|
2048
2053
|
# static analyzers may still treat create_client as Optional; cast for
|
|
2049
2054
|
# their sake so they recognize the value is callable beyond this point.
|
|
2050
|
-
client = cast(Callable[..., Any], create_client)(
|
|
2055
|
+
client = cast(Callable[..., Any], create_client)(
|
|
2056
|
+
SUPABASE_URL, effective_anon_key(SUPABASE_ANON_KEY)
|
|
2057
|
+
)
|
|
2051
2058
|
|
|
2052
2059
|
# Start local dashboard server for a clickable URL
|
|
2053
2060
|
dashboard_url = _start_dashboard_server()
|