cli-tools-kit 0.6.3__tar.gz → 0.6.4__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 (41) hide show
  1. {cli_tools_kit-0.6.3 → cli_tools_kit-0.6.4}/PKG-INFO +1 -1
  2. {cli_tools_kit-0.6.3 → cli_tools_kit-0.6.4}/cli_tools_kit/gui_installer.py +37 -3
  3. {cli_tools_kit-0.6.3 → cli_tools_kit-0.6.4}/cli_tools_kit/sources.py +9 -1
  4. {cli_tools_kit-0.6.3 → cli_tools_kit-0.6.4}/cli_tools_kit.egg-info/PKG-INFO +1 -1
  5. {cli_tools_kit-0.6.3 → cli_tools_kit-0.6.4}/pyproject.toml +1 -1
  6. {cli_tools_kit-0.6.3 → cli_tools_kit-0.6.4}/tests/test_sources.py +15 -0
  7. {cli_tools_kit-0.6.3 → cli_tools_kit-0.6.4}/LICENSE +0 -0
  8. {cli_tools_kit-0.6.3 → cli_tools_kit-0.6.4}/README.md +0 -0
  9. {cli_tools_kit-0.6.3 → cli_tools_kit-0.6.4}/cli_tools_kit/__init__.py +0 -0
  10. {cli_tools_kit-0.6.3 → cli_tools_kit-0.6.4}/cli_tools_kit/__main__.py +0 -0
  11. {cli_tools_kit-0.6.3 → cli_tools_kit-0.6.4}/cli_tools_kit/advertise.py +0 -0
  12. {cli_tools_kit-0.6.3 → cli_tools_kit-0.6.4}/cli_tools_kit/cron_installer.py +0 -0
  13. {cli_tools_kit-0.6.3 → cli_tools_kit-0.6.4}/cli_tools_kit/host.py +0 -0
  14. {cli_tools_kit-0.6.3 → cli_tools_kit-0.6.4}/cli_tools_kit/identity.py +0 -0
  15. {cli_tools_kit-0.6.3 → cli_tools_kit-0.6.4}/cli_tools_kit/onboarding.py +0 -0
  16. {cli_tools_kit-0.6.3 → cli_tools_kit-0.6.4}/cli_tools_kit/skills.py +0 -0
  17. {cli_tools_kit-0.6.3 → cli_tools_kit-0.6.4}/cli_tools_kit/taxonomy/__init__.py +0 -0
  18. {cli_tools_kit-0.6.3 → cli_tools_kit-0.6.4}/cli_tools_kit/taxonomy/build.py +0 -0
  19. {cli_tools_kit-0.6.3 → cli_tools_kit-0.6.4}/cli_tools_kit/taxonomy/capability.py +0 -0
  20. {cli_tools_kit-0.6.3 → cli_tools_kit-0.6.4}/cli_tools_kit/taxonomy/cluster.py +0 -0
  21. {cli_tools_kit-0.6.3 → cli_tools_kit-0.6.4}/cli_tools_kit/taxonomy/corpus.py +0 -0
  22. {cli_tools_kit-0.6.3 → cli_tools_kit-0.6.4}/cli_tools_kit/taxonomy/embedder.py +0 -0
  23. {cli_tools_kit-0.6.3 → cli_tools_kit-0.6.4}/cli_tools_kit/taxonomy/groups.py +0 -0
  24. {cli_tools_kit-0.6.3 → cli_tools_kit-0.6.4}/cli_tools_kit/taxonomy/llm_groups.py +0 -0
  25. {cli_tools_kit-0.6.3 → cli_tools_kit-0.6.4}/cli_tools_kit/tool_installer.py +0 -0
  26. {cli_tools_kit-0.6.3 → cli_tools_kit-0.6.4}/cli_tools_kit/tui_installer.py +0 -0
  27. {cli_tools_kit-0.6.3 → cli_tools_kit-0.6.4}/cli_tools_kit.egg-info/SOURCES.txt +0 -0
  28. {cli_tools_kit-0.6.3 → cli_tools_kit-0.6.4}/cli_tools_kit.egg-info/dependency_links.txt +0 -0
  29. {cli_tools_kit-0.6.3 → cli_tools_kit-0.6.4}/cli_tools_kit.egg-info/entry_points.txt +0 -0
  30. {cli_tools_kit-0.6.3 → cli_tools_kit-0.6.4}/cli_tools_kit.egg-info/requires.txt +0 -0
  31. {cli_tools_kit-0.6.3 → cli_tools_kit-0.6.4}/cli_tools_kit.egg-info/top_level.txt +0 -0
  32. {cli_tools_kit-0.6.3 → cli_tools_kit-0.6.4}/setup.cfg +0 -0
  33. {cli_tools_kit-0.6.3 → cli_tools_kit-0.6.4}/tests/test_capability_groups.py +0 -0
  34. {cli_tools_kit-0.6.3 → cli_tools_kit-0.6.4}/tests/test_cron_installer.py +0 -0
  35. {cli_tools_kit-0.6.3 → cli_tools_kit-0.6.4}/tests/test_host.py +0 -0
  36. {cli_tools_kit-0.6.3 → cli_tools_kit-0.6.4}/tests/test_identity.py +0 -0
  37. {cli_tools_kit-0.6.3 → cli_tools_kit-0.6.4}/tests/test_llm_groups.py +0 -0
  38. {cli_tools_kit-0.6.3 → cli_tools_kit-0.6.4}/tests/test_skills.py +0 -0
  39. {cli_tools_kit-0.6.3 → cli_tools_kit-0.6.4}/tests/test_taxonomy_cluster.py +0 -0
  40. {cli_tools_kit-0.6.3 → cli_tools_kit-0.6.4}/tests/test_tool_installer.py +0 -0
  41. {cli_tools_kit-0.6.3 → cli_tools_kit-0.6.4}/tests/test_tui_installer.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: cli-tools-kit
3
- Version: 0.6.3
3
+ Version: 0.6.4
4
4
  Summary: Installer protocol + helpers for self-installing Python CLI/GUI tools (desktop shortcuts, bash aliases, cron entries), plus reusable tkinter and curses installer screens
5
5
  Author: Steffen Probst
6
6
  License: MIT License
@@ -5434,13 +5434,37 @@ class InstallerApp:
5434
5434
  # ================= CLI FUNCTIONS =================
5435
5435
 
5436
5436
  def _self_shortcut_path() -> str:
5437
- """The manager's own shortcut: a .desktop file, or a .lnk on Windows."""
5437
+ """The manager's own shortcut: a .desktop file, or a .lnk on Windows.
5438
+
5439
+ Windows shows the file name in the Start Menu, where freedesktop reads the
5440
+ Name= line inside the file, so the .lnk is named after SELF_DESKTOP_NAME.
5441
+ Characters a file name cannot hold become spaces, and a name that is empty
5442
+ or all-punctuation falls back to the desktop file's stem.
5443
+ """
5438
5444
  if host.IS_WINDOWS:
5439
- return os.path.join(APPS_DIR,
5440
- os.path.splitext(SELF_DESKTOP_FILE)[0] + ".lnk")
5445
+ stem = "".join(" " if c in '<>:"/\\|?*' else c
5446
+ for c in SELF_DESKTOP_NAME).strip(" .")
5447
+ stem = " ".join(stem.split()) or os.path.splitext(SELF_DESKTOP_FILE)[0]
5448
+ return os.path.join(APPS_DIR, stem + ".lnk")
5441
5449
  return os.path.join(APPS_DIR, SELF_DESKTOP_FILE)
5442
5450
 
5443
5451
 
5452
+ def _windows_icon(icon: str) -> Optional[str]:
5453
+ """The .ico Windows can draw for `icon`, or None.
5454
+
5455
+ A .lnk renders only .ico/.exe/.dll; the shell stores a .png path without
5456
+ complaint and then draws nothing. Consumers configure one icon, normally a
5457
+ .png for freedesktop, so take a sibling .ico of the same stem when there is
5458
+ one and otherwise leave the shortcut on its default picture.
5459
+ """
5460
+ if not icon:
5461
+ return None
5462
+ for candidate in (icon, os.path.splitext(icon)[0] + ".ico"):
5463
+ if candidate.lower().endswith(".ico") and os.path.isfile(candidate):
5464
+ return candidate
5465
+ return None
5466
+
5467
+
5444
5468
  def cli_install_self(quiet: bool = False) -> tuple[bool, str]:
5445
5469
  """Install the manager's own desktop shortcut. Returns (success, message)."""
5446
5470
  try:
@@ -5450,9 +5474,19 @@ def cli_install_self(quiet: bool = False) -> tuple[bool, str]:
5450
5474
  if host.IS_WINDOWS:
5451
5475
  lnk_path = _self_shortcut_path()
5452
5476
  ok = host.write_shortcut(lnk_path, python_exec, f'"{script_path}"',
5477
+ icon=_windows_icon(SELF_DESKTOP_ICON),
5453
5478
  workdir=ROOT_DIR)
5454
5479
  if not ok:
5455
5480
  return False, f"Could not write {lnk_path}"
5481
+ # Before 0.6.4 the .lnk was named after the desktop file's stem.
5482
+ # Drop that one, or the Start Menu keeps both.
5483
+ legacy = os.path.join(APPS_DIR,
5484
+ os.path.splitext(SELF_DESKTOP_FILE)[0] + ".lnk")
5485
+ if legacy != lnk_path and os.path.isfile(legacy):
5486
+ try:
5487
+ os.remove(legacy)
5488
+ except OSError:
5489
+ pass
5456
5490
  if not quiet:
5457
5491
  print(f"Installed: {lnk_path}")
5458
5492
  return True, lnk_path
@@ -157,7 +157,15 @@ def save_local_root(config_path, root, local_path=None, log: Callable = print) -
157
157
  except OSError as exc:
158
158
  log(f"{local.name}: not updated ({exc})")
159
159
  return False
160
- line = f'root = "{root}"\n'
160
+ # A TOML literal string, because a Windows root is full of backslashes and
161
+ # a basic string would read them as escapes: "C:\Users\..." dies on \U and
162
+ # takes the whole file with it, so the question would be asked again on
163
+ # every launch. A path holding a single quote falls back to a basic string
164
+ # with the two characters TOML needs escaped there.
165
+ if "'" in root:
166
+ line = 'root = "{}"\n'.format(root.replace("\\", "\\\\").replace('"', '\\"'))
167
+ else:
168
+ line = f"root = '{root}'\n"
161
169
  try:
162
170
  local.write_text(line + ("\n" + existing.lstrip("\n") if existing.strip() else ""),
163
171
  encoding="utf-8")
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: cli-tools-kit
3
- Version: 0.6.3
3
+ Version: 0.6.4
4
4
  Summary: Installer protocol + helpers for self-installing Python CLI/GUI tools (desktop shortcuts, bash aliases, cron entries), plus reusable tkinter and curses installer screens
5
5
  Author: Steffen Probst
6
6
  License: MIT License
@@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"
4
4
 
5
5
  [project]
6
6
  name = "cli-tools-kit"
7
- version = "0.6.3"
7
+ version = "0.6.4"
8
8
  description = "Installer protocol + helpers for self-installing Python CLI/GUI tools (desktop shortcuts, bash aliases, cron entries), plus reusable tkinter and curses installer screens"
9
9
  readme = "README.md"
10
10
  requires-python = ">=3.10"
@@ -445,6 +445,21 @@ def test_an_existing_root_in_the_local_file_is_never_overwritten(tmp_path: Path)
445
445
  assert sources.local_root(config) == str(tmp_path / "mine")
446
446
 
447
447
 
448
+ def test_a_windows_root_survives_the_round_trip(tmp_path: Path) -> None:
449
+ """A backslash path must not be written as a TOML basic string.
450
+
451
+ ``root = "C:\\Users\\..."`` reads the \\U as a Unicode escape and kills the
452
+ whole file, so the install location was asked for again on every launch.
453
+ """
454
+ roots = [r"C:\Users\tester\WW3-tools", r"C:\temp\new\table\unicode",
455
+ r"C:\Users\o'brien\tools"]
456
+ for i, root in enumerate(roots):
457
+ config = _write(tmp_path / str(i) / "installer.toml", "")
458
+ assert sources.save_local_root(config, root, log=lambda *a: None)
459
+ local = config.with_name("installer.local.toml")
460
+ assert sources._read_toml(local).get("root") == root
461
+
462
+
448
463
  def test_the_default_root_name_names_the_suggested_folder(tmp_path: Path, engine,
449
464
  monkeypatch) -> None:
450
465
  config = _tree(tmp_path)
File without changes
File without changes
File without changes