witbitz-code 1.10.1__tar.gz → 1.11.0__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 (119) hide show
  1. {witbitz_code-1.10.1/src/witbitz_code.egg-info → witbitz_code-1.11.0}/PKG-INFO +1 -1
  2. {witbitz_code-1.10.1 → witbitz_code-1.11.0}/pyproject.toml +1 -1
  3. {witbitz_code-1.10.1 → witbitz_code-1.11.0}/src/witbitz_code/__init__.py +1 -1
  4. witbitz_code-1.11.0/src/witbitz_code/app.py +101 -0
  5. {witbitz_code-1.10.1 → witbitz_code-1.11.0}/src/witbitz_code/attachments.py +2 -1
  6. {witbitz_code-1.10.1 → witbitz_code-1.11.0}/src/witbitz_code/cli.py +21 -5
  7. {witbitz_code-1.10.1 → witbitz_code-1.11.0}/src/witbitz_code/connector.py +2 -1
  8. {witbitz_code-1.10.1 → witbitz_code-1.11.0}/src/witbitz_code/net.py +2 -1
  9. {witbitz_code-1.10.1 → witbitz_code-1.11.0}/src/witbitz_code/pair.py +7 -0
  10. {witbitz_code-1.10.1 → witbitz_code-1.11.0}/src/witbitz_code/pairings.py +11 -7
  11. {witbitz_code-1.10.1 → witbitz_code-1.11.0}/src/witbitz_code/selfupdate.py +3 -2
  12. {witbitz_code-1.10.1 → witbitz_code-1.11.0}/src/witbitz_code/ui.py +5 -4
  13. {witbitz_code-1.10.1 → witbitz_code-1.11.0}/src/witbitz_code/ui_static/app.js +4 -0
  14. {witbitz_code-1.10.1 → witbitz_code-1.11.0}/src/witbitz_code/ui_static/index.html +1 -1
  15. {witbitz_code-1.10.1 → witbitz_code-1.11.0}/src/witbitz_code/uninstall.py +2 -1
  16. {witbitz_code-1.10.1 → witbitz_code-1.11.0}/src/witbitz_code/wslstart.py +9 -8
  17. {witbitz_code-1.10.1 → witbitz_code-1.11.0/src/witbitz_code.egg-info}/PKG-INFO +1 -1
  18. {witbitz_code-1.10.1 → witbitz_code-1.11.0}/src/witbitz_code.egg-info/SOURCES.txt +1 -0
  19. {witbitz_code-1.10.1 → witbitz_code-1.11.0}/tests/test_pair_flows.py +35 -7
  20. {witbitz_code-1.10.1 → witbitz_code-1.11.0}/tests/test_wslstart.py +15 -0
  21. {witbitz_code-1.10.1 → witbitz_code-1.11.0}/LICENSE +0 -0
  22. {witbitz_code-1.10.1 → witbitz_code-1.11.0}/README.md +0 -0
  23. {witbitz_code-1.10.1 → witbitz_code-1.11.0}/setup.cfg +0 -0
  24. {witbitz_code-1.10.1 → witbitz_code-1.11.0}/src/witbitz_code/__main__.py +0 -0
  25. {witbitz_code-1.10.1 → witbitz_code-1.11.0}/src/witbitz_code/_js.py +0 -0
  26. {witbitz_code-1.10.1 → witbitz_code-1.11.0}/src/witbitz_code/account.py +0 -0
  27. {witbitz_code-1.10.1 → witbitz_code-1.11.0}/src/witbitz_code/asks.py +0 -0
  28. {witbitz_code-1.10.1 → witbitz_code-1.11.0}/src/witbitz_code/auto.py +0 -0
  29. {witbitz_code-1.10.1 → witbitz_code-1.11.0}/src/witbitz_code/auto_runner.py +0 -0
  30. {witbitz_code-1.10.1 → witbitz_code-1.11.0}/src/witbitz_code/browse.py +0 -0
  31. {witbitz_code-1.10.1 → witbitz_code-1.11.0}/src/witbitz_code/browsetree.py +0 -0
  32. {witbitz_code-1.10.1 → witbitz_code-1.11.0}/src/witbitz_code/codekeys.py +0 -0
  33. {witbitz_code-1.10.1 → witbitz_code-1.11.0}/src/witbitz_code/codetools.py +0 -0
  34. {witbitz_code-1.10.1 → witbitz_code-1.11.0}/src/witbitz_code/compress.py +0 -0
  35. {witbitz_code-1.10.1 → witbitz_code-1.11.0}/src/witbitz_code/computers.py +0 -0
  36. {witbitz_code-1.10.1 → witbitz_code-1.11.0}/src/witbitz_code/confidential.py +0 -0
  37. {witbitz_code-1.10.1 → witbitz_code-1.11.0}/src/witbitz_code/confidential_models.json +0 -0
  38. {witbitz_code-1.10.1 → witbitz_code-1.11.0}/src/witbitz_code/devicelink.py +0 -0
  39. {witbitz_code-1.10.1 → witbitz_code-1.11.0}/src/witbitz_code/dirs.py +0 -0
  40. {witbitz_code-1.10.1 → witbitz_code-1.11.0}/src/witbitz_code/folders.py +0 -0
  41. {witbitz_code-1.10.1 → witbitz_code-1.11.0}/src/witbitz_code/gateway.py +0 -0
  42. {witbitz_code-1.10.1 → witbitz_code-1.11.0}/src/witbitz_code/icons/witbitz-code-wsl.ico +0 -0
  43. {witbitz_code-1.10.1 → witbitz_code-1.11.0}/src/witbitz_code/icons/witbitz-code.ico +0 -0
  44. {witbitz_code-1.10.1 → witbitz_code-1.11.0}/src/witbitz_code/images.py +0 -0
  45. {witbitz_code-1.10.1 → witbitz_code-1.11.0}/src/witbitz_code/keys.py +0 -0
  46. {witbitz_code-1.10.1 → witbitz_code-1.11.0}/src/witbitz_code/link.py +0 -0
  47. {witbitz_code-1.10.1 → witbitz_code-1.11.0}/src/witbitz_code/mkdir.py +0 -0
  48. {witbitz_code-1.10.1 → witbitz_code-1.11.0}/src/witbitz_code/models.py +0 -0
  49. {witbitz_code-1.10.1 → witbitz_code-1.11.0}/src/witbitz_code/offered_models.json +0 -0
  50. {witbitz_code-1.10.1 → witbitz_code-1.11.0}/src/witbitz_code/opencode_plugins/notes-init.md +0 -0
  51. {witbitz_code-1.10.1 → witbitz_code-1.11.0}/src/witbitz_code/opencode_plugins/witbitz-browse-cdp.mjs +0 -0
  52. {witbitz_code-1.10.1 → witbitz_code-1.11.0}/src/witbitz_code/opencode_plugins/witbitz-browse-local.mjs +0 -0
  53. {witbitz_code-1.10.1 → witbitz_code-1.11.0}/src/witbitz_code/opencode_plugins/witbitz-browse-publisher.mjs +0 -0
  54. {witbitz_code-1.10.1 → witbitz_code-1.11.0}/src/witbitz_code/opencode_plugins/witbitz-browse-tree.mjs +0 -0
  55. {witbitz_code-1.10.1 → witbitz_code-1.11.0}/src/witbitz_code/opencode_plugins/witbitz-browser.js +0 -0
  56. {witbitz_code-1.10.1 → witbitz_code-1.11.0}/src/witbitz_code/opencode_plugins/witbitz-media-server.mjs +0 -0
  57. {witbitz_code-1.10.1 → witbitz_code-1.11.0}/src/witbitz_code/opencode_plugins/witbitz-notes.js +0 -0
  58. {witbitz_code-1.10.1 → witbitz_code-1.11.0}/src/witbitz_code/opencode_plugins/witbitz-progress.js +0 -0
  59. {witbitz_code-1.10.1 → witbitz_code-1.11.0}/src/witbitz_code/outputs.py +0 -0
  60. {witbitz_code-1.10.1 → witbitz_code-1.11.0}/src/witbitz_code/payers.py +0 -0
  61. {witbitz_code-1.10.1 → witbitz_code-1.11.0}/src/witbitz_code/plugins.py +0 -0
  62. {witbitz_code-1.10.1 → witbitz_code-1.11.0}/src/witbitz_code/policy.py +0 -0
  63. {witbitz_code-1.10.1 → witbitz_code-1.11.0}/src/witbitz_code/ports.py +0 -0
  64. {witbitz_code-1.10.1 → witbitz_code-1.11.0}/src/witbitz_code/qr.py +0 -0
  65. {witbitz_code-1.10.1 → witbitz_code-1.11.0}/src/witbitz_code/receipt.py +0 -0
  66. {witbitz_code-1.10.1 → witbitz_code-1.11.0}/src/witbitz_code/relay.py +0 -0
  67. {witbitz_code-1.10.1 → witbitz_code-1.11.0}/src/witbitz_code/remind.py +0 -0
  68. {witbitz_code-1.10.1 → witbitz_code-1.11.0}/src/witbitz_code/seccheck.py +0 -0
  69. {witbitz_code-1.10.1 → witbitz_code-1.11.0}/src/witbitz_code/seen.py +0 -0
  70. {witbitz_code-1.10.1 → witbitz_code-1.11.0}/src/witbitz_code/tinfoil_read.py +0 -0
  71. {witbitz_code-1.10.1 → witbitz_code-1.11.0}/src/witbitz_code/tools_catalog.json +0 -0
  72. {witbitz_code-1.10.1 → witbitz_code-1.11.0}/src/witbitz_code/tools_probe.py +0 -0
  73. {witbitz_code-1.10.1 → witbitz_code-1.11.0}/src/witbitz_code/toolsetup.py +0 -0
  74. {witbitz_code-1.10.1 → witbitz_code-1.11.0}/src/witbitz_code/ui_static/app.css +0 -0
  75. {witbitz_code-1.10.1 → witbitz_code-1.11.0}/src/witbitz_code/uitools.py +0 -0
  76. {witbitz_code-1.10.1 → witbitz_code-1.11.0}/src/witbitz_code/winagent.py +0 -0
  77. {witbitz_code-1.10.1 → witbitz_code-1.11.0}/src/witbitz_code/winbg.py +0 -0
  78. {witbitz_code-1.10.1 → witbitz_code-1.11.0}/src/witbitz_code/winconsole.py +0 -0
  79. {witbitz_code-1.10.1 → witbitz_code-1.11.0}/src/witbitz_code/wizard.py +0 -0
  80. {witbitz_code-1.10.1 → witbitz_code-1.11.0}/src/witbitz_code.egg-info/dependency_links.txt +0 -0
  81. {witbitz_code-1.10.1 → witbitz_code-1.11.0}/src/witbitz_code.egg-info/entry_points.txt +0 -0
  82. {witbitz_code-1.10.1 → witbitz_code-1.11.0}/src/witbitz_code.egg-info/requires.txt +0 -0
  83. {witbitz_code-1.10.1 → witbitz_code-1.11.0}/src/witbitz_code.egg-info/top_level.txt +0 -0
  84. {witbitz_code-1.10.1 → witbitz_code-1.11.0}/tests/test_asks.py +0 -0
  85. {witbitz_code-1.10.1 → witbitz_code-1.11.0}/tests/test_attachments.py +0 -0
  86. {witbitz_code-1.10.1 → witbitz_code-1.11.0}/tests/test_auto.py +0 -0
  87. {witbitz_code-1.10.1 → witbitz_code-1.11.0}/tests/test_browse.py +0 -0
  88. {witbitz_code-1.10.1 → witbitz_code-1.11.0}/tests/test_browsetree.py +0 -0
  89. {witbitz_code-1.10.1 → witbitz_code-1.11.0}/tests/test_cli.py +0 -0
  90. {witbitz_code-1.10.1 → witbitz_code-1.11.0}/tests/test_codekeys.py +0 -0
  91. {witbitz_code-1.10.1 → witbitz_code-1.11.0}/tests/test_codetools.py +0 -0
  92. {witbitz_code-1.10.1 → witbitz_code-1.11.0}/tests/test_confidential.py +0 -0
  93. {witbitz_code-1.10.1 → witbitz_code-1.11.0}/tests/test_connector_e2e.py +0 -0
  94. {witbitz_code-1.10.1 → witbitz_code-1.11.0}/tests/test_devicelink.py +0 -0
  95. {witbitz_code-1.10.1 → witbitz_code-1.11.0}/tests/test_dirs.py +0 -0
  96. {witbitz_code-1.10.1 → witbitz_code-1.11.0}/tests/test_files_and_compress.py +0 -0
  97. {witbitz_code-1.10.1 → witbitz_code-1.11.0}/tests/test_folders.py +0 -0
  98. {witbitz_code-1.10.1 → witbitz_code-1.11.0}/tests/test_images.py +0 -0
  99. {witbitz_code-1.10.1 → witbitz_code-1.11.0}/tests/test_outputs.py +0 -0
  100. {witbitz_code-1.10.1 → witbitz_code-1.11.0}/tests/test_plugins.py +0 -0
  101. {witbitz_code-1.10.1 → witbitz_code-1.11.0}/tests/test_policy.py +0 -0
  102. {witbitz_code-1.10.1 → witbitz_code-1.11.0}/tests/test_ports.py +0 -0
  103. {witbitz_code-1.10.1 → witbitz_code-1.11.0}/tests/test_ports_win.py +0 -0
  104. {witbitz_code-1.10.1 → witbitz_code-1.11.0}/tests/test_registry.py +0 -0
  105. {witbitz_code-1.10.1 → witbitz_code-1.11.0}/tests/test_relay_codec.py +0 -0
  106. {witbitz_code-1.10.1 → witbitz_code-1.11.0}/tests/test_remind.py +0 -0
  107. {witbitz_code-1.10.1 → witbitz_code-1.11.0}/tests/test_seccheck.py +0 -0
  108. {witbitz_code-1.10.1 → witbitz_code-1.11.0}/tests/test_selfupdate.py +0 -0
  109. {witbitz_code-1.10.1 → witbitz_code-1.11.0}/tests/test_setup.py +0 -0
  110. {witbitz_code-1.10.1 → witbitz_code-1.11.0}/tests/test_tools_probe.py +0 -0
  111. {witbitz_code-1.10.1 → witbitz_code-1.11.0}/tests/test_toolsetup.py +0 -0
  112. {witbitz_code-1.10.1 → witbitz_code-1.11.0}/tests/test_ui.py +0 -0
  113. {witbitz_code-1.10.1 → witbitz_code-1.11.0}/tests/test_ui_page.py +0 -0
  114. {witbitz_code-1.10.1 → witbitz_code-1.11.0}/tests/test_uitools.py +0 -0
  115. {witbitz_code-1.10.1 → witbitz_code-1.11.0}/tests/test_uninstall.py +0 -0
  116. {witbitz_code-1.10.1 → witbitz_code-1.11.0}/tests/test_winagent.py +0 -0
  117. {witbitz_code-1.10.1 → witbitz_code-1.11.0}/tests/test_winbg.py +0 -0
  118. {witbitz_code-1.10.1 → witbitz_code-1.11.0}/tests/test_winconsole.py +0 -0
  119. {witbitz_code-1.10.1 → witbitz_code-1.11.0}/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.10.1
3
+ Version: 1.11.0
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.10.1"
7
+ version = "1.11.0"
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"
@@ -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.10.1"
7
+ __version__ = "1.11.0"
@@ -0,0 +1,101 @@
1
+ """WHICH SPACES APP THIS INSTALL BELONGS TO (the owner, 2026-10-22: "Each code is installed from its own origin" → "A separate
2
+ Witbitz Code install for each app - do it").
3
+
4
+ The regular app (witbitz-spaces.pages.dev) and the vaulted app (spaces.witbitz.chat / app.witbitz.chat) are separate tenants:
5
+ an account's rooms answer only to their own app. So each app's installer makes its own Witbitz Code — its own folder, its
6
+ own OpenCode ports and sessions, its own page and Start-menu entry, its own updates — and a code scanned from the other
7
+ app is refused with where to install that one from.
8
+
9
+ One setting says which: WITBITZ_CODE_APP=vaulted (the vaulted app's installer writes it into its shortcuts and launcher;
10
+ `--app vaulted` on any command sets it). Anything else is the regular app — every install made before this, unchanged.
11
+ """
12
+
13
+ from __future__ import annotations
14
+
15
+ import os
16
+ from pathlib import Path
17
+ from typing import Any
18
+
19
+ REGULAR, VAULTED = "regular", "vaulted"
20
+
21
+ # The hosted (vaulted) app's origins (the tenant t_a7f1); any other origin, or none, is the regular app's.
22
+ VAULTED_ORIGINS = ("https://spaces.witbitz.chat", "https://app.witbitz.chat", "https://witbitz-vault-prod.pages.dev",
23
+ "https://witbitz-spaces-vaulted.pages.dev")
24
+
25
+ _APPS = {
26
+ REGULAR: {"dir": "code", "port": 4096, "origin": "https://witbitz-spaces.pages.dev",
27
+ "installer": "https://app.witbitz.chat/code.ps1", "label": "regular app"},
28
+ # Ports 4296–4395 (proxies 4396–4495): never the regular install's 4096–4195 (proxies 4196–4295).
29
+ VAULTED: {"dir": "code-vaulted", "port": 4296, "origin": "https://spaces.witbitz.chat",
30
+ "installer": "https://app.witbitz.chat/code-vaulted.ps1", "label": "vaulted app"},
31
+ }
32
+ PORTS_PER_APP = 100
33
+
34
+
35
+ def name() -> str:
36
+ v = (os.environ.get("WITBITZ_CODE_APP") or "").strip().lower()
37
+ return VAULTED if v == VAULTED else REGULAR
38
+
39
+
40
+ def is_vaulted() -> bool:
41
+ return name() == VAULTED
42
+
43
+
44
+ def of_origin(origin: Any) -> str:
45
+ """The app an origin belongs to (a pairing's `origin`; none = the regular app, as every pairing before 2026-10-22)."""
46
+ return VAULTED if origin in VAULTED_ORIGINS else REGULAR
47
+
48
+
49
+ def label(which: str | None = None) -> str:
50
+ return _APPS[which or name()]["label"]
51
+
52
+
53
+ def code_dir() -> Path:
54
+ """This install's folder: pairings, keys, session owners, the page's lock and log."""
55
+ return Path.home() / ".witbitz" / _APPS[name()]["dir"]
56
+
57
+
58
+ def base_port() -> int:
59
+ return _APPS[name()]["port"]
60
+
61
+
62
+ def ports() -> range:
63
+ """OpenCode's ports for this install's accounts (its proxy on each + 100)."""
64
+ return range(base_port(), base_port() + PORTS_PER_APP)
65
+
66
+
67
+ def origin() -> str:
68
+ """The app's origin for an account call with no pairing to name it."""
69
+ return _APPS[name()]["origin"]
70
+
71
+
72
+ def installer_url() -> str:
73
+ return _APPS[name()]["installer"]
74
+
75
+
76
+ def take_flag(argv: list[str]) -> list[str]:
77
+ """`--app vaulted` anywhere in a command → WITBITZ_CODE_APP for this process and everything it starts; the rest back."""
78
+ out, i = [], 0
79
+ while i < len(argv):
80
+ if argv[i] == "--app" and i + 1 < len(argv):
81
+ os.environ["WITBITZ_CODE_APP"] = VAULTED if argv[i + 1].strip().lower() == VAULTED else REGULAR
82
+ i += 2
83
+ continue
84
+ out.append(argv[i])
85
+ i += 1
86
+ return out
87
+
88
+
89
+ def display_name() -> str:
90
+ """The name on this install's Start-menu entry, launcher and Settings → Apps entry (code.ps1's $Named)."""
91
+ return "Witbitz Code (vaulted app)" if is_vaulted() else "Witbitz Code"
92
+
93
+
94
+ def args() -> list[str]:
95
+ """What a command line this install writes must carry for the program it starts to know its app (a login start, a WSL start)."""
96
+ return ["--app", VAULTED] if is_vaulted() else []
97
+
98
+
99
+ def windows_root(localappdata: str) -> str:
100
+ """The installer's folder (code.ps1 $root)."""
101
+ return os.path.join(localappdata, "Witbitz Vaulted" if is_vaulted() else "Witbitz")
@@ -22,6 +22,7 @@ from pathlib import Path
22
22
  from typing import Any
23
23
 
24
24
  from . import _js
25
+ from . import app
25
26
  from . import pairings as pp
26
27
 
27
28
  NOTE_HEAD = "The user attached files. They are saved on this computer — open them with the read tool."
@@ -44,7 +45,7 @@ _MIME = {"pdf": "application/pdf", "png": "image/png", "jpg": "image/jpeg", "jpe
44
45
 
45
46
 
46
47
  def default_root() -> Path:
47
- return Path(os.environ.get("WITBITZ_CODE_ATTACHMENTS") or Path.home() / ".witbitz" / "code" / "attachments")
48
+ return Path(os.environ.get("WITBITZ_CODE_ATTACHMENTS") or app.code_dir() / "attachments")
48
49
 
49
50
 
50
51
  def safe_name(name: Any) -> str:
@@ -23,7 +23,7 @@ from typing import Any
23
23
 
24
24
  import httpx
25
25
 
26
- from . import __version__
26
+ from . import __version__, app
27
27
  from . import pair as pair_cmd
28
28
  from . import pairings as pp
29
29
  from . import browse as browse_mod, codekeys, codetools, confidential, keys, models, plugins, policy, ports, toolsetup, uninstall as uninstall_mod, winagent, winbg, winconsole, wizard, wslstart
@@ -163,8 +163,8 @@ _SETUP_LOCK = asyncio.Lock()
163
163
  async def serve(args: list[str], *, stop: Any = None, live_out: dict | None = None) -> int:
164
164
  """`stop` and `live_out`: the page's manager (ui.py) runs serve inside itself — it stops it with its own flag instead
165
165
  of a signal, and reads the running connector (each pairing's relay state) from live_out."""
166
- port_n = to_number(_flag(args, "--port", "4096"))
167
- port = int(port_n) if truthy(port_n) and port_n == port_n and float(port_n).is_integer() else 4096
166
+ port_n = to_number(_flag(args, "--port", str(app.base_port())))
167
+ port = int(port_n) if truthy(port_n) and port_n == port_n and float(port_n).is_integer() else app.base_port()
168
168
  everything = pp.load_pairings(log=lambda _m: None)
169
169
  if not everything:
170
170
  _err("witbitz-code: this computer is not paired yet — run: witbitz-code setup")
@@ -335,8 +335,8 @@ async def _check_keys_every(live_keys: codekeys.LiveKeys, http: httpx.AsyncClien
335
335
 
336
336
 
337
337
  async def setup(args: list[str]) -> int:
338
- port_n = to_number(_flag(args, "--port", "4096"))
339
- port = int(port_n) if truthy(port_n) and port_n == port_n and float(port_n).is_integer() else 4096
338
+ port_n = to_number(_flag(args, "--port", str(app.base_port())))
339
+ port = int(port_n) if truthy(port_n) and port_n == port_n and float(port_n).is_integer() else app.base_port()
340
340
  from . import net # noqa: PLC0415
341
341
 
342
342
  client = net.new_client()
@@ -446,7 +446,23 @@ async def ui_command(args: list[str]) -> int:
446
446
  await http.aclose()
447
447
 
448
448
 
449
+ def _own_bin_first() -> None:
450
+ """Windows: this install's own bin (OpenCode, uv, Git) first on PATH — the vaulted app's install is not on the user PATH
451
+ (two installs must not fight over `witbitz-code`), and its shortcuts start Python directly. The installer's layout:
452
+ <root>\\tools\\witbitz-code\\Scripts\\python(w).exe beside <root>\\bin."""
453
+ if sys.platform != "win32":
454
+ return
455
+ try:
456
+ b = Path(sys.executable).resolve().parents[3] / "bin"
457
+ if (b / "opencode.exe").is_file() and str(b) not in os.environ.get("PATH", "").split(os.pathsep):
458
+ os.environ["PATH"] = str(b) + os.pathsep + os.environ.get("PATH", "")
459
+ except (OSError, IndexError):
460
+ pass
461
+
462
+
449
463
  async def _main(argv: list[str]) -> int:
464
+ argv = app.take_flag(argv) # `--app vaulted`: this install is the vaulted app's (app.py)
465
+ _own_bin_first()
450
466
  cmd, rest = (argv[0], argv[1:]) if argv else ("", [])
451
467
  if cmd == "uninstall":
452
468
  return await uninstall(rest)
@@ -31,6 +31,7 @@ from urllib.parse import urlsplit
31
31
  import httpx
32
32
 
33
33
  from . import _js
34
+ from . import app
34
35
  from .asks import ASK_FRAME, AskBook
35
36
  from .folders import FOLDER_ROUTE, folder_route
36
37
  from .attachments import (ATTACHMENT_ROUTE, MAX_FILE_BYTES, attachment_rule, default_root, prune_attachments,
@@ -64,7 +65,7 @@ _URI_SAFE = "-_.!~*'()" # encodeURIComponent
64
65
 
65
66
  def default_auto_dir() -> Path:
66
67
  """Auto mode's state (per pairing) and its verdict log — the JS connector's AUTO_DIR."""
67
- return Path(os.environ.get("WITBITZ_CODE_AUTO_DIR") or Path.home() / ".witbitz" / "code")
68
+ return Path(os.environ.get("WITBITZ_CODE_AUTO_DIR") or app.code_dir())
68
69
 
69
70
 
70
71
  def _round(x: float) -> int:
@@ -12,6 +12,7 @@ from typing import Any
12
12
  import httpx
13
13
 
14
14
  from . import _js
15
+ from . import app
15
16
 
16
17
  API = "https://api.witbitz.chat/v1/space"
17
18
  ORIGIN = "https://witbitz-spaces.pages.dev"
@@ -49,7 +50,7 @@ def make_api_call(client: httpx.AsyncClient, origin: str | None = None) -> ApiCa
49
50
  """POST {op:'state', …} to the account API, as the Spaces app would (same Origin). `origin`: the app this account was
50
51
  paired from (a pairing's `origin`); none → RC_ORIGIN, else the regular app."""
51
52
  api = os.environ.get("RC_BASE") or API
52
- origin = app_origin(origin) or os.environ.get("RC_ORIGIN") or ORIGIN
53
+ origin = app_origin(origin) or os.environ.get("RC_ORIGIN") or app.origin()
53
54
 
54
55
  async def call(body: dict) -> ApiReply:
55
56
  r = await client.post(api, content=_js.stringify(body).encode("utf-8"),
@@ -19,6 +19,7 @@ from typing import Any
19
19
  from . import codekeys
20
20
  from . import account as acct
21
21
  from . import link, net
22
+ from . import app
22
23
  from . import pairings as pp
23
24
  from ._js import UNDEFINED, number_str, to_number, truthy
24
25
  from .connector import START_HINT
@@ -265,6 +266,12 @@ async def _run(args: Args, call: Any, link_fn: LinkFn | None, out: Callable, err
265
266
  if not account:
266
267
  err("witbitz-code: pairing did not complete — nothing changed.")
267
268
  return 1
269
+ # EACH APP ITS OWN INSTALL (app.py; the owner, 2026-10-22): a code scanned from the other app is not paired here.
270
+ came = app.of_origin(account.get("origin")) if account.get("origin") else app.name()
271
+ if came != app.name():
272
+ err(f"witbitz-code: ✖ that code came from the {app.label(came)} — this Witbitz Code is the {app.label()}'s. "
273
+ f"Install the {app.label(came)}'s own from it: Code → Computers → Add a computer. Nothing changed.")
274
+ return 1
268
275
  port = None if args.opencode_url else pp.next_port(doc, account["idx"]["room"]) # another account: its own OpenCode
269
276
  u = pp.upsert_pairing(doc, account=account.get("email") or "", idx=account["idx"], name=args.name,
270
277
  opencode_url=args.opencode_url or (f"http://127.0.0.1:{port}" if port else UNDEFINED),
@@ -24,6 +24,7 @@ from pathlib import Path
24
24
  from typing import Any
25
25
 
26
26
  from . import _js
27
+ from . import app as app_mod
27
28
  from ._js import NOT_LT, UNDEFINED, WS, strict_eq, truthy
28
29
  from .relay import RELAY_URL, new_relay_secret
29
30
 
@@ -32,12 +33,12 @@ DEFAULT_OPENCODE_URL = "http://127.0.0.1:4096"
32
33
 
33
34
  def pairings_path() -> Path:
34
35
  env = os.environ.get("WITBITZ_CODE_PAIRINGS")
35
- return Path(env) if env else Path.home() / ".witbitz" / "code" / "pairings.json"
36
+ return Path(env) if env else app_mod.code_dir() / "pairings.json" # this install's app (app.py)
36
37
 
37
38
 
38
39
  def env_path() -> Path:
39
40
  env = os.environ.get("OPENCODE_ENV_FILE")
40
- return Path(env) if env else Path.home() / ".opencode-server.env"
41
+ return Path(env) if env else (app_mod.code_dir() / "opencode-server.env" if app_mod.is_vaulted() else Path.home() / ".opencode-server.env")
41
42
 
42
43
 
43
44
  # ── the env file ──────────────────────────────────────────────────────────────────────────────────────────────────────
@@ -167,13 +168,12 @@ def remove_pairings(doc: Any, account: str = "", computer_id: str = "") -> dict:
167
168
 
168
169
 
169
170
  # The hosted ("vaulted") app's origins (the tenant t_a7f1); any other origin, or none, is the regular app.
170
- VAULTED_ORIGINS = ("https://spaces.witbitz.chat", "https://app.witbitz.chat", "https://witbitz-vault-prod.pages.dev",
171
- "https://witbitz-spaces-vaulted.pages.dev")
171
+ VAULTED_ORIGINS = app_mod.VAULTED_ORIGINS
172
172
 
173
173
 
174
174
  def app_label(p: Any) -> str:
175
175
  """Which app a pairing belongs to, in words — two pairings of one email differ only in this (the owner, 2026-10-22)."""
176
- return "vaulted app" if isinstance(p, dict) and p.get("origin") in VAULTED_ORIGINS else "regular app"
176
+ return app_mod.label(app_mod.of_origin(p.get("origin") if isinstance(p, dict) else None))
177
177
 
178
178
 
179
179
  # EACH ACCOUNT ITS OWN OPENCODE (the owner, 2026-10-22: one account's sessions showed in the other's — "they should be
@@ -190,13 +190,15 @@ def next_port(doc: Any, room: str) -> int | None:
190
190
  """The OpenCode port for a NEW pairing of `room`'s account: None = keep (already paired, or the first one here → 4096);
191
191
  else the first port no other pairing uses — each account its own OpenCode (= the page's _pair_port)."""
192
192
  cur = norm_pairings(doc)["pairings"]
193
- if not cur or any(p["idx"]["room"] == room for p in cur):
193
+ if any(p["idx"]["room"] == room for p in cur):
194
194
  return None
195
+ if not cur:
196
+ return app_mod.base_port() if app_mod.is_vaulted() else None # the vaulted install's first: its own range
195
197
  used = set()
196
198
  for p in cur:
197
199
  m = re.search(r":(\d+)/*$", str(p.get("opencodeUrl") or "http://127.0.0.1:4096"))
198
200
  used.add(int(m.group(1)) if m else 4096)
199
- return next((n for n in range(4096, 4196) if n not in used), None)
201
+ return next((n for n in app_mod.ports() if n not in used), None)
200
202
 
201
203
 
202
204
  def stores_path() -> Path:
@@ -224,6 +226,8 @@ def default_store_owner(pairings: list[dict]) -> str:
224
226
  def store_dir(p: Any, pairings: list[dict]) -> Path | None:
225
227
  """None = OpenCode's default store (the first account's); else this account's own folder."""
226
228
  key = store_key(p)
229
+ if app_mod.is_vaulted(): # OpenCode's default store is the regular install's: every vaulted account has its own
230
+ return pairings_path().parent / "accounts" / key
227
231
  return None if key == default_store_owner(pairings) else pairings_path().parent / "accounts" / key
228
232
 
229
233
 
@@ -23,6 +23,7 @@ from collections.abc import Awaitable, Callable
23
23
  from typing import Any
24
24
 
25
25
  from . import __version__, uninstall
26
+ from . import app
26
27
 
27
28
  INSTALLER_URL = "https://app.witbitz.chat/code.ps1"
28
29
  CHECK_EVERY_S = 3600 # while the page is open (its status poll); one small file from Cloudflare
@@ -75,7 +76,7 @@ class Updates:
75
76
  return
76
77
  self.checked_at = self._now()
77
78
  try:
78
- v = parse_pin(await self._fetch(INSTALLER_URL))
79
+ v = parse_pin(await self._fetch(app.installer_url()))
79
80
  if v:
80
81
  self.latest, self.why = v, ""
81
82
  else:
@@ -99,7 +100,7 @@ class Updates:
99
100
  return {"ok": False, "why": self.why or f"witbitz-code {self.current} is the released version"}
100
101
  v = st["latest"]
101
102
  if st["kind"] == "installer":
102
- self._launch(["powershell.exe", "-NoProfile", "-ExecutionPolicy", "Bypass", "-Command", f"irm {INSTALLER_URL} | iex"])
103
+ self._launch(["powershell.exe", "-NoProfile", "-ExecutionPolicy", "Bypass", "-Command", f"irm {app.installer_url()} | iex"])
103
104
  return {"ok": True, "kind": "installer", "message": f"The installer is updating witbitz-code to {v} in its own window; this page opens again when it is done."}
104
105
  if st["kind"] == "pipx":
105
106
  r = self._run(["pipx", "install", "--force", "--pip-args=--no-cache-dir", f"witbitz-code=={v}"]) # pip caches the index ~10 min
@@ -37,6 +37,7 @@ from typing import Any, Awaitable, Callable
37
37
 
38
38
  from . import __version__, browsetree, devicelink, link, net, ports, qr, seccheck, selfupdate, uitools, uninstall, winbg, wizard, wslstart
39
39
  from . import pair as pair_cmd
40
+ from . import app
40
41
  from . import pairings as pp
41
42
 
42
43
  UI_PORT_FROM = 4290
@@ -163,7 +164,7 @@ class Manager:
163
164
 
164
165
  def port(self) -> int:
165
166
  ps = self.ports()
166
- return ps[0] if len(ps) == 1 else 4096
167
+ return ps[0] if len(ps) == 1 else app.base_port()
167
168
 
168
169
  def ports(self) -> list[int]:
169
170
  """Every port a pairing here uses — one OpenCode each."""
@@ -236,7 +237,7 @@ class Manager:
236
237
  serving = {"state": "elsewhere", "port": el["port"], "owners": el["owners"]}
237
238
  # Hourly while the page is open; within minutes when it is opened or looked at again (fresh). Never waited for.
238
239
  asyncio.ensure_future(self.updates.check(fresh=fresh))
239
- return {"version": __version__, "update": self.updates.status(), "computer": self.label, "paired": paired, "serving": serving, "pair": pair,
240
+ return {"version": __version__, "app": app.name(), "update": self.updates.status(), "computer": self.label, "paired": paired, "serving": serving, "pair": pair,
240
241
  "login": self.login(), "busy": self.busy, "background": self.background,
241
242
  "tools": await asyncio.to_thread(self.tools.status),
242
243
  "tree": await asyncio.to_thread(self.tree.status), "security": self.security.peek(), "log": list(self.log)[-14:]}
@@ -341,10 +342,10 @@ class Manager:
341
342
  (the owner, 2026-10-22: one account's sessions must not show in another's). Serving is stopped while a code is up,
342
343
  so a paired port is skipped by the list, not by listening."""
343
344
  used = set(self.ports())
344
- for p in range(4096, 4196): # OpenCode's port and its proxy's (+100) — WSL and Windows share them on one laptop
345
+ for p in app.ports(): # OpenCode's port and its proxy's (+100) — WSL and Windows share them on one laptop; this app's range (app.py)
345
346
  if p not in used and not await self._is_listening(p) and not await self._is_listening(p + 100):
346
347
  return p
347
- return 4096
348
+ return app.base_port()
348
349
 
349
350
  async def start_pairing(self) -> dict:
350
351
  if self.pair_task and not self.pair_task.done():
@@ -43,6 +43,10 @@ function elsewhereText(sv) {
43
43
  function render(s) {
44
44
  state = s
45
45
  $('computer').textContent = s.computer.label + (s.computer.kind && !s.computer.label.includes(s.computer.kind) ? ' · ' + s.computer.kind : '')
46
+ // WHICH APP (app.py; the owner, 2026-10-22: a separate install for each app) — the vaulted app's says so, here and in the tab
47
+ const vaulted = s.app === 'vaulted'
48
+ $('appName').textContent = vaulted ? 'Witbitz Code · vaulted app' : 'Witbitz Code'
49
+ document.title = vaulted ? 'Witbitz Code (vaulted app)' : 'Witbitz Code'
46
50
  document.title = s.computer.kind && s.computer.kind.startsWith('WSL') ? `Witbitz Code (${s.computer.kind})` : 'Witbitz Code'
47
51
  $('version').textContent = s.version
48
52
  const paired = s.paired.length > 0
@@ -10,7 +10,7 @@
10
10
  <body>
11
11
  <main>
12
12
  <header>
13
- <h1>Witbitz Code</h1>
13
+ <h1 id="appName">Witbitz Code</h1>
14
14
  <p id="computer" class="sub">This computer</p>
15
15
  </header>
16
16
 
@@ -24,6 +24,7 @@ from pathlib import Path
24
24
  from typing import Any
25
25
 
26
26
  from . import keys, plugins, ports
27
+ from . import app
27
28
  from . import pairings as pp
28
29
  from .wizard import find_opencode
29
30
 
@@ -344,7 +345,7 @@ class Deps:
344
345
  remove_opencode: bool = False
345
346
  all_pairings: Callable[[], list] = lambda: pp.load_pairings(log=lambda _m: None)
346
347
  unpair: Callable[[dict], Awaitable[dict]] = None # type: ignore[assignment]
347
- code_dir: str = field(default_factory=lambda: str(Path.home() / ".witbitz" / "code"))
348
+ code_dir: str = field(default_factory=lambda: str(app.code_dir()))
348
349
  has_keys: Callable[[], list] = None # type: ignore[assignment]
349
350
  delete_keys: Callable[[], None] = None # type: ignore[assignment]
350
351
  remove_path: Callable[[str], None] = lambda p: (shutil.rmtree(p, ignore_errors=True) if os.path.isdir(p) and not os.path.islink(p) else Path(p).unlink(missing_ok=True))
@@ -25,7 +25,7 @@ import subprocess
25
25
  import sys
26
26
  from typing import Callable
27
27
 
28
- from . import ports
28
+ from . import app, ports
29
29
 
30
30
  POWERSHELL = "/mnt/c/Windows/System32/WindowsPowerShell/v1.0/powershell.exe"
31
31
  STARTUP = "[Environment]::GetFolderPath('Startup')"
@@ -37,7 +37,8 @@ WIN_QUIET = ["ui", "--no-browser"] # native Windows: pythonw knows it has no wi
37
37
 
38
38
 
39
39
  def shortcut_name(distro: str) -> str:
40
- return f"Witbitz Code (WSL {distro}).lnk" if distro else "Witbitz Code (WSL).lnk"
40
+ v = " · vaulted app" if app.is_vaulted() else "" # each app its own install (app.py)
41
+ return f"Witbitz Code (WSL {distro}{v}).lnk" if distro else f"Witbitz Code (WSL{v}).lnk"
41
42
 
42
43
 
43
44
  def _join(parts: list[str]) -> str:
@@ -197,12 +198,12 @@ def launch_background(*, ps: Callable[[str], tuple[int, str]] = run_ps, which: C
197
198
  return False, "witbitz-code is not on PATH"
198
199
  copy = copy or copy_icon
199
200
  distro = os.environ.get("WSL_DISTRO_NAME", "")
200
- args = headless_args(distro, getpass.getuser(), wsl_command(exe), ["ui", "--hidden"])
201
+ args = headless_args(distro, getpass.getuser(), wsl_command(exe), ["ui", "--hidden", *app.args()])
201
202
  # The Start-menu entry first (it opens this page again, starting it if it is not running), then the start itself.
202
203
  icon = WSL_ICON if copy(ps=ps) else ""
203
204
  menu = enable_script(menu_name(distro), args, folder=PROGRAMS, target=CONHOST, icon=icon)
204
205
  # A login start written by 1.4.0–1.5.x opens a minimized window: an existing one is rewritten to start hidden too.
205
- quiet = headless_args(distro, getpass.getuser(), wsl_command(exe), UI_QUIET)
206
+ quiet = headless_args(distro, getpass.getuser(), wsl_command(exe), [*UI_QUIET, *app.args()])
206
207
  login = (f"if (Test-Path (Join-Path ({STARTUP}) {_q(shortcut_name(distro))})) {{ "
207
208
  f"{enable_script(shortcut_name(distro), quiet, target=CONHOST, icon=icon)} | Out-Null }}")
208
209
  code, out = ps(f"try {{ {menu} | Out-Null; {login} }} catch {{ }}; {launch_script(args)}")
@@ -218,13 +219,13 @@ def windows_startup_spec(env: dict | None = None, which: Callable[[str], str | N
218
219
  env = os.environ if env is None else env
219
220
  pyw = os.path.join(os.path.dirname(executable or sys.executable), "pythonw.exe")
220
221
  if exists(pyw):
221
- return {"name": "Witbitz Code.lnk", "args": _join(["-m", "witbitz_code", *WIN_QUIET]), "target": _q(pyw),
222
+ return {"name": app.display_name() + ".lnk", "args": _join(["-m", "witbitz_code", *WIN_QUIET, *app.args()]), "target": _q(pyw),
222
223
  "icon": _q(icon_file("witbitz-code.ico"))}
223
- launcher = os.path.join(env.get("LOCALAPPDATA", ""), "Witbitz", "Witbitz Code.cmd")
224
+ launcher = os.path.join(app.windows_root(env.get("LOCALAPPDATA", "")), app.display_name() + ".cmd") # it sets WITBITZ_CODE_APP
224
225
  if env.get("LOCALAPPDATA") and exists(launcher):
225
- return {"name": "Witbitz Code.lnk", "args": "--no-browser", "target": _q(launcher)}
226
+ return {"name": app.display_name() + ".lnk", "args": "--no-browser", "target": _q(launcher)}
226
227
  exe = which("witbitz-code")
227
- return {"name": "Witbitz Code.lnk", "args": _join(WIN_QUIET), "target": _q(exe)} if exe else None
228
+ return {"name": app.display_name() + ".lnk", "args": _join([*WIN_QUIET, *app.args()]), "target": _q(exe)} if exe else None
228
229
 
229
230
 
230
231
  def login_spec() -> dict | None:
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: witbitz-code
3
- Version: 1.10.1
3
+ Version: 1.11.0
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
@@ -5,6 +5,7 @@ src/witbitz_code/__init__.py
5
5
  src/witbitz_code/__main__.py
6
6
  src/witbitz_code/_js.py
7
7
  src/witbitz_code/account.py
8
+ src/witbitz_code/app.py
8
9
  src/witbitz_code/asks.py
9
10
  src/witbitz_code/attachments.py
10
11
  src/witbitz_code/auto.py
@@ -304,9 +304,11 @@ def test_a_new_pairing_on_wsl_is_named_for_wsl(monkeypatch):
304
304
  assert pp.hostname() == "my-laptop"
305
305
 
306
306
 
307
- def test_a_pairing_from_the_hosted_app_writes_that_app_s_account_and_remembers_it():
307
+ def test_a_pairing_from_the_hosted_app_writes_that_app_s_account_and_remembers_it(monkeypatch):
308
308
  """The owner, 2026-10-22: a second account added from the vaulted app failed "computers read refused (404)" — its
309
- rooms answer only to the hosted app's origin. The relay that answered the scan names the app; each pairing keeps it."""
309
+ rooms answer only to the hosted app's origin. The relay that answered the scan names the app; each pairing keeps it.
310
+ Since the same day each app has its OWN install (app.py): this is the vaulted app's, and the regular app's code is refused."""
311
+ monkeypatch.setenv("WITBITZ_CODE_APP", "vaulted")
310
312
  regular, hosted = MockApi(), MockApi()
311
313
  asked = []
312
314
 
@@ -328,16 +330,16 @@ def test_a_pairing_from_the_hosted_app_writes_that_app_s_account_and_remembers_i
328
330
  assert regular.writes == [] and hosted.writes, "the account was written through the hosted app's origin only"
329
331
  p = pp.read_pairings()["pairings"][0]
330
332
  assert p["origin"] == "https://spaces.witbitz.chat"
333
+ assert p["opencodeUrl"] == "http://127.0.0.1:4296", "the vaulted install's own OpenCode range"
331
334
 
332
335
  async def link_fn2():
333
336
  return {"email": "work@y", "idx": IDX2, "master": "M" * 43, "anchor": {**ANCHOR, "email": "work@y"},
334
337
  "origin": "https://witbitz-spaces.pages.dev"}
335
338
 
336
- asked.clear()
337
- assert run(pair.main([], call=call, link_fn=link_fn2, keys=keys, **Capture().kw())) == 0
338
- assert set(asked) == {"https://witbitz-spaces.pages.dev"}, "a regular-app pairing writes through its own origin"
339
- q = next(x for x in pp.read_pairings()["pairings"] if x["account"] == "work@y")
340
- assert q["origin"] == "https://witbitz-spaces.pages.dev"
339
+ cap = Capture()
340
+ assert run(pair.main([], call=call, link_fn=link_fn2, keys=keys, **cap.kw())) == 1
341
+ assert "came from the regular app" in cap.text() and "Nothing changed" in cap.text()
342
+ assert [x["account"] for x in pp.read_pairings()["pairings"]] == ["me@x"], "the regular app's code is not paired here"
341
343
 
342
344
  asked.clear()
343
345
  assert run(pair.main(["--rotate", "--account", "me@x"], call=call, **Capture().kw())) == 0
@@ -349,6 +351,32 @@ def test_a_pairing_from_the_hosted_app_writes_that_app_s_account_and_remembers_i
349
351
  assert len(hosted.writes) > n, "the unpair tombstone goes to the hosted app's account too"
350
352
 
351
353
 
354
+ def test_the_regular_install_refuses_a_code_from_the_vaulted_app(monkeypatch):
355
+ monkeypatch.delenv("WITBITZ_CODE_APP", raising=False)
356
+
357
+ async def link_fn():
358
+ return {"email": "me@x", "idx": IDX, "master": "M" * 43, "anchor": ANCHOR, "origin": "https://spaces.witbitz.chat"}
359
+
360
+ cap = Capture()
361
+ assert run(pair.main([], call=MockApi().call, link_fn=link_fn, keys=FakeKeys(), **cap.kw())) == 1
362
+ assert "came from the vaulted app" in cap.text() and "Code → Computers → Add a computer" in cap.text()
363
+ assert pp.read_pairings()["pairings"] == []
364
+
365
+
366
+ def test_each_app_has_its_own_folder_ports_installer_and_store(monkeypatch, tmp_path):
367
+ from witbitz_code import app
368
+ monkeypatch.setattr(app.Path, "home", lambda: tmp_path)
369
+ monkeypatch.delenv("WITBITZ_CODE_APP", raising=False)
370
+ assert (app.code_dir(), app.base_port(), app.installer_url()) == (tmp_path / ".witbitz" / "code", 4096, "https://app.witbitz.chat/code.ps1")
371
+ monkeypatch.setenv("WITBITZ_CODE_APP", "vaulted")
372
+ assert (app.code_dir(), app.base_port(), app.installer_url()) == (tmp_path / ".witbitz" / "code-vaulted", 4296, "https://app.witbitz.chat/code-vaulted.ps1")
373
+ assert set(app.ports()).isdisjoint(range(4096, 4296)), "never the regular install's OpenCode or proxy ports"
374
+ me = {"account": "me@x", "origin": "https://spaces.witbitz.chat"}
375
+ assert pp.store_dir(me, [me]) is not None, "OpenCode's default store is the regular install's"
376
+ monkeypatch.delenv("WITBITZ_CODE_APP")
377
+ assert app.take_flag(["ui", "--app", "vaulted", "--no-browser"]) == ["ui", "--no-browser"] and app.is_vaulted()
378
+
379
+
352
380
  def test_call_for_ignores_anything_that_is_not_an_https_origin():
353
381
  from witbitz_code import net
354
382
 
@@ -101,3 +101,18 @@ def test_the_icon_is_written_into_windows_local_app_data_from_this_side(tmp_path
101
101
  assert ok and (win / "Witbitz-WSL" / "witbitz-code-wsl.ico").read_bytes() == open(ws.icon_file("witbitz-code-wsl.ico"), "rb").read()
102
102
  assert ws.copy_icon(ps=lambda s: (-1, ""), to_linux=lambda p: str(win)) is False
103
103
  assert ws.copy_icon(ps=lambda s: (0, "ok"), to_linux=lambda p: "ok") is False, "not a Windows folder: never written"
104
+
105
+
106
+ def test_the_vaulted_app_s_install_starts_as_itself(monkeypatch):
107
+ """Each app its own install (app.py; the owner, 2026-10-22): the vaulted one's login start, Start-menu entry and WSL start
108
+ carry `--app vaulted` and its own names — a start that forgot would come back as the regular app's Witbitz Code."""
109
+ import os
110
+ from witbitz_code import wslstart as ws
111
+ monkeypatch.setenv("WITBITZ_CODE_APP", "vaulted")
112
+ exe = r"C:\Users\a\AppData\Local\Witbitz Vaulted\tools\witbitz-code\Scripts\python.exe"
113
+ pyw = os.path.join(os.path.dirname(exe), "pythonw.exe")
114
+ spec = ws.windows_startup_spec(env={"LOCALAPPDATA": "L"}, exists=lambda p: p == pyw, executable=exe)
115
+ assert spec["name"] == "Witbitz Code (vaulted app).lnk" and spec["args"].endswith("ui --no-browser --app vaulted")
116
+ launcher = ws.windows_startup_spec(env={"LOCALAPPDATA": "L"}, exists=lambda p: p.endswith("Witbitz Code (vaulted app).cmd"), executable=exe)
117
+ assert "Witbitz Vaulted" in launcher["target"] and launcher["name"] == "Witbitz Code (vaulted app).lnk"
118
+ assert ws.shortcut_name("Ubuntu") == "Witbitz Code (WSL Ubuntu · vaulted app).lnk"
File without changes
File without changes
File without changes