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 +1 -1
- package/src/cli.py +31 -1
- package/src/runtime_install.py +2 -0
- package/src/updater.py +57 -0
- package/src/uploader/client.py +6 -1
- package/src/version.py +1 -0
package/package.json
CHANGED
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)
|
package/src/runtime_install.py
CHANGED
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
|
package/src/uploader/client.py
CHANGED
|
@@ -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 = {
|
|
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"
|