From e300bb45a90ebc0e7d148f10dfea5c86c940e8e3 Mon Sep 17 00:00:00 2001 From: Andrei <16517508+anvacaru@users.noreply.github.com> Date: Thu, 9 Oct 2025 10:14:27 +0300 Subject: [PATCH 01/11] update formatting --- poetry.lock | 10 +++++----- src/kup/__main__.py | 16 ++++++++-------- 2 files changed, 13 insertions(+), 13 deletions(-) diff --git a/poetry.lock b/poetry.lock index a755996..e36c755 100644 --- a/poetry.lock +++ b/poetry.lock @@ -546,14 +546,14 @@ files = [ [[package]] name = "typing-extensions" -version = "4.4.0" -description = "Backported and Experimental Type Hints for Python 3.7+" +version = "4.15.0" +description = "Backported and Experimental Type Hints for Python 3.9+" optional = false -python-versions = ">=3.7" +python-versions = ">=3.9" groups = ["dev"] files = [ - {file = "typing_extensions-4.4.0-py3-none-any.whl", hash = "sha256:16fa4864408f655d35ec496218b85f79b3437c829e93320c7c9215ccfd92489e"}, - {file = "typing_extensions-4.4.0.tar.gz", hash = "sha256:1511434bb92bf8dd198c12b1cc812e800d4181cfcb867674e0f8279cc93087aa"}, + {file = "typing_extensions-4.15.0-py3-none-any.whl", hash = "sha256:f0fa19c6845758ab08074a0cfa8b7aecb71c999ca73d62883bc25cc018c4e548"}, + {file = "typing_extensions-4.15.0.tar.gz", hash = "sha256:0cea48d173cc12fa28ecabc3b837ea3cf6f38c6d1136f85cbaaf598984861466"}, ] [[package]] diff --git a/src/kup/__main__.py b/src/kup/__main__.py index ac53a04..d4a7b70 100644 --- a/src/kup/__main__.py +++ b/src/kup/__main__.py @@ -191,7 +191,7 @@ def package_metadata_tree( follows = (' - follows [green]' + '/'.join(p.follows)) if type(p) == Follows else '' status = '' if show_status and type(p) == PackageMetadata: - auth = {'Authorization': f'Bearer {os.getenv("GH_TOKEN")}'} if os.getenv('GH_TOKEN') else {} + auth = {'Authorization': f"Bearer {os.getenv('GH_TOKEN')}"} if os.getenv('GH_TOKEN') else {} commits = requests.get(f'https://api.github.com/repos/{p.org}/{p.repo}/commits', headers=auth) if commits.ok: commits_list = [c['sha'] for c in commits.json()] @@ -253,8 +253,8 @@ def reload_packages(load_versions: bool = True) -> None: if pinned.ok: pinned_package_cache = {r['name']: r['lastRevision']['storePath'] for r in pinned.json()} - if os.path.exists(f'{os.getenv("HOME")}/.nix-profile/manifest.json'): - manifest_file = open(f'{os.getenv("HOME")}/.nix-profile/manifest.json') + if os.path.exists(f"{os.getenv('HOME')}/.nix-profile/manifest.json"): + manifest_file = open(f"{os.getenv('HOME')}/.nix-profile/manifest.json") manifest = json.loads(manifest_file.read())['elements'] if type(manifest) is list: manifest = dict(enumerate(manifest)) @@ -333,7 +333,7 @@ def list_package( auth = ( {'Authorization': f'Bearer {listed_package.access_token}'} if listed_package.access_token - else {'Authorization': f'Bearer {os.getenv("GH_TOKEN")}'} + else {'Authorization': f"Bearer {os.getenv('GH_TOKEN')}"} if os.getenv('GH_TOKEN') else {} ) @@ -354,7 +354,7 @@ def list_package( c['commit']['message'], tagged_releases[c['sha']]['name'] if c['sha'] in tagged_releases else None, c['commit']['committer']['date'], - f'github:{listed_package.org}/{listed_package.repo}/{c["sha"]}#{listed_package.package_name}' + f"github:{listed_package.org}/{listed_package.repo}/{c['sha']}#{listed_package.package_name}" in pinned_package_cache.keys(), ) for c in commits.json() @@ -381,7 +381,7 @@ def list_package( table_data = [['Package name (alias)', 'Installed version', 'Status'],] + [ [ str(PackageName(alias, p.package_name.ext).pretty_name), - f'{p.commit[:7] if TERMINAL_WIDTH < 80 else p.commit}{" (" + p.tag + ")" if p.tag else ""}' + f"{p.commit[:7] if TERMINAL_WIDTH < 80 else p.commit}{' (' + p.tag + ')' if p.tag else ''}" if type(p) == ConcretePackage else '\033[3mlocal checkout\033[0m' if type(p) == LocalPackage @@ -546,7 +546,7 @@ def uninstall_package(package_name: str) -> None: rich.print( "⚠️ [yellow]You are about to remove '[green]kup[/]' " 'with other K framework packages still installed.\n' - '[/]Are you sure you want to continue? \[y/N]' # noqa: W605 + '[/]Are you sure you want to continue? [y/N]' # noqa: W605 ) yes = {'yes', 'y', 'ye', ''} @@ -594,7 +594,7 @@ def check_github_api_accessible(org: str, repo: str, access_token: Optional[str] auth = ( {'Authorization': f'Bearer {access_token}'} if access_token - else {'Authorization': f'Bearer {os.getenv("GH_TOKEN")}'} + else {'Authorization': f"Bearer {os.getenv('GH_TOKEN')}"} if os.getenv('GH_TOKEN') else {} ) From 3487f1982442c614ee9ccec11c0966fa35055e78 Mon Sep 17 00:00:00 2001 From: Andrei <16517508+anvacaru@users.noreply.github.com> Date: Thu, 9 Oct 2025 17:15:02 +0300 Subject: [PATCH 02/11] emit events when installing packages --- src/kup/__main__.py | 52 +++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 52 insertions(+) diff --git a/src/kup/__main__.py b/src/kup/__main__.py index d4a7b70..1e92458 100644 --- a/src/kup/__main__.py +++ b/src/kup/__main__.py @@ -5,7 +5,9 @@ import subprocess import sys import textwrap +import uuid from argparse import ArgumentParser, Namespace, RawDescriptionHelpFormatter, _HelpAction +from pathlib import Path from typing import Any, Dict, List, MutableMapping, Optional, Tuple, Union import giturlparse @@ -481,6 +483,15 @@ def install_package( _, git_token_options = package.concrete_repo_path_with_access overrides = mk_override_args(package, package_overrides) + _track_event( + 'kup_install_start', + { + 'package': package_name.base, + 'version': package_version, + 'has_overrides': len(package_overrides) > 0 if package_overrides else False, + }, + ) + if not overrides and package.uri in pinned_package_cache: rich.print(f" ⌛ Fetching cached version of '[green]{package_name.pretty_name}[/]' ...") nix( @@ -525,6 +536,16 @@ def install_package( display_version = None display_version = f' ({display_version})' if display_version is not None else '' + _track_event( + 'kup_install_complete', + { + 'package': package_name.base, + 'version': package_version or 'latest', + 'was_update': verb == 'updated', + 'from_cache': package.uri in pinned_package_cache and not overrides, + }, + ) + rich.print( f" ✅ Successfully {verb} '[green]{package_name.base}[/]' version [blue]{package.uri}{display_version}[/]." ) @@ -1037,5 +1058,36 @@ def main() -> None: ) +def _get_user_id() -> str: + """Get or create persistent anonymous user ID""" + config_dir = Path.home() / '.kprofile' + config_dir.mkdir(exist_ok=True) + user_id_file = config_dir / 'user_id' + + if user_id_file.exists(): + return user_id_file.read_text().strip() + + user_id = str(uuid.uuid4()) + user_id_file.write_text(user_id) + return user_id + + +def _track_event(event: str, properties: dict | None = None) -> None: + """Send telemetry event to proxy server""" + if os.getenv('K_TELEMETRY') == '0': + return + + try: + json = {'user_id': _get_user_id(), 'event': event, 'properties': properties or {}} + print(json) + # requests.post( + # 'http://localhost:5000/track', # TODO: replace with the telemetry proxy server ip + # json={'user_id': _get_user_id(), 'event': event, 'properties': properties or {}}, + # timeout=2, + # ) + except Exception: + pass # Fail silently + + if __name__ == '__main__': main() From 1c7a716cb34be55f026e3e771ffc7faecb6b0875 Mon Sep 17 00:00:00 2001 From: Andrei <16517508+anvacaru@users.noreply.github.com> Date: Thu, 9 Oct 2025 17:18:21 +0300 Subject: [PATCH 03/11] remove print stmt --- src/kup/__main__.py | 11 +++++------ 1 file changed, 5 insertions(+), 6 deletions(-) diff --git a/src/kup/__main__.py b/src/kup/__main__.py index 1e92458..32f89f1 100644 --- a/src/kup/__main__.py +++ b/src/kup/__main__.py @@ -1079,12 +1079,11 @@ def _track_event(event: str, properties: dict | None = None) -> None: try: json = {'user_id': _get_user_id(), 'event': event, 'properties': properties or {}} - print(json) - # requests.post( - # 'http://localhost:5000/track', # TODO: replace with the telemetry proxy server ip - # json={'user_id': _get_user_id(), 'event': event, 'properties': properties or {}}, - # timeout=2, - # ) + requests.post( + 'http://localhost:5000/track', # TODO: replace with the telemetry proxy server ip + json={'user_id': _get_user_id(), 'event': event, 'properties': properties or {}}, + timeout=2, + ) except Exception: pass # Fail silently From df770f5ccec4d3b65e502a90369e899dfcfef599 Mon Sep 17 00:00:00 2001 From: Andrei <16517508+anvacaru@users.noreply.github.com> Date: Mon, 19 Jan 2026 14:04:22 +0200 Subject: [PATCH 04/11] update telemetry script --- poetry.lock | 70 +++++++++++++++++++++++++++++++++++++++----- pyproject.toml | 2 ++ src/kup/__main__.py | 37 ++--------------------- src/kup/telemetry.py | 62 +++++++++++++++++++++++++++++++++++++++ 4 files changed, 130 insertions(+), 41 deletions(-) create mode 100644 src/kup/telemetry.py diff --git a/poetry.lock b/poetry.lock index e36c755..50d9b70 100644 --- a/poetry.lock +++ b/poetry.lock @@ -506,15 +506,71 @@ tests = ["pytest"] [[package]] name = "tomli" -version = "2.0.1" +version = "2.4.0" description = "A lil' TOML parser" optional = false -python-versions = ">=3.7" -groups = ["dev"] -markers = "python_full_version < \"3.11.0a7\"" +python-versions = ">=3.8" +groups = ["main", "dev"] +files = [ + {file = "tomli-2.4.0-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:b5ef256a3fd497d4973c11bf142e9ed78b150d36f5773f1ca6088c230ffc5867"}, + {file = "tomli-2.4.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:5572e41282d5268eb09a697c89a7bee84fae66511f87533a6f88bd2f7b652da9"}, + {file = "tomli-2.4.0-cp311-cp311-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:551e321c6ba03b55676970b47cb1b73f14a0a4dce6a3e1a9458fd6d921d72e95"}, + {file = "tomli-2.4.0-cp311-cp311-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:5e3f639a7a8f10069d0e15408c0b96a2a828cfdec6fca05296ebcdcc28ca7c76"}, + {file = "tomli-2.4.0-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:1b168f2731796b045128c45982d3a4874057626da0e2ef1fdd722848b741361d"}, + {file = "tomli-2.4.0-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:133e93646ec4300d651839d382d63edff11d8978be23da4cc106f5a18b7d0576"}, + {file = "tomli-2.4.0-cp311-cp311-win32.whl", hash = "sha256:b6c78bdf37764092d369722d9946cb65b8767bfa4110f902a1b2542d8d173c8a"}, + {file = "tomli-2.4.0-cp311-cp311-win_amd64.whl", hash = "sha256:d3d1654e11d724760cdb37a3d7691f0be9db5fbdaef59c9f532aabf87006dbaa"}, + {file = "tomli-2.4.0-cp311-cp311-win_arm64.whl", hash = "sha256:cae9c19ed12d4e8f3ebf46d1a75090e4c0dc16271c5bce1c833ac168f08fb614"}, + {file = "tomli-2.4.0-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:920b1de295e72887bafa3ad9f7a792f811847d57ea6b1215154030cf131f16b1"}, + {file = "tomli-2.4.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:7d6d9a4aee98fac3eab4952ad1d73aee87359452d1c086b5ceb43ed02ddb16b8"}, + {file = "tomli-2.4.0-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:36b9d05b51e65b254ea6c2585b59d2c4cb91c8a3d91d0ed0f17591a29aaea54a"}, + {file = "tomli-2.4.0-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:1c8a885b370751837c029ef9bc014f27d80840e48bac415f3412e6593bbc18c1"}, + {file = "tomli-2.4.0-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:8768715ffc41f0008abe25d808c20c3d990f42b6e2e58305d5da280ae7d1fa3b"}, + {file = "tomli-2.4.0-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:7b438885858efd5be02a9a133caf5812b8776ee0c969fea02c45e8e3f296ba51"}, + {file = "tomli-2.4.0-cp312-cp312-win32.whl", hash = "sha256:0408e3de5ec77cc7f81960c362543cbbd91ef883e3138e81b729fc3eea5b9729"}, + {file = "tomli-2.4.0-cp312-cp312-win_amd64.whl", hash = "sha256:685306e2cc7da35be4ee914fd34ab801a6acacb061b6a7abca922aaf9ad368da"}, + {file = "tomli-2.4.0-cp312-cp312-win_arm64.whl", hash = "sha256:5aa48d7c2356055feef06a43611fc401a07337d5b006be13a30f6c58f869e3c3"}, + {file = "tomli-2.4.0-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:84d081fbc252d1b6a982e1870660e7330fb8f90f676f6e78b052ad4e64714bf0"}, + {file = "tomli-2.4.0-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:9a08144fa4cba33db5255f9b74f0b89888622109bd2776148f2597447f92a94e"}, + {file = "tomli-2.4.0-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:c73add4bb52a206fd0c0723432db123c0c75c280cbd67174dd9d2db228ebb1b4"}, + {file = "tomli-2.4.0-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:1fb2945cbe303b1419e2706e711b7113da57b7db31ee378d08712d678a34e51e"}, + {file = "tomli-2.4.0-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:bbb1b10aa643d973366dc2cb1ad94f99c1726a02343d43cbc011edbfac579e7c"}, + {file = "tomli-2.4.0-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:4cbcb367d44a1f0c2be408758b43e1ffb5308abe0ea222897d6bfc8e8281ef2f"}, + {file = "tomli-2.4.0-cp313-cp313-win32.whl", hash = "sha256:7d49c66a7d5e56ac959cb6fc583aff0651094ec071ba9ad43df785abc2320d86"}, + {file = "tomli-2.4.0-cp313-cp313-win_amd64.whl", hash = "sha256:3cf226acb51d8f1c394c1b310e0e0e61fecdd7adcb78d01e294ac297dd2e7f87"}, + {file = "tomli-2.4.0-cp313-cp313-win_arm64.whl", hash = "sha256:d20b797a5c1ad80c516e41bc1fb0443ddb5006e9aaa7bda2d71978346aeb9132"}, + {file = "tomli-2.4.0-cp314-cp314-macosx_10_15_x86_64.whl", hash = "sha256:26ab906a1eb794cd4e103691daa23d95c6919cc2fa9160000ac02370cc9dd3f6"}, + {file = "tomli-2.4.0-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:20cedb4ee43278bc4f2fee6cb50daec836959aadaf948db5172e776dd3d993fc"}, + {file = "tomli-2.4.0-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:39b0b5d1b6dd03684b3fb276407ebed7090bbec989fa55838c98560c01113b66"}, + {file = "tomli-2.4.0-cp314-cp314-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:a26d7ff68dfdb9f87a016ecfd1e1c2bacbe3108f4e0f8bcd2228ef9a766c787d"}, + {file = "tomli-2.4.0-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:20ffd184fb1df76a66e34bd1b36b4a4641bd2b82954befa32fe8163e79f1a702"}, + {file = "tomli-2.4.0-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:75c2f8bbddf170e8effc98f5e9084a8751f8174ea6ccf4fca5398436e0320bc8"}, + {file = "tomli-2.4.0-cp314-cp314-win32.whl", hash = "sha256:31d556d079d72db7c584c0627ff3a24c5d3fb4f730221d3444f3efb1b2514776"}, + {file = "tomli-2.4.0-cp314-cp314-win_amd64.whl", hash = "sha256:43e685b9b2341681907759cf3a04e14d7104b3580f808cfde1dfdb60ada85475"}, + {file = "tomli-2.4.0-cp314-cp314-win_arm64.whl", hash = "sha256:3d895d56bd3f82ddd6faaff993c275efc2ff38e52322ea264122d72729dca2b2"}, + {file = "tomli-2.4.0-cp314-cp314t-macosx_10_15_x86_64.whl", hash = "sha256:5b5807f3999fb66776dbce568cc9a828544244a8eb84b84b9bafc080c99597b9"}, + {file = "tomli-2.4.0-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:c084ad935abe686bd9c898e62a02a19abfc9760b5a79bc29644463eaf2840cb0"}, + {file = "tomli-2.4.0-cp314-cp314t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:0f2e3955efea4d1cfbcb87bc321e00dc08d2bcb737fd1d5e398af111d86db5df"}, + {file = "tomli-2.4.0-cp314-cp314t-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:0e0fe8a0b8312acf3a88077a0802565cb09ee34107813bba1c7cd591fa6cfc8d"}, + {file = "tomli-2.4.0-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:413540dce94673591859c4c6f794dfeaa845e98bf35d72ed59636f869ef9f86f"}, + {file = "tomli-2.4.0-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:0dc56fef0e2c1c470aeac5b6ca8cc7b640bb93e92d9803ddaf9ea03e198f5b0b"}, + {file = "tomli-2.4.0-cp314-cp314t-win32.whl", hash = "sha256:d878f2a6707cc9d53a1be1414bbb419e629c3d6e67f69230217bb663e76b5087"}, + {file = "tomli-2.4.0-cp314-cp314t-win_amd64.whl", hash = "sha256:2add28aacc7425117ff6364fe9e06a183bb0251b03f986df0e78e974047571fd"}, + {file = "tomli-2.4.0-cp314-cp314t-win_arm64.whl", hash = "sha256:2b1e3b80e1d5e52e40e9b924ec43d81570f0e7d09d11081b797bc4692765a3d4"}, + {file = "tomli-2.4.0-py3-none-any.whl", hash = "sha256:1f776e7d669ebceb01dee46484485f43a4048746235e683bcdffacdf1fb4785a"}, + {file = "tomli-2.4.0.tar.gz", hash = "sha256:aa89c3f6c277dd275d8e243ad24f3b5e701491a860d5121f2cdd399fbb31fc9c"}, +] + +[[package]] +name = "tomli-w" +version = "1.2.0" +description = "A lil' TOML writer" +optional = false +python-versions = ">=3.9" +groups = ["main"] files = [ - {file = "tomli-2.0.1-py3-none-any.whl", hash = "sha256:939de3e7a6161af0c887ef91b7d41a53e7c5a1ca976325f429cb46ea9bc30ecc"}, - {file = "tomli-2.0.1.tar.gz", hash = "sha256:de526c12914f0c550d15924c62d72abc48d6fe7364aa87328337a31007fe8a4f"}, + {file = "tomli_w-1.2.0-py3-none-any.whl", hash = "sha256:188306098d013b691fcadc011abd66727d3c414c571bb01b1a174ba8c983cf90"}, + {file = "tomli_w-1.2.0.tar.gz", hash = "sha256:2dd14fac5a47c27be9cd4c976af5a12d87fb1f0b4512f81d69cce3b35ae25021"}, ] [[package]] @@ -576,4 +632,4 @@ socks = ["PySocks (>=1.5.6,!=1.5.7,<2.0)"] [metadata] lock-version = "2.1" python-versions = "^3.9" -content-hash = "bc2c286fd3c8a955e361f1dc8d0b0c780c9d60fdae10fed7ccd6bbd853aa6205" +content-hash = "0013931efc5d79a7ab8370bcff8de3909be9f773468512f1f55431de43f5c4bb" diff --git a/pyproject.toml b/pyproject.toml index a297a19..247441f 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -22,6 +22,8 @@ rich = "^12.6.0" pyxdg = "^0.28" tinynetrc = "^1.3.1" git-url-parse = "^1.2.2" +tomli = "^2.4.0" +tomli-w = "^1.2.0" [tool.poetry.group.dev.dependencies] autoflake = "*" diff --git a/src/kup/__main__.py b/src/kup/__main__.py index 6b8bf74..d8cf9d3 100644 --- a/src/kup/__main__.py +++ b/src/kup/__main__.py @@ -5,9 +5,7 @@ import subprocess import sys import textwrap -import uuid from argparse import ArgumentParser, Namespace, RawDescriptionHelpFormatter, _HelpAction -from pathlib import Path from typing import Any, Dict, List, MutableMapping, Optional, Tuple, Union import giturlparse @@ -52,6 +50,7 @@ PackageName, PackageVersion, ) +from .telemetry import _emit_event console = Console(theme=Theme({'markdown.code': 'green'})) @@ -484,7 +483,7 @@ def install_package( _, git_token_options = package.concrete_repo_path_with_access overrides = mk_override_args(package, package_overrides) - _track_event( + _emit_event( 'kup_install_start', { 'package': package_name.base, @@ -537,7 +536,7 @@ def install_package( display_version = None display_version = f' ({display_version})' if display_version is not None else '' - _track_event( + _emit_event( 'kup_install_complete', { 'package': package_name.base, @@ -1059,35 +1058,5 @@ def main() -> None: ) -def _get_user_id() -> str: - """Get or create persistent anonymous user ID""" - config_dir = Path.home() / '.kprofile' - config_dir.mkdir(exist_ok=True) - user_id_file = config_dir / 'user_id' - - if user_id_file.exists(): - return user_id_file.read_text().strip() - - user_id = str(uuid.uuid4()) - user_id_file.write_text(user_id) - return user_id - - -def _track_event(event: str, properties: dict | None = None) -> None: - """Send telemetry event to proxy server""" - if os.getenv('K_TELEMETRY') == '0': - return - - try: - json = {'user_id': _get_user_id(), 'event': event, 'properties': properties or {}} - requests.post( - 'http://localhost:5000/track', # TODO: replace with the telemetry proxy server ip - json={'user_id': _get_user_id(), 'event': event, 'properties': properties or {}}, - timeout=2, - ) - except Exception: - pass # Fail silently - - if __name__ == '__main__': main() diff --git a/src/kup/telemetry.py b/src/kup/telemetry.py new file mode 100644 index 0000000..da26b3e --- /dev/null +++ b/src/kup/telemetry.py @@ -0,0 +1,62 @@ +from __future__ import annotations + +import logging +import os +import uuid +from pathlib import Path +from typing import Final + +import requests +import tomli +import tomli_w + +_LOGGER: Final = logging.getLogger(__name__) + +KPROFILE_CONFIG_DIR: Final = Path.home() / '.config' / 'kprofile' +KPROFILE_CONFIG_FILE: Final = KPROFILE_CONFIG_DIR / 'config.toml' +TELEMETRY_MESSAGE: Final = f'Telemetry: sending anonymous usage data. You can opt out by setting KPROFILE_TELEMETRY_DISABLED=true or consent=false in {KPROFILE_CONFIG_FILE}' + + +def _get_user_id() -> str: + """Get or create persistent anonymous user ID""" + if not KPROFILE_CONFIG_FILE.exists(): + KPROFILE_CONFIG_FILE.parent.mkdir(parents=True, exist_ok=True) + config = {'user': {'user_id': str(uuid.uuid4()), 'consent': True}} + with open(KPROFILE_CONFIG_FILE, 'wb') as f: + tomli_w.dump(config, f) + return str(config['user']['user_id']) + + with open(KPROFILE_CONFIG_FILE, 'rb') as f: + config = tomli.load(f) + + return str(config['user']['user_id']) + + +def _has_permission() -> bool: + """Check if telemetry is enabled""" + if os.getenv('KPROFILE_TELEMETRY_DISABLED', '').lower() == 'true': + return False + + _get_user_id() + + with open(KPROFILE_CONFIG_FILE, 'rb') as f: + config = tomli.load(f) + + return config.get('user', {}).get('consent', True) + + +def _emit_event(event: str, properties: dict | None = None) -> None: + """Send telemetry event to proxy server""" + if not _has_permission(): + return + + _LOGGER.info(TELEMETRY_MESSAGE) + + try: + requests.post( + 'https://ojlk1fzi13.execute-api.us-east-1.amazonaws.com/dev/track', + json={'user_id': _get_user_id(), 'event': event, 'properties': properties}, + timeout=2, + ) + except Exception: + pass # Fail silently From 5cb8514150334f99b9a4cf39173363742748fd1a Mon Sep 17 00:00:00 2001 From: Andrei <16517508+anvacaru@users.noreply.github.com> Date: Mon, 19 Jan 2026 14:10:45 +0200 Subject: [PATCH 05/11] revert formatting --- src/kup/__main__.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/kup/__main__.py b/src/kup/__main__.py index d8cf9d3..27bcd8f 100644 --- a/src/kup/__main__.py +++ b/src/kup/__main__.py @@ -567,7 +567,7 @@ def uninstall_package(package_name: str) -> None: rich.print( "⚠️ [yellow]You are about to remove '[green]kup[/]' " 'with other K framework packages still installed.\n' - '[/]Are you sure you want to continue? [y/N]' # noqa: W605 + '[/]Are you sure you want to continue? \[y/N]' # noqa: W605 ) yes = {'yes', 'y', 'ye', ''} From 8da0e56c6db3be6a772c6f54d340f09f67b5ee39 Mon Sep 17 00:00:00 2001 From: Andrei <16517508+anvacaru@users.noreply.github.com> Date: Mon, 19 Jan 2026 17:26:16 +0200 Subject: [PATCH 06/11] review suggestions --- src/kup/__main__.py | 6 +++--- src/kup/telemetry.py | 6 +++--- 2 files changed, 6 insertions(+), 6 deletions(-) diff --git a/src/kup/__main__.py b/src/kup/__main__.py index 27bcd8f..7271dad 100644 --- a/src/kup/__main__.py +++ b/src/kup/__main__.py @@ -50,7 +50,7 @@ PackageName, PackageVersion, ) -from .telemetry import _emit_event +from .telemetry import emit_event console = Console(theme=Theme({'markdown.code': 'green'})) @@ -483,7 +483,7 @@ def install_package( _, git_token_options = package.concrete_repo_path_with_access overrides = mk_override_args(package, package_overrides) - _emit_event( + emit_event( 'kup_install_start', { 'package': package_name.base, @@ -536,7 +536,7 @@ def install_package( display_version = None display_version = f' ({display_version})' if display_version is not None else '' - _emit_event( + emit_event( 'kup_install_complete', { 'package': package_name.base, diff --git a/src/kup/telemetry.py b/src/kup/telemetry.py index da26b3e..e5c15af 100644 --- a/src/kup/telemetry.py +++ b/src/kup/telemetry.py @@ -45,7 +45,7 @@ def _has_permission() -> bool: return config.get('user', {}).get('consent', True) -def _emit_event(event: str, properties: dict | None = None) -> None: +def emit_event(event: str, properties: dict | None = None) -> None: """Send telemetry event to proxy server""" if not _has_permission(): return @@ -58,5 +58,5 @@ def _emit_event(event: str, properties: dict | None = None) -> None: json={'user_id': _get_user_id(), 'event': event, 'properties': properties}, timeout=2, ) - except Exception: - pass # Fail silently + except Exception as e: + _LOGGER.warning(f'Telemetry event failed: {event}', exc_info=e) From 9f6929188a9fb8f64d92e723395dcaadf78e66a2 Mon Sep 17 00:00:00 2001 From: Andrei <16517508+anvacaru@users.noreply.github.com> Date: Wed, 21 Jan 2026 14:23:39 +0200 Subject: [PATCH 07/11] [DO NOT MERGE] bump python verison --- .github/workflows/test-pr.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/test-pr.yml b/.github/workflows/test-pr.yml index a629ff8..c2454ec 100644 --- a/.github/workflows/test-pr.yml +++ b/.github/workflows/test-pr.yml @@ -30,7 +30,7 @@ jobs: skipPush: true - uses: actions/setup-python@v4 with: - python-version: '3.9' + python-version: '3.11' - name: 'Install Poetry' run: | curl -sSL https://install.python-poetry.org | python3 - From 70aad59c6590b8874104f3757c71d4259dcb7cb0 Mon Sep 17 00:00:00 2001 From: Andrei <16517508+anvacaru@users.noreply.github.com> Date: Wed, 21 Jan 2026 14:29:42 +0200 Subject: [PATCH 08/11] [DO NOT MERGE] bump python in flake.nix to 3.11 --- flake.nix | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/flake.nix b/flake.nix index 383a994..9613ac1 100644 --- a/flake.nix +++ b/flake.nix @@ -17,7 +17,7 @@ p2n = poetry2nix.lib.mkPoetry2Nix { pkgs = final; }; in { kup = p2n.mkPoetryApplication { - python = prev.python39; + python = prev.python311; projectDir = ./.; # We remove `"dev"` from `checkGroups`, so that poetry2nix does not try to resolve dev dependencies. checkGroups = []; From ff657a73cffd61497a94190589dec86ac537e12c Mon Sep 17 00:00:00 2001 From: Andrei <16517508+anvacaru@users.noreply.github.com> Date: Wed, 21 Jan 2026 14:38:17 +0200 Subject: [PATCH 09/11] revert tomli version to 2.0.1 --- poetry.lock | 2 +- pyproject.toml | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/poetry.lock b/poetry.lock index 50d9b70..1e96600 100644 --- a/poetry.lock +++ b/poetry.lock @@ -632,4 +632,4 @@ socks = ["PySocks (>=1.5.6,!=1.5.7,<2.0)"] [metadata] lock-version = "2.1" python-versions = "^3.9" -content-hash = "0013931efc5d79a7ab8370bcff8de3909be9f773468512f1f55431de43f5c4bb" +content-hash = "a735690f7c7f8c894452029241a7c229280f993aab27abdb319bd9cb5af52f2c" diff --git a/pyproject.toml b/pyproject.toml index 247441f..3bf6961 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -22,7 +22,7 @@ rich = "^12.6.0" pyxdg = "^0.28" tinynetrc = "^1.3.1" git-url-parse = "^1.2.2" -tomli = "^2.4.0" +tomli = "^2.0.1" tomli-w = "^1.2.0" [tool.poetry.group.dev.dependencies] @@ -54,4 +54,4 @@ skip-string-normalization = true [tool.mypy] disallow_untyped_defs = true -ignore_missing_imports = true \ No newline at end of file +ignore_missing_imports = true From 92efe54051752c6cfdac6c4377d53067bf743d9f Mon Sep 17 00:00:00 2001 From: Andrei <16517508+anvacaru@users.noreply.github.com> Date: Wed, 21 Jan 2026 14:40:17 +0200 Subject: [PATCH 10/11] Revert "[DO NOT MERGE] bump python in flake.nix to 3.11" This reverts commit 70aad59c6590b8874104f3757c71d4259dcb7cb0. --- flake.nix | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/flake.nix b/flake.nix index 9613ac1..383a994 100644 --- a/flake.nix +++ b/flake.nix @@ -17,7 +17,7 @@ p2n = poetry2nix.lib.mkPoetry2Nix { pkgs = final; }; in { kup = p2n.mkPoetryApplication { - python = prev.python311; + python = prev.python39; projectDir = ./.; # We remove `"dev"` from `checkGroups`, so that poetry2nix does not try to resolve dev dependencies. checkGroups = []; From c5714596a65a72b46d0001a00d00881385985e16 Mon Sep 17 00:00:00 2001 From: Andrei <16517508+anvacaru@users.noreply.github.com> Date: Wed, 21 Jan 2026 14:40:29 +0200 Subject: [PATCH 11/11] Revert "[DO NOT MERGE] bump python verison" This reverts commit 9f6929188a9fb8f64d92e723395dcaadf78e66a2. --- .github/workflows/test-pr.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/test-pr.yml b/.github/workflows/test-pr.yml index c2454ec..a629ff8 100644 --- a/.github/workflows/test-pr.yml +++ b/.github/workflows/test-pr.yml @@ -30,7 +30,7 @@ jobs: skipPush: true - uses: actions/setup-python@v4 with: - python-version: '3.11' + python-version: '3.9' - name: 'Install Poetry' run: | curl -sSL https://install.python-poetry.org | python3 -