witbitz-code 1.6.1__tar.gz → 1.6.2__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 (100) hide show
  1. {witbitz_code-1.6.1/src/witbitz_code.egg-info → witbitz_code-1.6.2}/PKG-INFO +1 -1
  2. {witbitz_code-1.6.1 → witbitz_code-1.6.2}/pyproject.toml +2 -2
  3. {witbitz_code-1.6.1 → witbitz_code-1.6.2}/src/witbitz_code/__init__.py +1 -1
  4. witbitz_code-1.6.2/src/witbitz_code/icons/witbitz-code-wsl.ico +0 -0
  5. witbitz_code-1.6.2/src/witbitz_code/icons/witbitz-code.ico +0 -0
  6. {witbitz_code-1.6.1 → witbitz_code-1.6.2}/src/witbitz_code/wslstart.py +83 -12
  7. {witbitz_code-1.6.1 → witbitz_code-1.6.2/src/witbitz_code.egg-info}/PKG-INFO +1 -1
  8. {witbitz_code-1.6.1 → witbitz_code-1.6.2}/src/witbitz_code.egg-info/SOURCES.txt +2 -0
  9. {witbitz_code-1.6.1 → witbitz_code-1.6.2}/tests/test_wslstart.py +39 -4
  10. {witbitz_code-1.6.1 → witbitz_code-1.6.2}/LICENSE +0 -0
  11. {witbitz_code-1.6.1 → witbitz_code-1.6.2}/README.md +0 -0
  12. {witbitz_code-1.6.1 → witbitz_code-1.6.2}/setup.cfg +0 -0
  13. {witbitz_code-1.6.1 → witbitz_code-1.6.2}/src/witbitz_code/__main__.py +0 -0
  14. {witbitz_code-1.6.1 → witbitz_code-1.6.2}/src/witbitz_code/_js.py +0 -0
  15. {witbitz_code-1.6.1 → witbitz_code-1.6.2}/src/witbitz_code/account.py +0 -0
  16. {witbitz_code-1.6.1 → witbitz_code-1.6.2}/src/witbitz_code/asks.py +0 -0
  17. {witbitz_code-1.6.1 → witbitz_code-1.6.2}/src/witbitz_code/attachments.py +0 -0
  18. {witbitz_code-1.6.1 → witbitz_code-1.6.2}/src/witbitz_code/auto.py +0 -0
  19. {witbitz_code-1.6.1 → witbitz_code-1.6.2}/src/witbitz_code/auto_runner.py +0 -0
  20. {witbitz_code-1.6.1 → witbitz_code-1.6.2}/src/witbitz_code/cli.py +0 -0
  21. {witbitz_code-1.6.1 → witbitz_code-1.6.2}/src/witbitz_code/codekeys.py +0 -0
  22. {witbitz_code-1.6.1 → witbitz_code-1.6.2}/src/witbitz_code/codetools.py +0 -0
  23. {witbitz_code-1.6.1 → witbitz_code-1.6.2}/src/witbitz_code/compress.py +0 -0
  24. {witbitz_code-1.6.1 → witbitz_code-1.6.2}/src/witbitz_code/computers.py +0 -0
  25. {witbitz_code-1.6.1 → witbitz_code-1.6.2}/src/witbitz_code/confidential.py +0 -0
  26. {witbitz_code-1.6.1 → witbitz_code-1.6.2}/src/witbitz_code/confidential_models.json +0 -0
  27. {witbitz_code-1.6.1 → witbitz_code-1.6.2}/src/witbitz_code/connector.py +0 -0
  28. {witbitz_code-1.6.1 → witbitz_code-1.6.2}/src/witbitz_code/devicelink.py +0 -0
  29. {witbitz_code-1.6.1 → witbitz_code-1.6.2}/src/witbitz_code/dirs.py +0 -0
  30. {witbitz_code-1.6.1 → witbitz_code-1.6.2}/src/witbitz_code/folders.py +0 -0
  31. {witbitz_code-1.6.1 → witbitz_code-1.6.2}/src/witbitz_code/gateway.py +0 -0
  32. {witbitz_code-1.6.1 → witbitz_code-1.6.2}/src/witbitz_code/keys.py +0 -0
  33. {witbitz_code-1.6.1 → witbitz_code-1.6.2}/src/witbitz_code/link.py +0 -0
  34. {witbitz_code-1.6.1 → witbitz_code-1.6.2}/src/witbitz_code/mkdir.py +0 -0
  35. {witbitz_code-1.6.1 → witbitz_code-1.6.2}/src/witbitz_code/models.py +0 -0
  36. {witbitz_code-1.6.1 → witbitz_code-1.6.2}/src/witbitz_code/net.py +0 -0
  37. {witbitz_code-1.6.1 → witbitz_code-1.6.2}/src/witbitz_code/offered_models.json +0 -0
  38. {witbitz_code-1.6.1 → witbitz_code-1.6.2}/src/witbitz_code/opencode_plugins/notes-init.md +0 -0
  39. {witbitz_code-1.6.1 → witbitz_code-1.6.2}/src/witbitz_code/opencode_plugins/witbitz-media-server.mjs +0 -0
  40. {witbitz_code-1.6.1 → witbitz_code-1.6.2}/src/witbitz_code/opencode_plugins/witbitz-notes.js +0 -0
  41. {witbitz_code-1.6.1 → witbitz_code-1.6.2}/src/witbitz_code/opencode_plugins/witbitz-progress.js +0 -0
  42. {witbitz_code-1.6.1 → witbitz_code-1.6.2}/src/witbitz_code/outputs.py +0 -0
  43. {witbitz_code-1.6.1 → witbitz_code-1.6.2}/src/witbitz_code/pair.py +0 -0
  44. {witbitz_code-1.6.1 → witbitz_code-1.6.2}/src/witbitz_code/pairings.py +0 -0
  45. {witbitz_code-1.6.1 → witbitz_code-1.6.2}/src/witbitz_code/payers.py +0 -0
  46. {witbitz_code-1.6.1 → witbitz_code-1.6.2}/src/witbitz_code/plugins.py +0 -0
  47. {witbitz_code-1.6.1 → witbitz_code-1.6.2}/src/witbitz_code/policy.py +0 -0
  48. {witbitz_code-1.6.1 → witbitz_code-1.6.2}/src/witbitz_code/ports.py +0 -0
  49. {witbitz_code-1.6.1 → witbitz_code-1.6.2}/src/witbitz_code/qr.py +0 -0
  50. {witbitz_code-1.6.1 → witbitz_code-1.6.2}/src/witbitz_code/receipt.py +0 -0
  51. {witbitz_code-1.6.1 → witbitz_code-1.6.2}/src/witbitz_code/relay.py +0 -0
  52. {witbitz_code-1.6.1 → witbitz_code-1.6.2}/src/witbitz_code/seen.py +0 -0
  53. {witbitz_code-1.6.1 → witbitz_code-1.6.2}/src/witbitz_code/tinfoil_read.py +0 -0
  54. {witbitz_code-1.6.1 → witbitz_code-1.6.2}/src/witbitz_code/tools_catalog.json +0 -0
  55. {witbitz_code-1.6.1 → witbitz_code-1.6.2}/src/witbitz_code/tools_probe.py +0 -0
  56. {witbitz_code-1.6.1 → witbitz_code-1.6.2}/src/witbitz_code/toolsetup.py +0 -0
  57. {witbitz_code-1.6.1 → witbitz_code-1.6.2}/src/witbitz_code/ui.py +0 -0
  58. {witbitz_code-1.6.1 → witbitz_code-1.6.2}/src/witbitz_code/ui_static/app.css +0 -0
  59. {witbitz_code-1.6.1 → witbitz_code-1.6.2}/src/witbitz_code/ui_static/app.js +0 -0
  60. {witbitz_code-1.6.1 → witbitz_code-1.6.2}/src/witbitz_code/ui_static/index.html +0 -0
  61. {witbitz_code-1.6.1 → witbitz_code-1.6.2}/src/witbitz_code/uitools.py +0 -0
  62. {witbitz_code-1.6.1 → witbitz_code-1.6.2}/src/witbitz_code/uninstall.py +0 -0
  63. {witbitz_code-1.6.1 → witbitz_code-1.6.2}/src/witbitz_code/winagent.py +0 -0
  64. {witbitz_code-1.6.1 → witbitz_code-1.6.2}/src/witbitz_code/winbg.py +0 -0
  65. {witbitz_code-1.6.1 → witbitz_code-1.6.2}/src/witbitz_code/winconsole.py +0 -0
  66. {witbitz_code-1.6.1 → witbitz_code-1.6.2}/src/witbitz_code/wizard.py +0 -0
  67. {witbitz_code-1.6.1 → witbitz_code-1.6.2}/src/witbitz_code.egg-info/dependency_links.txt +0 -0
  68. {witbitz_code-1.6.1 → witbitz_code-1.6.2}/src/witbitz_code.egg-info/entry_points.txt +0 -0
  69. {witbitz_code-1.6.1 → witbitz_code-1.6.2}/src/witbitz_code.egg-info/requires.txt +0 -0
  70. {witbitz_code-1.6.1 → witbitz_code-1.6.2}/src/witbitz_code.egg-info/top_level.txt +0 -0
  71. {witbitz_code-1.6.1 → witbitz_code-1.6.2}/tests/test_asks.py +0 -0
  72. {witbitz_code-1.6.1 → witbitz_code-1.6.2}/tests/test_attachments.py +0 -0
  73. {witbitz_code-1.6.1 → witbitz_code-1.6.2}/tests/test_auto.py +0 -0
  74. {witbitz_code-1.6.1 → witbitz_code-1.6.2}/tests/test_cli.py +0 -0
  75. {witbitz_code-1.6.1 → witbitz_code-1.6.2}/tests/test_codekeys.py +0 -0
  76. {witbitz_code-1.6.1 → witbitz_code-1.6.2}/tests/test_codetools.py +0 -0
  77. {witbitz_code-1.6.1 → witbitz_code-1.6.2}/tests/test_confidential.py +0 -0
  78. {witbitz_code-1.6.1 → witbitz_code-1.6.2}/tests/test_connector_e2e.py +0 -0
  79. {witbitz_code-1.6.1 → witbitz_code-1.6.2}/tests/test_devicelink.py +0 -0
  80. {witbitz_code-1.6.1 → witbitz_code-1.6.2}/tests/test_dirs.py +0 -0
  81. {witbitz_code-1.6.1 → witbitz_code-1.6.2}/tests/test_files_and_compress.py +0 -0
  82. {witbitz_code-1.6.1 → witbitz_code-1.6.2}/tests/test_folders.py +0 -0
  83. {witbitz_code-1.6.1 → witbitz_code-1.6.2}/tests/test_outputs.py +0 -0
  84. {witbitz_code-1.6.1 → witbitz_code-1.6.2}/tests/test_pair_flows.py +0 -0
  85. {witbitz_code-1.6.1 → witbitz_code-1.6.2}/tests/test_plugins.py +0 -0
  86. {witbitz_code-1.6.1 → witbitz_code-1.6.2}/tests/test_policy.py +0 -0
  87. {witbitz_code-1.6.1 → witbitz_code-1.6.2}/tests/test_ports.py +0 -0
  88. {witbitz_code-1.6.1 → witbitz_code-1.6.2}/tests/test_ports_win.py +0 -0
  89. {witbitz_code-1.6.1 → witbitz_code-1.6.2}/tests/test_registry.py +0 -0
  90. {witbitz_code-1.6.1 → witbitz_code-1.6.2}/tests/test_relay_codec.py +0 -0
  91. {witbitz_code-1.6.1 → witbitz_code-1.6.2}/tests/test_setup.py +0 -0
  92. {witbitz_code-1.6.1 → witbitz_code-1.6.2}/tests/test_tools_probe.py +0 -0
  93. {witbitz_code-1.6.1 → witbitz_code-1.6.2}/tests/test_toolsetup.py +0 -0
  94. {witbitz_code-1.6.1 → witbitz_code-1.6.2}/tests/test_ui.py +0 -0
  95. {witbitz_code-1.6.1 → witbitz_code-1.6.2}/tests/test_uitools.py +0 -0
  96. {witbitz_code-1.6.1 → witbitz_code-1.6.2}/tests/test_uninstall.py +0 -0
  97. {witbitz_code-1.6.1 → witbitz_code-1.6.2}/tests/test_winagent.py +0 -0
  98. {witbitz_code-1.6.1 → witbitz_code-1.6.2}/tests/test_winbg.py +0 -0
  99. {witbitz_code-1.6.1 → witbitz_code-1.6.2}/tests/test_winconsole.py +0 -0
  100. {witbitz_code-1.6.1 → witbitz_code-1.6.2}/tests/test_windows_files.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: witbitz-code
3
- Version: 1.6.1
3
+ Version: 1.6.2
4
4
  Summary: Reach OpenCode on this computer from the Witbitz Spaces Code section, through an end-to-end sealed relay.
5
5
  Author: Witbitz
6
6
  License-Expression: MIT
@@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"
4
4
 
5
5
  [project]
6
6
  name = "witbitz-code"
7
- version = "1.6.1"
7
+ version = "1.6.2"
8
8
  description = "Reach OpenCode on this computer from the Witbitz Spaces Code section, through an end-to-end sealed relay."
9
9
  readme = "README.md"
10
10
  requires-python = ">=3.10"
@@ -43,7 +43,7 @@ where = ["src"]
43
43
 
44
44
  # Our OpenCode plugins, installed by `serve` (plugins.py) — copies of tools/opencode-plugins, kept current by embed.mjs
45
45
  [tool.setuptools.package-data]
46
- witbitz_code = ["opencode_plugins/*", "confidential_models.json", "offered_models.json", "tools_catalog.json", "ui_static/*"]
46
+ witbitz_code = ["opencode_plugins/*", "confidential_models.json", "offered_models.json", "tools_catalog.json", "ui_static/*", "icons/*"]
47
47
 
48
48
  [tool.pytest.ini_options]
49
49
  testpaths = ["tests"]
@@ -4,4 +4,4 @@ The Python implementation of tools/witbitz-code.mjs (docs/opencode-relay.md phas
4
4
  vectors and cross-implementation tests.
5
5
  """
6
6
 
7
- __version__ = "1.6.1"
7
+ __version__ = "1.6.2"
@@ -19,6 +19,7 @@ from __future__ import annotations
19
19
  import base64
20
20
  import getpass
21
21
  import os
22
+ import re
22
23
  import shutil
23
24
  import subprocess
24
25
  import sys
@@ -50,6 +51,31 @@ def wsl_args(distro: str, user: str, cmd: list[str], verb: list[str] | None = No
50
51
  return _join(parts)
51
52
 
52
53
 
54
+ _SAFE_DIR = re.compile(r"[A-Za-z0-9_./+:@-]+")
55
+
56
+
57
+ def carried_path(path: str | None = None) -> str:
58
+ """This terminal's PATH for a start that runs the command directly — wsl.exe's `--` reads no ~/.bashrc, so a hidden copy
59
+ had only the system folders: no OpenCode (nvm's, ~/.opencode/bin), no witbitz-code (the owner, 2026-10-09: "OpenCode is
60
+ not installed" and "Start when I log in — not available"). Only plain folder names cross the Windows command line."""
61
+ parts = (os.environ.get("PATH", "") if path is None else path).split(":")
62
+ return ":".join(dict.fromkeys(p for p in parts if p and _SAFE_DIR.fullmatch(p) and ":" not in p))
63
+
64
+
65
+ def wsl_command(exe: str, path: str | None = None) -> list[str]:
66
+ """[env, PATH=…, exe] — the command a hidden start runs, with this terminal's PATH."""
67
+ return ["env", "PATH=" + carried_path(path), exe]
68
+
69
+
70
+ def this_program() -> str:
71
+ """witbitz-code on PATH, else the program running now (a hidden copy started before 1.6.2 had no ~/.local/bin)."""
72
+ exe = shutil.which("witbitz-code")
73
+ if exe:
74
+ return exe
75
+ argv0 = os.path.abspath(sys.argv[0]) if sys.argv and sys.argv[0] else ""
76
+ return argv0 if argv0 and os.path.basename(argv0) == "witbitz-code" and os.path.exists(argv0) else ""
77
+
78
+
53
79
  def headless_args(distro: str, user: str, cmd: list[str], verb: list[str]) -> str:
54
80
  """conhost.exe's arguments: no window, then wsl.exe with wsl_args."""
55
81
  return "--headless wsl.exe " + wsl_args(distro, user, cmd, verb)
@@ -59,11 +85,52 @@ def _q(s: str) -> str: # a PowerShell single-quoted string
59
85
  return "'" + s.replace("'", "''") + "'"
60
86
 
61
87
 
62
- def enable_script(name: str, args: str, folder: str = STARTUP, target: str = WSL_EXE) -> str:
63
- """`target` is a PowerShell expression (wsl.exe), or a path made one with _q (native Windows: the launcher)."""
88
+ def enable_script(name: str, args: str, folder: str = STARTUP, target: str = WSL_EXE, icon: str = "") -> str:
89
+ """`target` is a PowerShell expression (wsl.exe), or a path made one with _q (native Windows: the launcher). `icon`: a
90
+ PowerShell expression for an .ico (else the target's own icon — a console window's, for conhost)."""
64
91
  return (f"$p = Join-Path ({folder}) {_q(name)}; $s = (New-Object -ComObject WScript.Shell).CreateShortcut($p); "
65
92
  f"$s.TargetPath = {target}; $s.Arguments = {_q(args)}; $s.WorkingDirectory = $env:USERPROFILE; $s.WindowStyle = 7; "
66
- "$s.Description = 'Witbitz Code'; $s.Save(); 'ok'")
93
+ + (f"if (Test-Path ({icon})) {{ $s.IconLocation = ({icon}) + ',0' }}; " if icon else "")
94
+ + "$s.Description = 'Witbitz Code'; $s.Save(); 'ok'")
95
+
96
+
97
+ # The shortcuts' icons (icons/ in this package; the owner, 2026-10-09: the WSL entry "has a terminal icon"). WSL's is
98
+ # copied to a Windows folder of its own — not under %LOCALAPPDATA%\Witbitz, which the Windows copy's uninstall deletes.
99
+ ICON_DIR = "(Join-Path $env:LOCALAPPDATA 'Witbitz-WSL')"
100
+ WSL_ICON = f"(Join-Path {ICON_DIR} 'witbitz-code-wsl.ico')"
101
+
102
+
103
+ def icon_file(name: str) -> str:
104
+ return os.path.join(os.path.dirname(os.path.abspath(__file__)), "icons", name)
105
+
106
+
107
+ def linux_path(windows: str, run: Callable = subprocess.run) -> str:
108
+ """wslpath -u — '' when it cannot say."""
109
+ try:
110
+ r = run(["wslpath", "-u", windows], capture_output=True, text=True, timeout=10)
111
+ return (r.stdout or "").strip() if r.returncode == 0 else ""
112
+ except (OSError, subprocess.SubprocessError):
113
+ return ""
114
+
115
+
116
+ def copy_icon(*, ps: Callable[[str], tuple[int, str]] | None = None, to_linux: Callable[[str], str] = linux_path,
117
+ src: str | None = None) -> bool:
118
+ """Put WSL's icon in its Windows folder, written from THIS side through /mnt/c (Windows could not read the
119
+ \\\\wsl.localhost path of the package on the owner's laptop: "does not exist"). → whether it is there now."""
120
+ src = src or icon_file("witbitz-code-wsl.ico")
121
+ code, out = (ps or run_ps)("$env:LOCALAPPDATA")
122
+ local = out.splitlines()[-1].strip() if code == 0 and out else ""
123
+ if not re.fullmatch(r"[A-Za-z]:\\[^\x00-\x1f]+", local): # only a real Windows folder ("C:\Users\…\Local")
124
+ return False
125
+ dest_dir = to_linux(local)
126
+ if not dest_dir or not os.path.isabs(dest_dir) or not os.path.exists(src):
127
+ return False
128
+ try:
129
+ os.makedirs(os.path.join(dest_dir, "Witbitz-WSL"), exist_ok=True)
130
+ shutil.copyfile(src, os.path.join(dest_dir, "Witbitz-WSL", "witbitz-code-wsl.ico"))
131
+ return True
132
+ except OSError:
133
+ return False
67
134
 
68
135
 
69
136
  def state_script(name: str, folder: str = STARTUP) -> str:
@@ -101,11 +168,12 @@ def startup_here() -> dict | None:
101
168
  """What the WSL shortcut would be — None off WSL, with no Windows PowerShell to write it, or no witbitz-code on PATH."""
102
169
  if not ports.on_wsl() or not (os.path.exists(POWERSHELL) or shutil.which("powershell.exe")):
103
170
  return None
104
- exe = shutil.which("witbitz-code")
171
+ exe = this_program()
105
172
  if not exe:
106
173
  return None
107
174
  distro = os.environ.get("WSL_DISTRO_NAME", "")
108
- return {"name": shortcut_name(distro), "args": headless_args(distro, getpass.getuser(), [exe], UI_QUIET), "target": CONHOST}
175
+ return {"name": shortcut_name(distro), "args": headless_args(distro, getpass.getuser(), wsl_command(exe), UI_QUIET), "target": CONHOST,
176
+ "icon": WSL_ICON}
109
177
 
110
178
 
111
179
  def menu_name(distro: str) -> str:
@@ -119,7 +187,7 @@ def launch_script(args: str) -> str:
119
187
 
120
188
 
121
189
  def launch_background(*, ps: Callable[[str], tuple[int, str]] = run_ps, which: Callable[[str], str | None] = shutil.which,
122
- on_wsl: Callable[[], bool] = ports.on_wsl) -> tuple[bool, str]:
190
+ on_wsl: Callable[[], bool] = ports.on_wsl, copy: Callable[..., bool] | None = None) -> tuple[bool, str]:
123
191
  """WSL: start the page's manager with no window, held by a hidden wsl.exe on the Windows side, and let it open the
124
192
  page in the Windows browser — setup's hand-off (this terminal may then close). → (ok, why)."""
125
193
  if not on_wsl():
@@ -127,14 +195,16 @@ def launch_background(*, ps: Callable[[str], tuple[int, str]] = run_ps, which: C
127
195
  exe = which("witbitz-code")
128
196
  if not exe:
129
197
  return False, "witbitz-code is not on PATH"
198
+ copy = copy or copy_icon
130
199
  distro = os.environ.get("WSL_DISTRO_NAME", "")
131
- args = headless_args(distro, getpass.getuser(), [exe], ["ui", "--hidden"])
200
+ args = headless_args(distro, getpass.getuser(), wsl_command(exe), ["ui", "--hidden"])
132
201
  # The Start-menu entry first (it opens this page again, starting it if it is not running), then the start itself.
133
- menu = enable_script(menu_name(distro), args, folder=PROGRAMS, target=CONHOST)
202
+ icon = WSL_ICON if copy(ps=ps) else ""
203
+ menu = enable_script(menu_name(distro), args, folder=PROGRAMS, target=CONHOST, icon=icon)
134
204
  # A login start written by 1.4.0–1.5.x opens a minimized window: an existing one is rewritten to start hidden too.
135
- quiet = headless_args(distro, getpass.getuser(), [exe], UI_QUIET)
205
+ quiet = headless_args(distro, getpass.getuser(), wsl_command(exe), UI_QUIET)
136
206
  login = (f"if (Test-Path (Join-Path ({STARTUP}) {_q(shortcut_name(distro))})) {{ "
137
- f"{enable_script(shortcut_name(distro), quiet, target=CONHOST)} | Out-Null }}")
207
+ f"{enable_script(shortcut_name(distro), quiet, target=CONHOST, icon=icon)} | Out-Null }}")
138
208
  code, out = ps(f"try {{ {menu} | Out-Null; {login} }} catch {{ }}; {launch_script(args)}")
139
209
  ok = code == 0 and bool(out) and out.splitlines()[-1].strip() == "ok"
140
210
  return ok, "" if ok else ("Windows PowerShell could not be started" if code == -1 else f"Windows PowerShell answered {code}")
@@ -148,7 +218,8 @@ def windows_startup_spec(env: dict | None = None, which: Callable[[str], str | N
148
218
  env = os.environ if env is None else env
149
219
  pyw = os.path.join(os.path.dirname(executable or sys.executable), "pythonw.exe")
150
220
  if exists(pyw):
151
- return {"name": "Witbitz Code.lnk", "args": _join(["-m", "witbitz_code", *WIN_QUIET]), "target": _q(pyw)}
221
+ return {"name": "Witbitz Code.lnk", "args": _join(["-m", "witbitz_code", *WIN_QUIET]), "target": _q(pyw),
222
+ "icon": _q(icon_file("witbitz-code.ico"))}
152
223
  launcher = os.path.join(env.get("LOCALAPPDATA", ""), "Witbitz", "Witbitz Code.cmd")
153
224
  if env.get("LOCALAPPDATA") and exists(launcher):
154
225
  return {"name": "Witbitz Code.lnk", "args": "--no-browser", "target": _q(launcher)}
@@ -171,7 +242,7 @@ def startup_state(spec: dict, *, ps: Callable[[str], tuple[int, str]] = run_ps)
171
242
 
172
243
 
173
244
  def startup_enable(spec: dict, *, ps: Callable[[str], tuple[int, str]] = run_ps) -> tuple[bool, str]:
174
- code, out = ps(enable_script(spec["name"], spec["args"], target=spec.get("target", WSL_EXE)))
245
+ code, out = ps(enable_script(spec["name"], spec["args"], target=spec.get("target", WSL_EXE), icon=spec.get("icon", "")))
175
246
  ok = code == 0 and out.splitlines()[-1].strip() == "ok" if out else False
176
247
  return ok, "" if ok else (f"Windows PowerShell answered {code}" if code != -1 else "Windows PowerShell could not be started")
177
248
 
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: witbitz-code
3
- Version: 1.6.1
3
+ Version: 1.6.2
4
4
  Summary: Reach OpenCode on this computer from the Witbitz Spaces Code section, through an end-to-end sealed relay.
5
5
  Author: Witbitz
6
6
  License-Expression: MIT
@@ -56,6 +56,8 @@ src/witbitz_code.egg-info/dependency_links.txt
56
56
  src/witbitz_code.egg-info/entry_points.txt
57
57
  src/witbitz_code.egg-info/requires.txt
58
58
  src/witbitz_code.egg-info/top_level.txt
59
+ src/witbitz_code/icons/witbitz-code-wsl.ico
60
+ src/witbitz_code/icons/witbitz-code.ico
59
61
  src/witbitz_code/opencode_plugins/notes-init.md
60
62
  src/witbitz_code/opencode_plugins/witbitz-media-server.mjs
61
63
  src/witbitz_code/opencode_plugins/witbitz-notes.js
@@ -47,7 +47,8 @@ def test_the_windows_login_start_runs_pythonw_in_the_background_when_it_is_there
47
47
  exe = r"C:\Users\a\AppData\Local\Witbitz\tools\witbitz-code\Scripts\python.exe"
48
48
  pyw = __import__("os").path.join(__import__("os").path.dirname(exe), "pythonw.exe")
49
49
  spec = ws.windows_startup_spec(env={"LOCALAPPDATA": r"C:\Users\a\AppData\Local"}, exists=lambda p: p == pyw, executable=exe)
50
- assert spec == {"name": "Witbitz Code.lnk", "args": "-m witbitz_code ui --no-browser", "target": "'" + pyw + "'"}
50
+ assert {k: spec[k] for k in ("name", "args", "target")} == {"name": "Witbitz Code.lnk", "args": "-m witbitz_code ui --no-browser", "target": "'" + pyw + "'"}
51
+ assert spec["icon"].endswith("witbitz-code.ico'"), "the robot, not the launcher's icon"
51
52
  launcher_only = ws.windows_startup_spec(env={"LOCALAPPDATA": "L"}, exists=lambda p: p.endswith("Witbitz Code.cmd"), executable=exe)
52
53
  assert launcher_only["args"] == "--no-browser" and launcher_only["target"].endswith("Witbitz Code.cmd'")
53
54
 
@@ -56,12 +57,46 @@ def test_wsl_runs_headless_held_by_a_hidden_wsl_exe():
56
57
  from witbitz_code import wslstart as ws
57
58
  seen = []
58
59
  ok, why = ws.launch_background(ps=lambda script: seen.append(script) or (0, "ok"), which=lambda n: "/home/a/.local/bin/witbitz-code",
59
- on_wsl=lambda: True)
60
+ on_wsl=lambda: True, copy=lambda ps: False)
60
61
  assert ok and why == ""
61
62
  assert "GetFolderPath('Programs')" in seen[0] and "'Witbitz Code (WSL " in seen[0], "its own Start-menu entry"
62
63
  assert seen[0].index("Programs") < seen[0].index("Start-Process"), "the entry is written before the start"
63
64
  assert "if (Test-Path (Join-Path ([Environment]::GetFolderPath('Startup'))" in seen[0], "an older login start is rewritten hidden"
64
- assert "conhost.exe" in seen[0] and "'--headless wsl.exe " in seen[0] and "-- /home/a/.local/bin/witbitz-code ui --hidden'" in seen[0]
65
+ assert "conhost.exe" in seen[0] and "'--headless wsl.exe " in seen[0] and " /home/a/.local/bin/witbitz-code ui --hidden'" in seen[0] and "-- env PATH=" in seen[0]
65
66
  assert ws.launch_background(ps=lambda s: (0, "ok"), on_wsl=lambda: False) == (False, "not in WSL")
66
- assert ws.launch_background(ps=lambda s: (1, ""), which=lambda n: "/w", on_wsl=lambda: True) == (False, "Windows PowerShell answered 1")
67
+ assert ws.launch_background(ps=lambda s: (1, ""), which=lambda n: "/w", on_wsl=lambda: True, copy=lambda ps: False) == (False, "Windows PowerShell answered 1")
67
68
  assert ws.headless_args("UbuntuD", "u", ["/w"], ws.UI_QUIET) == "--headless wsl.exe -d UbuntuD -u u --cd ~ -- /w ui --no-browser --hidden"
69
+
70
+
71
+ def test_a_hidden_start_carries_this_terminal_s_path():
72
+ """wsl.exe -- reads no ~/.bashrc: the hidden copy found no OpenCode (nvm's) and no witbitz-code (the owner, 2026-10-09)."""
73
+ from witbitz_code import wslstart as ws
74
+ path = "/home/u/.nvm/versions/node/v24.3.0/bin:/home/u/.local/bin:/usr/bin:/mnt/c/Program Files/Git/cmd:/mnt/c/WINDOWS/system32:/usr/bin::/x;rm"
75
+ assert ws.carried_path(path) == "/home/u/.nvm/versions/node/v24.3.0/bin:/home/u/.local/bin:/usr/bin:/mnt/c/WINDOWS/system32"
76
+ assert ws.wsl_command("/home/u/.local/bin/witbitz-code", path)[:2] == ["env", "PATH=" + ws.carried_path(path)]
77
+
78
+
79
+ def test_the_shortcuts_carry_witbitz_icons_and_wsl_copies_its_own_to_windows(monkeypatch):
80
+ """The owner, 2026-10-09: the WSL entry "has a terminal icon" (conhost's)."""
81
+ import os
82
+ from witbitz_code import wslstart as ws
83
+ assert os.path.getsize(ws.icon_file("witbitz-code.ico")) > 1000 and os.path.getsize(ws.icon_file("witbitz-code-wsl.ico")) > 1000
84
+ s = ws.enable_script("x.lnk", "-a", icon=ws.WSL_ICON)
85
+ assert "if (Test-Path ((Join-Path (Join-Path $env:LOCALAPPDATA 'Witbitz-WSL') 'witbitz-code-wsl.ico')))" in s and "IconLocation" in s
86
+ assert "IconLocation" not in ws.enable_script("x.lnk", "-a"), "no icon asked → the script is as before (Node parity)"
87
+ seen = []
88
+ ws.launch_background(ps=lambda script: seen.append(script) or (0, "ok"), which=lambda n: "/w", on_wsl=lambda: True, copy=lambda ps: True)
89
+ assert "IconLocation" in seen[0] and "Witbitz-WSL" in seen[0]
90
+ seen.clear()
91
+ ws.launch_background(ps=lambda script: seen.append(script) or (0, "ok"), which=lambda n: "/w", on_wsl=lambda: True, copy=lambda ps: False)
92
+ assert "IconLocation" not in seen[0], "no icon copied → the shortcut keeps its own icon"
93
+
94
+
95
+ def test_the_icon_is_written_into_windows_local_app_data_from_this_side(tmp_path):
96
+ from witbitz_code import wslstart as ws
97
+ win = tmp_path / "Local"
98
+ win.mkdir()
99
+ ok = ws.copy_icon(ps=lambda s: (0, "C:\\Users\\a\\AppData\\Local"), to_linux=lambda p: str(win) if p.endswith("Local") else "")
100
+ assert ok and (win / "Witbitz-WSL" / "witbitz-code-wsl.ico").read_bytes() == open(ws.icon_file("witbitz-code-wsl.ico"), "rb").read()
101
+ assert ws.copy_icon(ps=lambda s: (-1, ""), to_linux=lambda p: str(win)) is False
102
+ assert ws.copy_icon(ps=lambda s: (0, "ok"), to_linux=lambda p: "ok") is False, "not a Windows folder: never written"
File without changes
File without changes
File without changes