promty-collector 0.1.1 → 0.1.2

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.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "promty-collector",
3
- "version": "0.1.1",
3
+ "version": "0.1.2",
4
4
  "description": "Promty local event collector",
5
5
  "license": "UNLICENSED",
6
6
  "repository": {
package/src/cli.py CHANGED
@@ -41,11 +41,13 @@ from payloads import (
41
41
  WORKSPACE_KEYS,
42
42
  get_first_string,
43
43
  )
44
- from runtime_install import install_runtime, quote_command_path
44
+ from runtime_install import install_runtime, launcher_path, quote_command_path
45
45
  from sequence import SequenceStore
46
46
  from session_index import SessionIndex
47
47
  from uploader.client import PromptHubUploader
48
48
  from uploader.queue import JSONLQueue
49
+ from updater import auto_update
50
+ from version import COLLECTOR_VERSION
49
51
 
50
52
  InstallTarget = Literal["all", "claude-code", "codex-cli"]
51
53
  Profile = Literal["dev", "prod"]
@@ -61,6 +63,7 @@ PROFILE_URLS: dict[Profile, tuple[str, str]] = {
61
63
  "dev": ("http://127.0.0.1:5173", "http://127.0.0.1:8011"),
62
64
  "prod": ("https://promty.org", "https://api.promty.org"),
63
65
  }
66
+ AUTO_UPDATE_INTERVAL_SECONDS = 6 * 60 * 60
64
67
  CODEX_HOOKS: tuple[dict[str, Any], ...] = (
65
68
  {
66
69
  "event": "UserPromptSubmit",
@@ -374,8 +377,20 @@ def upload(args: argparse.Namespace) -> int:
374
377
  f"{JSONLQueue(args.queue_path).path} -> {api_url} "
375
378
  f"every {interval:g}s"
376
379
  )
380
+ next_update_check = 0.0
377
381
  while True:
378
382
  try:
383
+ now = time.monotonic()
384
+ if not args.no_auto_update and now >= next_update_check:
385
+ next_update_check = now + AUTO_UPDATE_INTERVAL_SECONDS
386
+ updated_version = auto_update()
387
+ if updated_version:
388
+ print(
389
+ f"Promty updated {COLLECTOR_VERSION} -> {updated_version}; restarting",
390
+ flush=True,
391
+ )
392
+ launcher = launcher_path()
393
+ os.execv(str(launcher), [str(launcher), *sys.argv[1:]])
379
394
  uploaded_count = _upload_queued_events(args)
380
395
  if uploaded_count:
381
396
  print(f"Uploaded {uploaded_count} events", flush=True)
@@ -795,6 +810,8 @@ def start_uploader(args: argparse.Namespace) -> int:
795
810
  ]
796
811
  if getattr(args, "queue_path", None):
797
812
  command.extend(["--queue-path", str(Path(args.queue_path).expanduser())])
813
+ if getattr(args, "no_auto_update", False):
814
+ command.append("--no-auto-update")
798
815
  process = subprocess.Popen(
799
816
  command,
800
817
  stdout=log_file,
@@ -960,6 +977,7 @@ def init(args: argparse.Namespace) -> int:
960
977
  log_path=args.log_path,
961
978
  pid_path=args.pid_path,
962
979
  queue_path=args.queue_path,
980
+ no_auto_update=args.no_auto_update,
963
981
  token=args.token,
964
982
  )
965
983
  start_uploader(uploader_args)
@@ -1038,6 +1056,7 @@ def build_parser() -> argparse.ArgumentParser:
1038
1056
  upload_parser.add_argument("--config-path")
1039
1057
  upload_parser.add_argument("--queue-path")
1040
1058
  upload_parser.add_argument("--profile", choices=sorted(PROFILE_URLS))
1059
+ upload_parser.add_argument("--no-auto-update", action="store_true")
1041
1060
  upload_parser.add_argument("--limit", type=int, default=100)
1042
1061
  upload_parser.add_argument("--timeout", type=float, default=10)
1043
1062
  upload_parser.add_argument(
@@ -1094,6 +1113,7 @@ def build_parser() -> argparse.ArgumentParser:
1094
1113
  start_uploader_parser.add_argument("--pid-path")
1095
1114
  start_uploader_parser.add_argument("--profile", choices=sorted(PROFILE_URLS))
1096
1115
  start_uploader_parser.add_argument("--queue-path")
1116
+ start_uploader_parser.add_argument("--no-auto-update", action="store_true")
1097
1117
  start_uploader_parser.add_argument("--token")
1098
1118
  start_uploader_parser.set_defaults(func=start_uploader)
1099
1119
 
@@ -1125,6 +1145,7 @@ def build_parser() -> argparse.ArgumentParser:
1125
1145
  init_parser.add_argument("--pid-path")
1126
1146
  init_parser.add_argument("--profile", choices=sorted(PROFILE_URLS))
1127
1147
  init_parser.add_argument("--queue-path")
1148
+ init_parser.add_argument("--no-auto-update", action="store_true")
1128
1149
  init_parser.add_argument("--repo-root")
1129
1150
  init_parser.add_argument("--skip-login", action="store_true")
1130
1151
  init_parser.add_argument("--skip-uploader", action="store_true")
@@ -1139,9 +1160,18 @@ def build_parser() -> argparse.ArgumentParser:
1139
1160
  init_parser.add_argument("--upload-interval", type=float, default=2)
1140
1161
  init_parser.set_defaults(func=init)
1141
1162
 
1163
+ update_runtime_parser = subparsers.add_parser("update-runtime")
1164
+ update_runtime_parser.set_defaults(func=lambda _: _update_runtime())
1165
+
1142
1166
  return parser
1143
1167
 
1144
1168
 
1169
+ def _update_runtime() -> int:
1170
+ path = install_runtime()
1171
+ print(f"Promty {COLLECTOR_VERSION} runtime ready: {path}")
1172
+ return 0
1173
+
1174
+
1145
1175
  def main(argv: Sequence[str] | None = None) -> int:
1146
1176
  parser = build_parser()
1147
1177
  args = parser.parse_args(argv)
@@ -25,6 +25,8 @@ RUNTIME_MODULES: tuple[str, ...] = (
25
25
  "runtime_install.py",
26
26
  "sequence.py",
27
27
  "session_index.py",
28
+ "updater.py",
29
+ "version.py",
28
30
  )
29
31
  RUNTIME_PACKAGES: tuple[str, ...] = ("adapters", "uploader")
30
32
 
package/src/updater.py ADDED
@@ -0,0 +1,57 @@
1
+ from __future__ import annotations
2
+
3
+ import json
4
+ import shutil
5
+ import subprocess
6
+ from urllib import request
7
+
8
+ from version import COLLECTOR_VERSION
9
+
10
+
11
+ NPM_PACKAGE = "promty-collector"
12
+ NPM_REGISTRY_URL = f"https://registry.npmjs.org/{NPM_PACKAGE}/latest"
13
+
14
+
15
+ def _version_tuple(value: str) -> tuple[int, ...]:
16
+ core = value.strip().split("-", 1)[0]
17
+ try:
18
+ return tuple(int(part) for part in core.split("."))
19
+ except ValueError:
20
+ return ()
21
+
22
+
23
+ def latest_version(timeout: float = 5) -> str | None:
24
+ with request.urlopen(NPM_REGISTRY_URL, timeout=timeout) as response:
25
+ payload = json.loads(response.read().decode("utf-8"))
26
+ version = payload.get("version")
27
+ return version.strip() if isinstance(version, str) and version.strip() else None
28
+
29
+
30
+ def update_available(current: str, latest: str) -> bool:
31
+ current_parts = _version_tuple(current)
32
+ latest_parts = _version_tuple(latest)
33
+ return bool(current_parts and latest_parts and latest_parts > current_parts)
34
+
35
+
36
+ def install_latest(version: str) -> bool:
37
+ npx = shutil.which("npx")
38
+ if npx is None:
39
+ return False
40
+ result = subprocess.run(
41
+ [npx, "--yes", f"{NPM_PACKAGE}@{version}", "update-runtime"],
42
+ check=False,
43
+ capture_output=True,
44
+ text=True,
45
+ timeout=120,
46
+ )
47
+ if result.returncode != 0:
48
+ message = result.stderr.strip() or result.stdout.strip() or "unknown error"
49
+ raise RuntimeError(f"automatic update failed: {message}")
50
+ return True
51
+
52
+
53
+ def auto_update() -> str | None:
54
+ newest = latest_version()
55
+ if newest is None or not update_available(COLLECTOR_VERSION, newest):
56
+ return None
57
+ return newest if install_latest(newest) else None
@@ -4,6 +4,8 @@ import json
4
4
  from typing import Any
5
5
  from urllib import request
6
6
 
7
+ from version import COLLECTOR_VERSION
8
+
7
9
 
8
10
  class PromptHubUploader:
9
11
  def __init__(self, api_url: str, token: str | None = None, timeout: float = 10) -> None:
@@ -16,7 +18,10 @@ class PromptHubUploader:
16
18
  return []
17
19
 
18
20
  body = json.dumps({"events": events}).encode("utf-8")
19
- headers = {"Content-Type": "application/json"}
21
+ headers = {
22
+ "Content-Type": "application/json",
23
+ "X-Promty-Collector-Version": COLLECTOR_VERSION,
24
+ }
20
25
  if self.token:
21
26
  headers["Authorization"] = f"Bearer {self.token}"
22
27
 
package/src/version.py ADDED
@@ -0,0 +1 @@
1
+ COLLECTOR_VERSION = "0.1.2"