collab-runtime 0.6.2__tar.gz → 0.7.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 (32) hide show
  1. {collab_runtime-0.6.2/collab_runtime.egg-info → collab_runtime-0.7.0}/PKG-INFO +1 -1
  2. {collab_runtime-0.6.2 → collab_runtime-0.7.0}/collab/main.py +197 -0
  3. {collab_runtime-0.6.2 → collab_runtime-0.7.0/collab_runtime.egg-info}/PKG-INFO +1 -1
  4. {collab_runtime-0.6.2 → collab_runtime-0.7.0}/pyproject.toml +1 -1
  5. {collab_runtime-0.6.2 → collab_runtime-0.7.0}/LICENSE +0 -0
  6. {collab_runtime-0.6.2 → collab_runtime-0.7.0}/README.md +0 -0
  7. {collab_runtime-0.6.2 → collab_runtime-0.7.0}/collab/__init__.py +0 -0
  8. {collab_runtime-0.6.2 → collab_runtime-0.7.0}/collab/__main__.py +0 -0
  9. {collab_runtime-0.6.2 → collab_runtime-0.7.0}/collab/agent_hooks.py +0 -0
  10. {collab_runtime-0.6.2 → collab_runtime-0.7.0}/collab/agent_identity.py +0 -0
  11. {collab_runtime-0.6.2 → collab_runtime-0.7.0}/collab/dashboard/dashboard-format.js +0 -0
  12. {collab_runtime-0.6.2 → collab_runtime-0.7.0}/collab/dashboard/index.html +0 -0
  13. {collab_runtime-0.6.2 → collab_runtime-0.7.0}/collab/dashboard_server.py +0 -0
  14. {collab_runtime-0.6.2 → collab_runtime-0.7.0}/collab/errors.py +0 -0
  15. {collab_runtime-0.6.2 → collab_runtime-0.7.0}/collab/githooks.py +0 -0
  16. {collab_runtime-0.6.2 → collab_runtime-0.7.0}/collab/hook_templates/commit-msg +0 -0
  17. {collab_runtime-0.6.2 → collab_runtime-0.7.0}/collab/hook_templates/post-commit +0 -0
  18. {collab_runtime-0.6.2 → collab_runtime-0.7.0}/collab/hook_templates/pre-commit +0 -0
  19. {collab_runtime-0.6.2 → collab_runtime-0.7.0}/collab/hook_templates/pre-push +0 -0
  20. {collab_runtime-0.6.2 → collab_runtime-0.7.0}/collab/live_locks_watcher.py +0 -0
  21. {collab_runtime-0.6.2 → collab_runtime-0.7.0}/collab/lock_client.py +0 -0
  22. {collab_runtime-0.6.2 → collab_runtime-0.7.0}/collab/logging_config.py +0 -0
  23. {collab_runtime-0.6.2 → collab_runtime-0.7.0}/collab/platform_probe.py +0 -0
  24. {collab_runtime-0.6.2 → collab_runtime-0.7.0}/collab/safe_subprocess.py +0 -0
  25. {collab_runtime-0.6.2 → collab_runtime-0.7.0}/collab/subprocess_bridge.py +0 -0
  26. {collab_runtime-0.6.2 → collab_runtime-0.7.0}/collab_runtime.egg-info/SOURCES.txt +0 -0
  27. {collab_runtime-0.6.2 → collab_runtime-0.7.0}/collab_runtime.egg-info/dependency_links.txt +0 -0
  28. {collab_runtime-0.6.2 → collab_runtime-0.7.0}/collab_runtime.egg-info/entry_points.txt +0 -0
  29. {collab_runtime-0.6.2 → collab_runtime-0.7.0}/collab_runtime.egg-info/requires.txt +0 -0
  30. {collab_runtime-0.6.2 → collab_runtime-0.7.0}/collab_runtime.egg-info/top_level.txt +0 -0
  31. {collab_runtime-0.6.2 → collab_runtime-0.7.0}/docs/pypi/README.md +0 -0
  32. {collab_runtime-0.6.2 → collab_runtime-0.7.0}/setup.cfg +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: collab-runtime
3
- Version: 0.6.2
3
+ Version: 0.7.0
4
4
  Summary: Collaborative file locking runtime
5
5
  Author-email: KirilMT <kiril.mt95@gmail.com>
6
6
  License-Expression: MIT
@@ -15,6 +15,8 @@ import traceback as _tb
15
15
  from argparse import ArgumentParser
16
16
  from contextlib import nullcontext
17
17
 
18
+ from . import __version__
19
+
18
20
  logger = logging.getLogger("collab.lock_client")
19
21
 
20
22
 
@@ -109,6 +111,11 @@ def _run_cli() -> None:
109
111
  description="Collaborative File Lock Manager (Supabase)",
110
112
  parents=[common],
111
113
  )
114
+ parser.add_argument(
115
+ "--version",
116
+ action="version",
117
+ version=f"collab-runtime {__version__}",
118
+ )
112
119
  sub = parser.add_subparsers(dest="command")
113
120
 
114
121
  # acquire
@@ -200,6 +207,20 @@ def _run_cli() -> None:
200
207
  # daemon-status
201
208
  sub.add_parser("daemon-status", help="Check watcher daemon status")
202
209
 
210
+ # restart
211
+ rst = sub.add_parser(
212
+ "restart",
213
+ help="Restart the watcher daemon (stop + start)",
214
+ )
215
+ rst.add_argument("--interval", type=int, default=5, help="Poll interval (seconds)")
216
+ rst.add_argument(
217
+ "--timeout",
218
+ type=int,
219
+ default=0,
220
+ help="Idle timeout in minutes (0 = disabled)",
221
+ )
222
+ rst.add_argument("--open-dashboard", action="store_true")
223
+
203
224
  # cleanup - kill orphaned processes
204
225
  sub.add_parser(
205
226
  "cleanup", help="Kill all orphaned lock_client processes (preserves locks)"
@@ -246,6 +267,27 @@ def _run_cli() -> None:
246
267
  hpr = sub.add_parser("history-prune", help="Delete lock history older than N days")
247
268
  hpr.add_argument("--days", type=int, default=30, help="Retention window in days")
248
269
 
270
+ # ping
271
+ sub.add_parser("ping", help="Check Supabase connectivity")
272
+
273
+ # info
274
+ sub.add_parser("info", help="Show comprehensive status overview")
275
+
276
+ # logs
277
+ lg = sub.add_parser("logs", help="Show recent collab log entries")
278
+ lg.add_argument(
279
+ "--lines",
280
+ type=int,
281
+ default=50,
282
+ help="Number of recent log lines to display (default: 50)",
283
+ )
284
+ lg.add_argument(
285
+ "--follow",
286
+ "-f",
287
+ action="store_true",
288
+ help="Follow the log file (tail -f mode, Ctrl+C to stop)",
289
+ )
290
+
249
291
  # watch (internal, called by daemon-start)
250
292
  wp = sub.add_parser("watch", help="Run watcher in foreground")
251
293
  wp.add_argument("--interval", type=int, default=5)
@@ -374,6 +416,9 @@ def _run_cli() -> None:
374
416
  "history",
375
417
  "history-prune",
376
418
  "whoami",
419
+ "ping",
420
+ "info",
421
+ "logs",
377
422
  }
378
423
  quiet_ctx = (
379
424
  _quiet_console_loggers() if args.command in quiet_commands else nullcontext()
@@ -540,6 +585,20 @@ def _run_cli() -> None:
540
585
  running = client.daemon_status()
541
586
  sys.exit(0 if running else 1)
542
587
 
588
+ elif args.command == "restart":
589
+ open_flag = getattr(args, "open_dashboard", False)
590
+ auto_env = os.getenv("AUTO_OPEN_DASHBOARD", "0").lower() in (
591
+ "1",
592
+ "true",
593
+ "yes",
594
+ )
595
+ client.daemon_stop()
596
+ client.daemon_start(
597
+ interval=getattr(args, "interval", 5),
598
+ timeout_mins=getattr(args, "timeout", 0),
599
+ open_dashboard=(open_flag or auto_env),
600
+ )
601
+
543
602
  elif args.command == "cleanup":
544
603
  client.cleanup_orphaned_processes()
545
604
 
@@ -599,6 +658,144 @@ def _run_cli() -> None:
599
658
  print(f"✗ Failed to prune lock history: {msg}")
600
659
  sys.exit(1)
601
660
 
661
+ elif args.command == "ping":
662
+ import time as _t
663
+ from socket import create_connection as _connect
664
+ from urllib.parse import urlparse as _urlparse
665
+
666
+ url = os.getenv("SUPABASE_URL", "")
667
+ if not url:
668
+ print("✗ SUPABASE_URL is not configured (check .env)")
669
+ sys.exit(1)
670
+
671
+ try:
672
+ host = _urlparse(url).hostname or ""
673
+ except Exception:
674
+ print(f"✗ Invalid SUPABASE_URL: {url!r}")
675
+ sys.exit(1)
676
+
677
+ if not host:
678
+ print(f"✗ Could not parse hostname from SUPABASE_URL: {url!r}")
679
+ sys.exit(1)
680
+
681
+ try:
682
+ start = _t.monotonic()
683
+ with _connect((host, 443), timeout=5.0):
684
+ pass
685
+ elapsed = _t.monotonic() - start
686
+ print(f"✓ Supabase is reachable " f"({host}, {elapsed*1000:.0f}ms)")
687
+ sys.exit(0)
688
+ except OSError as exc:
689
+ print(f"✗ Cannot reach {host}: {exc}")
690
+ print(" Verify network/VPN and that the Supabase project is active.")
691
+ sys.exit(1)
692
+
693
+ elif args.command == "info":
694
+ # Gather daemon status (suppress daemon_status()'s own print output)
695
+ import io as _io
696
+
697
+ from . import __version__ as _ver
698
+ from . import agent_identity
699
+ from .lock_client import _COLLAB_ROOT as _root
700
+ from .lock_client import _PROJECT_ROOT as _p_root
701
+
702
+ try:
703
+ _old_stdout = sys.stdout
704
+ sys.stdout = _io.StringIO()
705
+ try:
706
+ daemon_running = client.daemon_status()
707
+ finally:
708
+ sys.stdout = _old_stdout
709
+ except Exception:
710
+ daemon_running = False
711
+ daemon_state = "RUNNING" if daemon_running else "stopped"
712
+
713
+ # Count active locks
714
+ lock_count: int | str
715
+ my_locks: int | str
716
+ try:
717
+ active_locks = client.active()
718
+ lock_count = len(active_locks)
719
+ my_locks = sum(1 for lk in active_locks if client._lock_owned_by_me(lk))
720
+ except Exception:
721
+ lock_count = "?"
722
+ my_locks = "?"
723
+
724
+ # Identity summary
725
+ ident = agent_identity.identity_summary(
726
+ client.developer_id,
727
+ client.agent_id,
728
+ client.agent_label,
729
+ getattr(client, "agent_kind", None),
730
+ )
731
+
732
+ state_dir = os.path.join(_root, ".collab")
733
+
734
+ print(f"collab-runtime v{_ver}")
735
+ print()
736
+ print("── Daemon ──")
737
+ print(f" Status: {daemon_state}")
738
+ print(f" PID file: {os.path.join(state_dir, '.daemon.pid')}")
739
+ print()
740
+ print("── Identity ──")
741
+ print(f" Developer: {ident['developer_id']}")
742
+ print(f" Mode: {ident['mode']}")
743
+ if ident.get("agent_id"):
744
+ print(f" Agent ID: {ident['agent_id']}")
745
+ if ident.get("agent_label"):
746
+ print(f" Agent label: {ident['agent_label']}")
747
+ if ident.get("agent_kind"):
748
+ print(f" Agent kind: {ident['agent_kind']}")
749
+ print()
750
+ print("── Locks ──")
751
+ print(f" Active: {lock_count}")
752
+ print(f" Mine: {my_locks}")
753
+ print(f" Admin: {'yes' if client.is_admin else 'no'}")
754
+ print()
755
+ print("── Paths ──")
756
+ print(f" Runtime: {_root}")
757
+ print(f" Project: {_p_root}")
758
+ print(f" State dir: {state_dir}")
759
+ print(f" Log file: {os.path.join(_root, 'logs', 'collab.log')}")
760
+ sys.exit(0)
761
+
762
+ elif args.command == "logs":
763
+ from .lock_client import _COLLAB_ROOT as _root
764
+
765
+ log_path = os.path.join(_root, "logs", "collab.log")
766
+ lines = int(getattr(args, "lines", 50))
767
+ follow = getattr(args, "follow", False)
768
+
769
+ if not os.path.isfile(log_path):
770
+ print(f"No log file found at {log_path}")
771
+ sys.exit(1)
772
+
773
+ if follow:
774
+ # tail -f mode
775
+ print(f"Following {log_path} (Ctrl+C to stop)...")
776
+ try:
777
+ with open(log_path, "r", encoding="utf-8", errors="replace") as fh:
778
+ fh.seek(0, 2) # seek to end
779
+ while True:
780
+ line = fh.readline()
781
+ if line:
782
+ print(line, end="")
783
+ else:
784
+ time.sleep(0.25)
785
+ except KeyboardInterrupt:
786
+ print()
787
+ sys.exit(0)
788
+ else:
789
+ try:
790
+ with open(log_path, "r", encoding="utf-8", errors="replace") as fh:
791
+ all_lines = fh.readlines()
792
+ for line in all_lines[-lines:]:
793
+ print(line, end="")
794
+ except OSError as exc:
795
+ print(f"✗ Cannot read log file: {exc}")
796
+ sys.exit(1)
797
+ sys.exit(0)
798
+
602
799
  elif args.command == "watch":
603
800
  # Ensure watcher child process uses the explicit PID namespace passed by
604
801
  # daemon-start. This prevents cross-instance status/stop interference.
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: collab-runtime
3
- Version: 0.6.2
3
+ Version: 0.7.0
4
4
  Summary: Collaborative file locking runtime
5
5
  Author-email: KirilMT <kiril.mt95@gmail.com>
6
6
  License-Expression: MIT
@@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"
4
4
 
5
5
  [project]
6
6
  name = "collab-runtime"
7
- version = "0.6.2"
7
+ version = "0.7.0"
8
8
  description = "Collaborative file locking runtime"
9
9
  readme = "docs/pypi/README.md"
10
10
  license = "MIT"
File without changes
File without changes
File without changes