ruyi 0.41.0b20250926__py3-none-any.whl → 0.42.0__py3-none-any.whl

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.
ruyi/ruyipkg/unpack.py CHANGED
@@ -2,7 +2,7 @@ import mmap
2
2
  import os
3
3
  import shutil
4
4
  import subprocess
5
- from typing import BinaryIO, NoReturn, Protocol
5
+ from typing import Any, BinaryIO, NoReturn, Protocol
6
6
 
7
7
  from ..log import RuyiLogger
8
8
  from ..utils import ar, prereqs
@@ -20,7 +20,7 @@ class SupportsRead(Protocol):
20
20
  def do_unpack(
21
21
  logger: RuyiLogger,
22
22
  filename: str,
23
- dest: str | None,
23
+ dest: str | os.PathLike[Any] | None,
24
24
  strip_components: int,
25
25
  unpack_method: UnpackMethod,
26
26
  stream: BinaryIO | SupportsRead | None = None,
@@ -77,7 +77,7 @@ def do_unpack(
77
77
  def do_unpack_or_symlink(
78
78
  logger: RuyiLogger,
79
79
  filename: str,
80
- dest: str | None,
80
+ dest: str | os.PathLike[Any] | None,
81
81
  strip_components: int,
82
82
  unpack_method: UnpackMethod,
83
83
  stream: BinaryIO | SupportsRead | None = None,
@@ -100,7 +100,7 @@ def do_unpack_or_symlink(
100
100
 
101
101
  def _do_copy_raw(
102
102
  src_path: str,
103
- destdir: str | None,
103
+ destdir: str | os.PathLike[Any] | None,
104
104
  ) -> None:
105
105
  src_filename = os.path.basename(src_path)
106
106
  if destdir is None:
@@ -114,7 +114,7 @@ def _do_copy_raw(
114
114
 
115
115
  def do_symlink(
116
116
  src_path: str,
117
- destdir: str | None,
117
+ destdir: str | os.PathLike[Any] | None,
118
118
  ) -> None:
119
119
  src_filename = os.path.basename(src_path)
120
120
  if destdir is None:
@@ -132,7 +132,7 @@ def do_symlink(
132
132
  def _do_unpack_tar(
133
133
  logger: RuyiLogger,
134
134
  filename: str,
135
- dest: str | None,
135
+ dest: str | os.PathLike[Any] | None,
136
136
  strip_components: int,
137
137
  unpack_method: UnpackMethod,
138
138
  stream: SupportsRead | None,
@@ -164,8 +164,6 @@ def _do_unpack_tar(
164
164
  stdin = subprocess.PIPE
165
165
 
166
166
  argv.extend(("-f", filename, f"--strip-components={strip_components}"))
167
- if dest is not None:
168
- argv.extend(("-C", dest))
169
167
  if prefixes_to_unpack:
170
168
  if any(p.startswith("-") for p in prefixes_to_unpack):
171
169
  raise ValueError(
@@ -200,11 +198,11 @@ def _do_unpack_tar(
200
198
  def _do_unpack_zip(
201
199
  logger: RuyiLogger,
202
200
  filename: str,
203
- dest: str | None,
201
+ dest: str | os.PathLike[Any] | None,
204
202
  ) -> None:
205
203
  argv = ["unzip", filename]
206
204
  if dest is not None:
207
- argv.extend(("-d", dest))
205
+ argv.extend(("-d", str(dest)))
208
206
  logger.D(f"about to call unzip: argv={argv}")
209
207
  retcode = subprocess.call(argv, cwd=dest)
210
208
  if retcode != 0:
@@ -214,7 +212,7 @@ def _do_unpack_zip(
214
212
  def _do_unpack_bare_gz(
215
213
  logger: RuyiLogger,
216
214
  filename: str,
217
- destdir: str | None,
215
+ destdir: str | os.PathLike[Any] | None,
218
216
  ) -> None:
219
217
  # the suffix may not be ".gz" so do this generically
220
218
  dest_filename = os.path.splitext(os.path.basename(filename))[0]
@@ -235,7 +233,7 @@ def _do_unpack_bare_gz(
235
233
  def _do_unpack_bare_bzip2(
236
234
  logger: RuyiLogger,
237
235
  filename: str,
238
- destdir: str | None,
236
+ destdir: str | os.PathLike[Any] | None,
239
237
  ) -> None:
240
238
  # the suffix may not be ".bz2" so do this generically
241
239
  dest_filename = os.path.splitext(os.path.basename(filename))[0]
@@ -256,7 +254,7 @@ def _do_unpack_bare_bzip2(
256
254
  def _do_unpack_bare_lz4(
257
255
  logger: RuyiLogger,
258
256
  filename: str,
259
- destdir: str | None,
257
+ destdir: str | os.PathLike[Any] | None,
260
258
  ) -> None:
261
259
  # the suffix may not be ".lz4" so do this generically
262
260
  dest_filename = os.path.splitext(os.path.basename(filename))[0]
@@ -271,7 +269,7 @@ def _do_unpack_bare_lz4(
271
269
  def _do_unpack_bare_xz(
272
270
  logger: RuyiLogger,
273
271
  filename: str,
274
- destdir: str | None,
272
+ destdir: str | os.PathLike[Any] | None,
275
273
  ) -> None:
276
274
  # the suffix may not be ".xz" so do this generically
277
275
  dest_filename = os.path.splitext(os.path.basename(filename))[0]
@@ -292,7 +290,7 @@ def _do_unpack_bare_xz(
292
290
  def _do_unpack_bare_zstd(
293
291
  logger: RuyiLogger,
294
292
  filename: str,
295
- destdir: str | None,
293
+ destdir: str | os.PathLike[Any] | None,
296
294
  ) -> None:
297
295
  # the suffix may not be ".zst" so do this generically
298
296
  dest_filename = os.path.splitext(os.path.basename(filename))[0]
@@ -307,7 +305,7 @@ def _do_unpack_bare_zstd(
307
305
  def _do_unpack_deb(
308
306
  logger: RuyiLogger,
309
307
  filename: str,
310
- destdir: str | None,
308
+ destdir: str | os.PathLike[Any] | None,
311
309
  ) -> None:
312
310
  with ar.ArpyArchiveWrapper(filename) as a:
313
311
  for f in a.infolist():
@@ -18,19 +18,23 @@ if TYPE_CHECKING:
18
18
 
19
19
  FALLBACK_PM_TELEMETRY_ENDPOINT = "https://api.ruyisdk.cn/telemetry/pm/"
20
20
 
21
- TELEMETRY_INITIAL_UPLOAD_PROMPT = """
22
- By default, the RuyiSDK team collects anonymous usage data to help us improve
23
- the product. No personal information or detail about your project is ever
24
- collected. The data will be uploaded to RuyiSDK team-managed servers located
25
- in the Chinese mainland if you agree to the uploading. You can change this
26
- setting at any time by running [yellow]ruyi telemetry consent[/] or
27
- [yellow]ruyi telemetry optout[/].
28
-
29
- We would like to ask if you agree to have basic information about this [yellow]ruyi[/]
30
- installation uploaded, right now, for one time. This will allow the RuyiSDK team
31
- to have more precise knowledge about the product's adoption. Thank you for
32
- your support!
21
+ TELEMETRY_CONSENT_AND_UPLOAD_DESC = """
22
+ RuyiSDK collects anonymized usage data locally to help us improve the product.
23
+
24
+ [green]By default, nothing leaves your machine[/], and you can also turn off usage data
25
+ collection completely. Only with your explicit permission can [yellow]ruyi[/] upload
26
+ collected telemetry, periodically to RuyiSDK team-managed servers located in
27
+ the Chinese mainland. You can change this setting at any time by running
28
+ [yellow]ruyi telemetry consent[/], [yellow]ruyi telemetry local[/], or [yellow]ruyi telemetry optout[/].
29
+
30
+ If you enable uploads now, we'll also send a one-time report from this [yellow]ruyi[/]
31
+ installation so the RuyiSDK team can better understand adoption. Thank you for
32
+ helping us build a better experience!
33
33
  """
34
+ TELEMETRY_CONSENT_AND_UPLOAD_PROMPT = (
35
+ "Enable telemetry uploads and send a one-time report now?"
36
+ )
37
+ TELEMETRY_OPTOUT_PROMPT = "\nDo you want to disable telemetry entirely?"
34
38
 
35
39
 
36
40
  def next_utc_weekday(wday: int, now: float | None = None) -> int:
@@ -66,12 +70,17 @@ def set_telemetry_mode(
66
70
 
67
71
  logger = gc.logger
68
72
 
73
+ if mode == "on":
74
+ if consent_time is None:
75
+ consent_time = datetime.datetime.now().astimezone()
76
+ else:
77
+ # clear any previously recorded consent time
78
+ consent_time = None
79
+
80
+ # First, persist the changes to user config
69
81
  with ConfigEditor.work_on_user_local_config(gc) as ed:
70
82
  ed.set_value((schema.SECTION_TELEMETRY, schema.KEY_TELEMETRY_MODE), mode)
71
-
72
- if mode == "on":
73
- if consent_time is None:
74
- consent_time = datetime.datetime.now().astimezone()
83
+ if consent_time is not None:
75
84
  ed.set_value(
76
85
  (schema.SECTION_TELEMETRY, schema.KEY_TELEMETRY_UPLOAD_CONSENT),
77
86
  consent_time,
@@ -83,6 +92,18 @@ def set_telemetry_mode(
83
92
 
84
93
  ed.stage()
85
94
 
95
+ # Then, apply the changes to the running instance's GlobalConfig
96
+ # TelemetryProvider instance (if any) will pick them up automatically
97
+ # because the properties are backed by GlobalConfig.
98
+ gc.set_by_key(
99
+ (schema.SECTION_TELEMETRY, schema.KEY_TELEMETRY_MODE),
100
+ mode,
101
+ )
102
+ gc.set_by_key(
103
+ (schema.SECTION_TELEMETRY, schema.KEY_TELEMETRY_UPLOAD_CONSENT),
104
+ consent_time,
105
+ )
106
+
86
107
  if not show_cli_feedback:
87
108
  return
88
109
  match mode:
@@ -111,8 +132,6 @@ def set_telemetry_mode(
111
132
  class TelemetryProvider:
112
133
  def __init__(self, gc: "GlobalConfig") -> None:
113
134
  self.state_root = pathlib.Path(gc.telemetry_root)
114
- self.local_mode = gc.telemetry_mode == "local"
115
- self.upload_consent_time = gc.telemetry_upload_consent_time
116
135
 
117
136
  self._discard_events = False
118
137
  self._gc = gc
@@ -129,6 +148,14 @@ class TelemetryProvider:
129
148
  def logger(self) -> RuyiLogger:
130
149
  return self._gc.logger
131
150
 
151
+ @property
152
+ def local_mode(self) -> bool:
153
+ return self._gc.telemetry_mode == "local"
154
+
155
+ @property
156
+ def upload_consent_time(self) -> datetime.datetime | None:
157
+ return self._gc.telemetry_upload_consent_time
158
+
132
159
  def store(self, scope: TelemetryScope) -> TelemetryStore | None:
133
160
  return self._stores.get(scope)
134
161
 
@@ -386,16 +413,32 @@ class TelemetryProvider:
386
413
  return store.upload_staged_payloads()
387
414
 
388
415
  def oobe_prompt(self) -> None:
389
- """Ask whether the user consents to a first-run telemetry upload."""
416
+ """Ask whether the user consents to a first-run telemetry upload, and
417
+ persist the user's exact telemetry choice."""
390
418
 
391
419
  from ..cli import user_input
392
420
 
393
- self.logger.stdout(TELEMETRY_INITIAL_UPLOAD_PROMPT)
421
+ self.logger.stdout(TELEMETRY_CONSENT_AND_UPLOAD_DESC)
394
422
  if not user_input.ask_for_yesno_confirmation(
395
423
  self.logger,
396
- "Do you agree?",
397
- True,
424
+ TELEMETRY_CONSENT_AND_UPLOAD_PROMPT,
425
+ False,
398
426
  ):
427
+ # ask if the user wants to opt out entirely
428
+ if user_input.ask_for_yesno_confirmation(
429
+ self.logger,
430
+ TELEMETRY_OPTOUT_PROMPT,
431
+ False,
432
+ ):
433
+ set_telemetry_mode(self._gc, "off")
434
+ return
435
+
436
+ # user wants to stay in local mode
437
+ # explicitly record the preference, so we don't have to worry about
438
+ # us potentially changing defaults yet another time
439
+ set_telemetry_mode(self._gc, "local")
399
440
  return
400
441
 
442
+ consent_time = datetime.datetime.now().astimezone()
443
+ set_telemetry_mode(self._gc, "on", consent_time)
401
444
  self._upload_on_exit = True
ruyi/telemetry/store.py CHANGED
@@ -165,7 +165,7 @@ class TelemetryStore:
165
165
  payload: UploadPayload = {
166
166
  "fmt": 1,
167
167
  "nonce": payload_nonce,
168
- "ruyi_version": str(RUYI_SEMVER),
168
+ "ruyi_version": RUYI_SEMVER,
169
169
  "events": aggregate_data,
170
170
  }
171
171
  if installation_data is not None:
ruyi/utils/git.py CHANGED
@@ -119,13 +119,15 @@ def pull_ff_or_die(
119
119
  logger.I(f"repository: [yellow]{repo_path}[/]")
120
120
  logger.I(f"expected remote URL: [yellow]{remote_url}[/]")
121
121
  logger.I(f"actual remote URL: [yellow]{remote.url}[/]")
122
- logger.I("please fix the repo settings manually")
122
+ logger.I("please [bold red]fix the repo settings manually[/]")
123
123
  raise SystemExit(1)
124
124
 
125
125
  logger.D(
126
126
  f"updating url of remote {remote_name} from {remote.url} to {remote_url}"
127
127
  )
128
- repo.remotes.set_url("origin", remote_url)
128
+ repo.remotes.set_url(remote_name, remote_url)
129
+ # this needs manual refreshing
130
+ remote = repo.remotes[remote_name]
129
131
 
130
132
  logger.D("fetching")
131
133
  try:
ruyi/utils/global_mode.py CHANGED
@@ -128,6 +128,24 @@ def _guess_porcelain_from_argv(argv: list[str]) -> bool:
128
128
  return len(argv) > 1 and argv[1] == "--porcelain"
129
129
 
130
130
 
131
+ def _probe_cli_autocomplete(env: Mapping[str, str], argv: list[str]) -> bool:
132
+ """
133
+ Probe if the current invocation is related to shell completion based on
134
+ the arguments passed, without requiring the ``argparse`` machinery to be
135
+ completely initialized, and the environment.
136
+ """
137
+
138
+ # If `--output-completion-script` is present anywhere in the arguments,
139
+ # then this is related to shell completion even if _ARGCOMPLETE is not yet
140
+ # set (which is only set for invocations after the shell finished sourcing
141
+ # the completion script).
142
+ for arg in argv:
143
+ if arg.startswith("--output-completion-script"):
144
+ return True
145
+
146
+ return "_ARGCOMPLETE" in os.environ
147
+
148
+
131
149
  class EnvGlobalModeProvider(GlobalModeProvider):
132
150
  def __init__(
133
151
  self,
@@ -150,7 +168,7 @@ class EnvGlobalModeProvider(GlobalModeProvider):
150
168
 
151
169
  # We have to lift this piece of implementation detail out of argcomplete,
152
170
  # as the argcomplete import is very costly in terms of startup time.
153
- self._is_cli_autocomplete = "_ARGCOMPLETE" in os.environ
171
+ self._is_cli_autocomplete = _probe_cli_autocomplete(env, argv)
154
172
 
155
173
  self._venv_root = env.get(ENV_VENV_ROOT_KEY)
156
174
 
ruyi/utils/toml.py CHANGED
@@ -7,6 +7,17 @@ from tomlkit.container import Container
7
7
  from tomlkit.items import Array, Comment, InlineTable, Item, Table, Trivia, Whitespace
8
8
 
9
9
 
10
+ class NoneValue(Exception):
11
+ """Used to indicate that a None value is to be dumped in TOML. Because TOML
12
+ does not support None natively, this means special handling is needed."""
13
+
14
+ def __str__(self) -> str:
15
+ return "NoneValue()"
16
+
17
+ def __repr__(self) -> str:
18
+ return "NoneValue()"
19
+
20
+
10
21
  def with_indent(item: Item, spaces: int = 2) -> Item:
11
22
  item.indent(spaces)
12
23
  return item
ruyi/utils/xdg_basedir.py CHANGED
@@ -4,7 +4,12 @@
4
4
 
5
5
  import os
6
6
  import pathlib
7
- from typing import Iterable
7
+ from typing import Iterable, NamedTuple
8
+
9
+
10
+ class XDGPathEntry(NamedTuple):
11
+ path: pathlib.Path
12
+ is_global: bool
8
13
 
9
14
 
10
15
  def _paths_from_env(env: str, default: str) -> Iterable[pathlib.Path]:
@@ -38,14 +43,16 @@ class XDGBaseDir:
38
43
  return pathlib.Path(v) if v else pathlib.Path.home() / ".local" / "state"
39
44
 
40
45
  @property
41
- def config_dirs(self) -> Iterable[pathlib.Path]:
46
+ def config_dirs(self) -> Iterable[XDGPathEntry]:
42
47
  # from highest precedence to lowest
43
- yield from _paths_from_env("XDG_CONFIG_DIRS", "/etc/xdg")
48
+ for p in _paths_from_env("XDG_CONFIG_DIRS", "/etc/xdg"):
49
+ yield XDGPathEntry(p, True)
44
50
 
45
51
  @property
46
- def data_dirs(self) -> Iterable[pathlib.Path]:
52
+ def data_dirs(self) -> Iterable[XDGPathEntry]:
47
53
  # from highest precedence to lowest
48
- yield from _paths_from_env("XDG_DATA_DIRS", "/usr/local/share/:/usr/share/")
54
+ for p in _paths_from_env("XDG_DATA_DIRS", "/usr/local/share/:/usr/share/"):
55
+ yield XDGPathEntry(p, True)
49
56
 
50
57
  # derived info
51
58
 
@@ -66,15 +73,15 @@ class XDGBaseDir:
66
73
  return self.state_home / self.app_name
67
74
 
68
75
  @property
69
- def app_config_dirs(self) -> Iterable[pathlib.Path]:
76
+ def app_config_dirs(self) -> Iterable[XDGPathEntry]:
70
77
  # from highest precedence to lowest
71
- yield self.app_config
72
- for p in self.config_dirs:
73
- yield p / self.app_name
78
+ yield XDGPathEntry(self.app_config, False)
79
+ for e in self.config_dirs:
80
+ yield XDGPathEntry(e.path / self.app_name, e.is_global)
74
81
 
75
82
  @property
76
- def app_data_dirs(self) -> Iterable[pathlib.Path]:
83
+ def app_data_dirs(self) -> Iterable[XDGPathEntry]:
77
84
  # from highest precedence to lowest
78
- yield self.app_data
79
- for p in self.data_dirs:
80
- yield p / self.app_name
85
+ yield XDGPathEntry(self.app_data, False)
86
+ for e in self.data_dirs:
87
+ yield XDGPathEntry(e.path / self.app_name, e.is_global)
ruyi/version.py CHANGED
@@ -1,53 +1,6 @@
1
- import importlib.metadata
2
- from typing import Final, TYPE_CHECKING
1
+ from typing import Final
3
2
 
4
- import packaging.version
5
-
6
- if TYPE_CHECKING:
7
- # pyright only works with semver 3.x
8
- from semver.version import Version
9
- else:
10
- try:
11
- from semver.version import Version # type: ignore[import-untyped,unused-ignore]
12
- except ModuleNotFoundError:
13
- # semver 2.x
14
- from semver import VersionInfo as Version # type: ignore[import-untyped,unused-ignore]
15
-
16
- # NOTE: one cannot print logs in the version helpers, because the version info
17
- # is initialized so early (before argparse can look at argv because --version
18
- # requires version info to be ready) that the porcelain status is not yet
19
- # available.
20
-
21
- _PYPI_PRERELEASE_KINDS_MAP: Final = {
22
- "a": "alpha",
23
- "b": "beta",
24
- "rc": "rc",
25
- }
26
-
27
-
28
- # based on https://python-semver.readthedocs.io/en/3.0.2/advanced/convert-pypi-to-semver.html
29
- def _convert2semver(ver: packaging.version.Version) -> Version:
30
- if ver.epoch:
31
- raise ValueError("Can't convert an epoch to semver")
32
- if ver.post:
33
- raise ValueError("Can't convert a post part to semver")
34
-
35
- pre: str | None = None
36
- if ver.pre:
37
- kind, val = ver.pre
38
- pre = f"{_PYPI_PRERELEASE_KINDS_MAP.get(kind, kind)}.{val}"
39
-
40
- maj, min, pat = ver.release[:3]
41
- return Version(maj, min, pat, prerelease=pre, build=ver.dev)
42
-
43
-
44
- def _init_pkg_semver() -> Version:
45
- pkg_pypi_ver = packaging.version.Version(importlib.metadata.version("ruyi"))
46
- # log.D(f"PyPI-style version of ruyi: {pkg_pypi_ver}")
47
- return _convert2semver(pkg_pypi_ver)
48
-
49
-
50
- RUYI_SEMVER: Final = _init_pkg_semver()
3
+ RUYI_SEMVER: Final = "0.42.0"
51
4
  RUYI_USER_AGENT: Final = f"ruyi/{RUYI_SEMVER}"
52
5
 
53
6
  COPYRIGHT_NOTICE: Final = """\
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: ruyi
3
- Version: 0.41.0b20250926
3
+ Version: 0.42.0
4
4
  Summary: Package manager for RuyiSDK
5
5
  License: Apache License
6
6
  Version 2.0, January 2004
@@ -221,7 +221,7 @@ Classifier: Topic :: Software Development :: Build Tools
221
221
  Classifier: Topic :: Software Development :: Embedded Systems
222
222
  Classifier: Topic :: System :: Software Distribution
223
223
  Classifier: Typing :: Typed
224
- Requires-Dist: argcomplete (>=2.0.0,<4.0.0)
224
+ Requires-Dist: argcomplete (>=2.0.0)
225
225
  Requires-Dist: arpy
226
226
  Requires-Dist: fastjsonschema (>=2.15.1)
227
227
  Requires-Dist: jinja2 (>=3,<4)
@@ -332,6 +332,10 @@ Various aspects of `ruyi` can be configured with files or environment variables.
332
332
  look up its config accordingly. If these are not explicitly set though, as in
333
333
  typical use cases, the default config directory is most likely `~/.config/ruyi`.
334
334
 
335
+ GNU/Linux distribution packagers and system administrators will find that the
336
+ directories `/usr/share/ruyi` and `/usr/local/share/ruyi` are searched for the
337
+ config file on such systems.
338
+
335
339
  ### Config file
336
340
 
337
341
  Currently `ruyi` will look for an optional `config.toml` in its XDG config
@@ -361,9 +365,8 @@ branch = "main"
361
365
  # details.
362
366
  #
363
367
  # If unset or empty, this default value is used: data will be collected and
364
- # uploaded every week, on a random weekday determined by the installation's
365
- # anonymous ID alone.
366
- mode = "on"
368
+ # stored locally; nothing will be uploaded automatically.
369
+ mode = "local"
367
370
  # The time the user's consent is given to telemetry data uploading. If the
368
371
  # system time is later than the time given here, telemetry consent banner will
369
372
  # not be displayed any more each time `ruyi` is executed. The exact consent
@@ -407,12 +410,16 @@ There are 3 telemetry modes available:
407
410
  * `off`: data will not be collected nor uploaded.
408
411
  * `on`: data will be collected and periodically uploaded.
409
412
 
410
- By default the `on` mode is active, which means every `ruyi` invocation
411
- will record some non-sensitive information locally alongside various other
412
- states of `ruyi`, and collected data will be periodically uploaded to servers
413
- managed by the RuyiSDK team in the People's Republic of China, in a weekly fashion.
414
- The upload will happen on a random weekday which is determined by the
415
- installation's anonymous ID alone.
413
+ By default the `local` mode is active from `ruyi` 0.42.0 (inclusive) on, which
414
+ means every `ruyi` invocation will record some non-sensitive information locally
415
+ alongside various other states of `ruyi`, but collected data will not be
416
+ uploaded automatically unless you explicitly request so (for example by
417
+ switching to the `on` mode, or by executing `ruyi telemetry upload`).
418
+
419
+ In case the `on` mode is active, collected data will be periodically uploaded
420
+ to servers managed by the RuyiSDK team in the People's Republic of China, in a
421
+ weekly fashion. The upload will happen on a random weekday which is determined
422
+ by the installation's anonymous ID alone.
416
423
 
417
424
  You can change the telemetry mode by editing `ruyi`'s config file, or simply
418
425
  disable telemetry altogether by setting the `RUYI_TELEMETRY_OPTOUT` environment
@@ -5,19 +5,19 @@ ruyi/cli/builtin_commands.py,sha256=cYyPSF00DSBH1WMv6mHcMygbFRBGXObMWhbXHs5K1Mc,
5
5
  ruyi/cli/cmd.py,sha256=kR3aEiDE3AfPoP0Zr7MO-09CKoExbkLLmPvve9oKaUg,6725
6
6
  ruyi/cli/completer.py,sha256=cnOkU7veDe-jP8ROXZL2uBop2HgWfaAZl6dromnPLx8,1426
7
7
  ruyi/cli/completion.py,sha256=ffLs3Dv7pY_uinwH98wkBPohRvAjpUOGqy01OTA_Bgo,841
8
- ruyi/cli/config_cli.py,sha256=IMsUEs1iiFCvGLdGAVByMxwjNiP4IMZammkUMs0nFio,3497
9
- ruyi/cli/main.py,sha256=3_0ZPrQh5LJSe73UM4zP6FYw8NCG5ayT2sWf-6xUqL8,3382
10
- ruyi/cli/oobe.py,sha256=fpzNukwpT16CSqAobClC615Fii7GFDYc5g5ArZMur1A,2463
11
- ruyi/cli/self_cli.py,sha256=opDHVpvgg0AgZdzi7kEY73_jwV6LRjtIpdgsFkRL1hI,8668
8
+ ruyi/cli/config_cli.py,sha256=9kq5W3Ir_lfwImhvrUmQ1KTKy1aRCv_UU1CmL5eGyJs,4038
9
+ ruyi/cli/main.py,sha256=qWgCKbWG8sHxM7rInkoQfzZOHKo2k8JwdgJp_AMH388,4455
10
+ ruyi/cli/oobe.py,sha256=3m7oLPBhzn5JgagwQYDPy8vhZ4v8mBiDlO5n4cR_jVA,2712
11
+ ruyi/cli/self_cli.py,sha256=UEX3Xjy8RGGn9W2O5cM-5rLCwpsM7uSQ9vYYBYxyjyQ,8837
12
12
  ruyi/cli/user_input.py,sha256=ZJPyCAD7Aizkt37f_KDtW683aKvGZ2pL85Rg_y1LLfg,3711
13
13
  ruyi/cli/version_cli.py,sha256=L2pejZ7LIPYLUTb9NIz4t51KB8ai8igPBuE64tyqUuI,1275
14
- ruyi/config/__init__.py,sha256=VqHcXAt82bDJPsLnZKzzL-d-tNbe5X4SnLKlx9WdfRw,13501
15
- ruyi/config/editor.py,sha256=6b4VpViEPvfPHtEyrQHAjeFpJOT8-mfTla6dXbKf--g,3148
16
- ruyi/config/errors.py,sha256=EO-ezHgtkWoiH0jOjkkw3MgiTAHHLhppks6F9-p2i6I,2162
14
+ ruyi/config/__init__.py,sha256=9w1dtgvNxIW65eWzhRN4gl5-460hv-GipIoRZ_oqfXc,16859
15
+ ruyi/config/editor.py,sha256=piAJ-a68iX6IFWXy1g9OXoSs_3DardZwoaSPdStKKL0,4243
16
+ ruyi/config/errors.py,sha256=Yrzd3hX5Gb_bCawoVrWjFrLrpO-q_IOtg4ikE0P8ia0,2561
17
17
  ruyi/config/news.py,sha256=83LjQjJHsqOPdRrytG7VBFubG6pyDwJ-Mg37gpBRU20,1061
18
- ruyi/config/schema.py,sha256=j-oXLl5qeoaWNONxSzAfuaBM6be9m1Xjliez2FUEpjg,5862
18
+ ruyi/config/schema.py,sha256=u0TdpoUV6I7ZGZKlHAAGnqZbfaoTiA45UsM5c82M99M,7405
19
19
  ruyi/device/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
20
- ruyi/device/provision.py,sha256=0Lywhs9_sLMG21DRMP1DyijYFIB4k4-xYYNJikH8qcs,20902
20
+ ruyi/device/provision.py,sha256=9rUbLtybBQ8x9qRXaMwz_yAMf51vb3wmYE0qm54P5Bk,20906
21
21
  ruyi/device/provision_cli.py,sha256=sc6AF8ohWrXA-kIAYdZcD6sl1HHbj_dH2cCi-pjjOQg,1031
22
22
  ruyi/log/__init__.py,sha256=ehgUl8iY1oRfP_nJKrln5lnvJFSPPU1J-3g-PK28YWk,6516
23
23
  ruyi/mux/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
@@ -28,7 +28,7 @@ ruyi/mux/venv/maker.py,sha256=RzipQWANOpKZxXGG_XiExyoQzHBw-j6wew7Iu6hZwZE,27234
28
28
  ruyi/mux/venv/venv_cli.py,sha256=rIKNcYKu1j7ahHXNgv_ytYVR7qJOoly-TaHPvb21FGA,2974
29
29
  ruyi/mux/venv_cfg.py,sha256=m75JCVLFWE1gE8OzcNDOHqwUc2c6ikJhZ-GhjsXv94U,6840
30
30
  ruyi/pluginhost/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
31
- ruyi/pluginhost/api.py,sha256=yu8zsvjGTFINwdwTQ6vGJPKQvb_9QHkbu8jpfoglNr4,5716
31
+ ruyi/pluginhost/api.py,sha256=Zh69xJfR7PEbM6fjoFZRNqeqTneWAmC71NjA76HodSY,5711
32
32
  ruyi/pluginhost/ctx.py,sha256=MsP1L7nchnbSrauId3GaQzv0YJLnWE0u_H3ZFEmjUX8,6590
33
33
  ruyi/pluginhost/paths.py,sha256=3EVY3_i3LLff4Lk9py-E317C7_ysiRatfCiuxvCsVWw,4227
34
34
  ruyi/pluginhost/plugin_cli.py,sha256=rLdXG4_OJ2nlaSjFBmTaclx_N2f0s_Wi-9qcSeq7h9I,968
@@ -45,14 +45,14 @@ ruyi/ruyipkg/augmented_pkg.py,sha256=QbzYNHCRanFx9xwYRosN_dAEzOSxmOBxyBVli8SC9FE
45
45
  ruyi/ruyipkg/canonical_dump.py,sha256=Qu25YXwJpjWUBte0C3bmmaJxe7zyqfN-2-__u2_dJDM,9363
46
46
  ruyi/ruyipkg/checksum.py,sha256=ChYFPXl7-Y8p_bDerkXOGroRz4k6ejjWY9ViaVWuEgk,1274
47
47
  ruyi/ruyipkg/cli_completion.py,sha256=kJf7vN5rXi5zAwTI3dr4J8HdUzR3-ZXnsdaNQV6kqzo,1151
48
- ruyi/ruyipkg/distfile.py,sha256=mgEZAS8jv9zP_7SSk59B8QCZfcouUf4qZGBnL44B5zg,6895
48
+ ruyi/ruyipkg/distfile.py,sha256=wkyYVcSM2b3vD462TtoltdLcUZElCwWvcZBiPG3lTZ0,7002
49
49
  ruyi/ruyipkg/entity.py,sha256=s8h5kNaR2vsP6v_QUFIO6SZiUrt0jrb8GU11bNT6fn4,14498
50
50
  ruyi/ruyipkg/entity_cli.py,sha256=hF66i3sJX8XVLIWq376GA9vzgegfQy-6s0x0L831Irk,3802
51
51
  ruyi/ruyipkg/entity_provider.py,sha256=jDfS2Jh01PVHo0kb1XyI5WkZgL4fv21AooXLLwqK-1I,8741
52
- ruyi/ruyipkg/fetch.py,sha256=_btz2hkTz0uEUCCSAhOK7tlhAuvzCu42ZTUCP-RpU7U,9047
52
+ ruyi/ruyipkg/fetcher.py,sha256=_btz2hkTz0uEUCCSAhOK7tlhAuvzCu42ZTUCP-RpU7U,9047
53
53
  ruyi/ruyipkg/host.py,sha256=pmqgggi7koDCWgzFexwHpycv4SZ07VF6xUbi4s8FSKA,1399
54
- ruyi/ruyipkg/install.py,sha256=1RpDVEGkni8Z9z-3GVJepDYpF9ADdkh2vXTyG7BZms4,16299
55
- ruyi/ruyipkg/install_cli.py,sha256=BuhGJcrKsJqr63-8AamouY1mXAvjEe2nkxHYuAQ3U0Y,4269
54
+ ruyi/ruyipkg/install.py,sha256=RhJwIqGxMveWrGmpP-2uWpZBRnqkA75HwyHwrNTdqow,18176
55
+ ruyi/ruyipkg/install_cli.py,sha256=joIV5iY4iblDinoH2pgbHNYkMVWqMxtVNj89T0IpJuk,5267
56
56
  ruyi/ruyipkg/list.py,sha256=iO7666xFEWKTDNtJqVLaaQKsp0BfLTzgXBwFFHrqCGc,4172
57
57
  ruyi/ruyipkg/list_cli.py,sha256=9tRWWRcJv3yJqzup6Oc89E3xahGp5jIfybfEvwwd55I,2243
58
58
  ruyi/ruyipkg/list_filter.py,sha256=F64_UhwUEiaUR73EkLu91qoUBA-Yz9mEVWj8XY46MXQ,5467
@@ -61,28 +61,28 @@ ruyi/ruyipkg/news.py,sha256=5uN4UYqvEW5MU_10W9MzwFo3lH25oZCvMMjfAjLed7U,3857
61
61
  ruyi/ruyipkg/news_cli.py,sha256=s5mirEBmO2lZITfjcpzDakP7rNYPqopdM9odbwmAPpg,2378
62
62
  ruyi/ruyipkg/news_store.py,sha256=ocTO4YDpL_HrKaIRFvJ_Gxvpmr7V7-R8b1B8f5Eue9c,5276
63
63
  ruyi/ruyipkg/pkg_manifest.py,sha256=FmksKjQyBnU4zA3MFXiHdB2EjNmhs-J9km9vE-zBQlk,18836
64
- ruyi/ruyipkg/profile.py,sha256=FVppmA6x12m3JfeAWd3Vl9osk4aBprZL4ff7akO018Q,6467
64
+ ruyi/ruyipkg/profile.py,sha256=6xwL24crALShqD8bazGOIAFTYCpM_91mD8d6YMxM2FU,10762
65
65
  ruyi/ruyipkg/profile_cli.py,sha256=ud9MS9JQLOtec_1tFRu669I-imVfi1DHU3AuBJym9mw,848
66
66
  ruyi/ruyipkg/protocols.py,sha256=lPKRaAcK3DY3wmkyO2tKpFvPQ_4QA4aSNNsNLvi78O8,1833
67
- ruyi/ruyipkg/repo.py,sha256=K0kKj333T6vDwhxWneiPm_CVZdwKP-gpOJdfD5rXnnY,25142
68
- ruyi/ruyipkg/state.py,sha256=3DRYyHvm_GOvKFVWZUV0iFVnXdK2vn6_RKBXKxFzK64,11235
69
- ruyi/ruyipkg/unpack.py,sha256=X_MKcxU1bcgIM-th1--CUfmnjQOFp-EqWWpvk27YttA,11028
67
+ ruyi/ruyipkg/repo.py,sha256=l5d4XPZS-ofbwlAOk5GZMM7AFGay1n1xDVzge935oA4,25231
68
+ ruyi/ruyipkg/state.py,sha256=Ie4vrt79Wp9P0L36tMj36tOtDlFnIb6uwVGDQCQyg8s,11544
69
+ ruyi/ruyipkg/unpack.py,sha256=hPd-lOnDXp1lEb4mdbLUEckT4QXblSFxz-dGAdwaAjc,11207
70
70
  ruyi/ruyipkg/unpack_method.py,sha256=MonFFvcDb7MVsi2w4yitnCeZkmWmS7nRMMY-wSt9AMs,2106
71
71
  ruyi/ruyipkg/update_cli.py,sha256=ywsAAUPctJ3_2qPDvFL2__ql9m8tGZ6ZfrwbSk30Xh4,1425
72
72
  ruyi/telemetry/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
73
73
  ruyi/telemetry/aggregate.py,sha256=Ybt25xrg0n62A9Ipslv18EBC-kLkyYsPeEzgguTtChg,1981
74
74
  ruyi/telemetry/event.py,sha256=GZnFj6E59Q7mjp-2VRApAZH3rT_bu4_cWb5QMCPm-Zc,982
75
75
  ruyi/telemetry/node_info.py,sha256=ix-udhoKQI-HV7qVeEbd-jy3ZyhXL62sVA9uX4fowEg,5178
76
- ruyi/telemetry/provider.py,sha256=KKYoic29hWq4u2Bk_usORkT9yQCpREinOLYZXj3eN9k,14832
76
+ ruyi/telemetry/provider.py,sha256=U88ydGga3dwYAi7mfNKmEEyfhAZ4xelF2L-jNAk29IY,16486
77
77
  ruyi/telemetry/scope.py,sha256=e45VPAvRAqSxrL0ESorN9SCnR_I6Bwi2CMPJDDshJEE,1133
78
- ruyi/telemetry/store.py,sha256=7ThCzKF2txT_xU3qgjotOcwRqwNhozIkIyYQ0GSUxBg,8015
78
+ ruyi/telemetry/store.py,sha256=jodRQ7aRye9qYfvc0uaPQ5DLYGmYYuJtb8dnUN9KVs8,8010
79
79
  ruyi/telemetry/telemetry_cli.py,sha256=PBVMUSE3P6IBKQVMji_bueVenCdbchbOlowySXy0468,3364
80
80
  ruyi/utils/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
81
81
  ruyi/utils/ar.py,sha256=w9wiYYdbInLewr5IRTcP0TiOw2ibVDQEmnV0hHm9WlA,2271
82
82
  ruyi/utils/ci.py,sha256=66DBm4ooA7yozDtXCJFd1n2jJXTsEnxPSpkNzLfE28M,2970
83
83
  ruyi/utils/frontmatter.py,sha256=4EOohEYCZ_q6ncpDv7ktJYf9PN4WEdgFfdE9hZBV3Zg,1052
84
- ruyi/utils/git.py,sha256=SlOaS9sR4_0mhNgTXMvrmI8Q7-eh2z8pyhGq_9HuqSU,5893
85
- ruyi/utils/global_mode.py,sha256=A9ehY4z1ckUSMHqiDDlXmJpjPzTajCgsDvDqyTwZZj4,4979
84
+ ruyi/utils/git.py,sha256=YspRRkfxLXluCv4LNx6q_mjkPdoX7WSM9aR7EfudqlM,5991
85
+ ruyi/utils/global_mode.py,sha256=9Y17rVDm157EKvU6GJ8K1rnVTXnYyLN5e_7psObne00,5691
86
86
  ruyi/utils/l10n.py,sha256=l003oQ5M8fWIKQHbYTVSc6oHzFFGU2sbKac7Hh6FNFU,2530
87
87
  ruyi/utils/markdown.py,sha256=Mpq--ClM4j9lm_-5zO53ptYePUTLI4rg0V1YshOwsf8,2654
88
88
  ruyi/utils/mounts.py,sha256=31BGsVOpD2bO7PYpm8I1ogDHzP3MvrMWznKonS3Ajos,1210
@@ -91,12 +91,12 @@ ruyi/utils/porcelain.py,sha256=pF6ieSE2xlnC0HBADFY0m-uuwVNNME3wlbHo2jWdLFA,1403
91
91
  ruyi/utils/prereqs.py,sha256=oWAaH-smpTMQxpHt782YBxqHxrTaheataslN988-a78,2076
92
92
  ruyi/utils/ssl_patch.py,sha256=a5gf4br6nC39wTHsqiFtcJ-mGzqB-YzK6DHSeERLaHQ,5661
93
93
  ruyi/utils/templating.py,sha256=94xBJTkIfDqmUBTc9hnLO54zQoC7hwGWONGF3YbaqHk,966
94
- ruyi/utils/toml.py,sha256=ri97Ki0xhBwoDtrjngCc6hlPXuEsma1ShPFFuFMYqpA,3192
94
+ ruyi/utils/toml.py,sha256=aniIF3SGfR69_s3GWWwlnoKxW4B5IDVY2CM0eUI55_c,3501
95
95
  ruyi/utils/url.py,sha256=Wyct6syS4GmZC6mY7SK-YgBWxKl3cOOBXtp9UtvGkto,186
96
- ruyi/utils/xdg_basedir.py,sha256=vd9p-JZru2DkXvt_W3gCS1_Wj4QkuftErI1MLPaBiz4,2478
97
- ruyi/version.py,sha256=KLJkvKexU07mu-GVDbYKsQvReRvwlVFYkRmcvnyfQNY,2142
98
- ruyi-0.41.0b20250926.dist-info/METADATA,sha256=--zkfVJ7C1CjBf4XAd6gZ2Ke3TB-7Zn5rNlP0D7OME4,23892
99
- ruyi-0.41.0b20250926.dist-info/WHEEL,sha256=zp0Cn7JsFoX2ATtOhtaFYIiE2rmFAD4OcMhtUki8W3U,88
100
- ruyi-0.41.0b20250926.dist-info/entry_points.txt,sha256=GXSNSy7OgFrnlU5xm5dE3l3PGO92Qf6VDIUCdvQNm8E,49
101
- ruyi-0.41.0b20250926.dist-info/licenses/LICENSE-Apache.txt,sha256=xx0jnfkXJvxRnG63LTGOxlggYnIysveWIZ6H3PNdCrQ,11357
102
- ruyi-0.41.0b20250926.dist-info/RECORD,,
96
+ ruyi/utils/xdg_basedir.py,sha256=RwVH199jPcLVsg5ngR62RaNS5hqnMpkdt31LqkCfa1g,2751
97
+ ruyi/version.py,sha256=womuPV6RmxG2TAe5KhRnuQctXR6qLV3be8xJN3QqUdI,595
98
+ ruyi-0.42.0.dist-info/METADATA,sha256=Te5Fou6OsjO0de6Yk3V9Ei6o3jz_2izFKuSgAchaznM,24266
99
+ ruyi-0.42.0.dist-info/WHEEL,sha256=zp0Cn7JsFoX2ATtOhtaFYIiE2rmFAD4OcMhtUki8W3U,88
100
+ ruyi-0.42.0.dist-info/entry_points.txt,sha256=GXSNSy7OgFrnlU5xm5dE3l3PGO92Qf6VDIUCdvQNm8E,49
101
+ ruyi-0.42.0.dist-info/licenses/LICENSE-Apache.txt,sha256=xx0jnfkXJvxRnG63LTGOxlggYnIysveWIZ6H3PNdCrQ,11357
102
+ ruyi-0.42.0.dist-info/RECORD,,
File without changes