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.
- {witbitz_code-1.10.1/src/witbitz_code.egg-info → witbitz_code-1.11.0}/PKG-INFO +1 -1
- {witbitz_code-1.10.1 → witbitz_code-1.11.0}/pyproject.toml +1 -1
- {witbitz_code-1.10.1 → witbitz_code-1.11.0}/src/witbitz_code/__init__.py +1 -1
- witbitz_code-1.11.0/src/witbitz_code/app.py +101 -0
- {witbitz_code-1.10.1 → witbitz_code-1.11.0}/src/witbitz_code/attachments.py +2 -1
- {witbitz_code-1.10.1 → witbitz_code-1.11.0}/src/witbitz_code/cli.py +21 -5
- {witbitz_code-1.10.1 → witbitz_code-1.11.0}/src/witbitz_code/connector.py +2 -1
- {witbitz_code-1.10.1 → witbitz_code-1.11.0}/src/witbitz_code/net.py +2 -1
- {witbitz_code-1.10.1 → witbitz_code-1.11.0}/src/witbitz_code/pair.py +7 -0
- {witbitz_code-1.10.1 → witbitz_code-1.11.0}/src/witbitz_code/pairings.py +11 -7
- {witbitz_code-1.10.1 → witbitz_code-1.11.0}/src/witbitz_code/selfupdate.py +3 -2
- {witbitz_code-1.10.1 → witbitz_code-1.11.0}/src/witbitz_code/ui.py +5 -4
- {witbitz_code-1.10.1 → witbitz_code-1.11.0}/src/witbitz_code/ui_static/app.js +4 -0
- {witbitz_code-1.10.1 → witbitz_code-1.11.0}/src/witbitz_code/ui_static/index.html +1 -1
- {witbitz_code-1.10.1 → witbitz_code-1.11.0}/src/witbitz_code/uninstall.py +2 -1
- {witbitz_code-1.10.1 → witbitz_code-1.11.0}/src/witbitz_code/wslstart.py +9 -8
- {witbitz_code-1.10.1 → witbitz_code-1.11.0/src/witbitz_code.egg-info}/PKG-INFO +1 -1
- {witbitz_code-1.10.1 → witbitz_code-1.11.0}/src/witbitz_code.egg-info/SOURCES.txt +1 -0
- {witbitz_code-1.10.1 → witbitz_code-1.11.0}/tests/test_pair_flows.py +35 -7
- {witbitz_code-1.10.1 → witbitz_code-1.11.0}/tests/test_wslstart.py +15 -0
- {witbitz_code-1.10.1 → witbitz_code-1.11.0}/LICENSE +0 -0
- {witbitz_code-1.10.1 → witbitz_code-1.11.0}/README.md +0 -0
- {witbitz_code-1.10.1 → witbitz_code-1.11.0}/setup.cfg +0 -0
- {witbitz_code-1.10.1 → witbitz_code-1.11.0}/src/witbitz_code/__main__.py +0 -0
- {witbitz_code-1.10.1 → witbitz_code-1.11.0}/src/witbitz_code/_js.py +0 -0
- {witbitz_code-1.10.1 → witbitz_code-1.11.0}/src/witbitz_code/account.py +0 -0
- {witbitz_code-1.10.1 → witbitz_code-1.11.0}/src/witbitz_code/asks.py +0 -0
- {witbitz_code-1.10.1 → witbitz_code-1.11.0}/src/witbitz_code/auto.py +0 -0
- {witbitz_code-1.10.1 → witbitz_code-1.11.0}/src/witbitz_code/auto_runner.py +0 -0
- {witbitz_code-1.10.1 → witbitz_code-1.11.0}/src/witbitz_code/browse.py +0 -0
- {witbitz_code-1.10.1 → witbitz_code-1.11.0}/src/witbitz_code/browsetree.py +0 -0
- {witbitz_code-1.10.1 → witbitz_code-1.11.0}/src/witbitz_code/codekeys.py +0 -0
- {witbitz_code-1.10.1 → witbitz_code-1.11.0}/src/witbitz_code/codetools.py +0 -0
- {witbitz_code-1.10.1 → witbitz_code-1.11.0}/src/witbitz_code/compress.py +0 -0
- {witbitz_code-1.10.1 → witbitz_code-1.11.0}/src/witbitz_code/computers.py +0 -0
- {witbitz_code-1.10.1 → witbitz_code-1.11.0}/src/witbitz_code/confidential.py +0 -0
- {witbitz_code-1.10.1 → witbitz_code-1.11.0}/src/witbitz_code/confidential_models.json +0 -0
- {witbitz_code-1.10.1 → witbitz_code-1.11.0}/src/witbitz_code/devicelink.py +0 -0
- {witbitz_code-1.10.1 → witbitz_code-1.11.0}/src/witbitz_code/dirs.py +0 -0
- {witbitz_code-1.10.1 → witbitz_code-1.11.0}/src/witbitz_code/folders.py +0 -0
- {witbitz_code-1.10.1 → witbitz_code-1.11.0}/src/witbitz_code/gateway.py +0 -0
- {witbitz_code-1.10.1 → witbitz_code-1.11.0}/src/witbitz_code/icons/witbitz-code-wsl.ico +0 -0
- {witbitz_code-1.10.1 → witbitz_code-1.11.0}/src/witbitz_code/icons/witbitz-code.ico +0 -0
- {witbitz_code-1.10.1 → witbitz_code-1.11.0}/src/witbitz_code/images.py +0 -0
- {witbitz_code-1.10.1 → witbitz_code-1.11.0}/src/witbitz_code/keys.py +0 -0
- {witbitz_code-1.10.1 → witbitz_code-1.11.0}/src/witbitz_code/link.py +0 -0
- {witbitz_code-1.10.1 → witbitz_code-1.11.0}/src/witbitz_code/mkdir.py +0 -0
- {witbitz_code-1.10.1 → witbitz_code-1.11.0}/src/witbitz_code/models.py +0 -0
- {witbitz_code-1.10.1 → witbitz_code-1.11.0}/src/witbitz_code/offered_models.json +0 -0
- {witbitz_code-1.10.1 → witbitz_code-1.11.0}/src/witbitz_code/opencode_plugins/notes-init.md +0 -0
- {witbitz_code-1.10.1 → witbitz_code-1.11.0}/src/witbitz_code/opencode_plugins/witbitz-browse-cdp.mjs +0 -0
- {witbitz_code-1.10.1 → witbitz_code-1.11.0}/src/witbitz_code/opencode_plugins/witbitz-browse-local.mjs +0 -0
- {witbitz_code-1.10.1 → witbitz_code-1.11.0}/src/witbitz_code/opencode_plugins/witbitz-browse-publisher.mjs +0 -0
- {witbitz_code-1.10.1 → witbitz_code-1.11.0}/src/witbitz_code/opencode_plugins/witbitz-browse-tree.mjs +0 -0
- {witbitz_code-1.10.1 → witbitz_code-1.11.0}/src/witbitz_code/opencode_plugins/witbitz-browser.js +0 -0
- {witbitz_code-1.10.1 → witbitz_code-1.11.0}/src/witbitz_code/opencode_plugins/witbitz-media-server.mjs +0 -0
- {witbitz_code-1.10.1 → witbitz_code-1.11.0}/src/witbitz_code/opencode_plugins/witbitz-notes.js +0 -0
- {witbitz_code-1.10.1 → witbitz_code-1.11.0}/src/witbitz_code/opencode_plugins/witbitz-progress.js +0 -0
- {witbitz_code-1.10.1 → witbitz_code-1.11.0}/src/witbitz_code/outputs.py +0 -0
- {witbitz_code-1.10.1 → witbitz_code-1.11.0}/src/witbitz_code/payers.py +0 -0
- {witbitz_code-1.10.1 → witbitz_code-1.11.0}/src/witbitz_code/plugins.py +0 -0
- {witbitz_code-1.10.1 → witbitz_code-1.11.0}/src/witbitz_code/policy.py +0 -0
- {witbitz_code-1.10.1 → witbitz_code-1.11.0}/src/witbitz_code/ports.py +0 -0
- {witbitz_code-1.10.1 → witbitz_code-1.11.0}/src/witbitz_code/qr.py +0 -0
- {witbitz_code-1.10.1 → witbitz_code-1.11.0}/src/witbitz_code/receipt.py +0 -0
- {witbitz_code-1.10.1 → witbitz_code-1.11.0}/src/witbitz_code/relay.py +0 -0
- {witbitz_code-1.10.1 → witbitz_code-1.11.0}/src/witbitz_code/remind.py +0 -0
- {witbitz_code-1.10.1 → witbitz_code-1.11.0}/src/witbitz_code/seccheck.py +0 -0
- {witbitz_code-1.10.1 → witbitz_code-1.11.0}/src/witbitz_code/seen.py +0 -0
- {witbitz_code-1.10.1 → witbitz_code-1.11.0}/src/witbitz_code/tinfoil_read.py +0 -0
- {witbitz_code-1.10.1 → witbitz_code-1.11.0}/src/witbitz_code/tools_catalog.json +0 -0
- {witbitz_code-1.10.1 → witbitz_code-1.11.0}/src/witbitz_code/tools_probe.py +0 -0
- {witbitz_code-1.10.1 → witbitz_code-1.11.0}/src/witbitz_code/toolsetup.py +0 -0
- {witbitz_code-1.10.1 → witbitz_code-1.11.0}/src/witbitz_code/ui_static/app.css +0 -0
- {witbitz_code-1.10.1 → witbitz_code-1.11.0}/src/witbitz_code/uitools.py +0 -0
- {witbitz_code-1.10.1 → witbitz_code-1.11.0}/src/witbitz_code/winagent.py +0 -0
- {witbitz_code-1.10.1 → witbitz_code-1.11.0}/src/witbitz_code/winbg.py +0 -0
- {witbitz_code-1.10.1 → witbitz_code-1.11.0}/src/witbitz_code/winconsole.py +0 -0
- {witbitz_code-1.10.1 → witbitz_code-1.11.0}/src/witbitz_code/wizard.py +0 -0
- {witbitz_code-1.10.1 → witbitz_code-1.11.0}/src/witbitz_code.egg-info/dependency_links.txt +0 -0
- {witbitz_code-1.10.1 → witbitz_code-1.11.0}/src/witbitz_code.egg-info/entry_points.txt +0 -0
- {witbitz_code-1.10.1 → witbitz_code-1.11.0}/src/witbitz_code.egg-info/requires.txt +0 -0
- {witbitz_code-1.10.1 → witbitz_code-1.11.0}/src/witbitz_code.egg-info/top_level.txt +0 -0
- {witbitz_code-1.10.1 → witbitz_code-1.11.0}/tests/test_asks.py +0 -0
- {witbitz_code-1.10.1 → witbitz_code-1.11.0}/tests/test_attachments.py +0 -0
- {witbitz_code-1.10.1 → witbitz_code-1.11.0}/tests/test_auto.py +0 -0
- {witbitz_code-1.10.1 → witbitz_code-1.11.0}/tests/test_browse.py +0 -0
- {witbitz_code-1.10.1 → witbitz_code-1.11.0}/tests/test_browsetree.py +0 -0
- {witbitz_code-1.10.1 → witbitz_code-1.11.0}/tests/test_cli.py +0 -0
- {witbitz_code-1.10.1 → witbitz_code-1.11.0}/tests/test_codekeys.py +0 -0
- {witbitz_code-1.10.1 → witbitz_code-1.11.0}/tests/test_codetools.py +0 -0
- {witbitz_code-1.10.1 → witbitz_code-1.11.0}/tests/test_confidential.py +0 -0
- {witbitz_code-1.10.1 → witbitz_code-1.11.0}/tests/test_connector_e2e.py +0 -0
- {witbitz_code-1.10.1 → witbitz_code-1.11.0}/tests/test_devicelink.py +0 -0
- {witbitz_code-1.10.1 → witbitz_code-1.11.0}/tests/test_dirs.py +0 -0
- {witbitz_code-1.10.1 → witbitz_code-1.11.0}/tests/test_files_and_compress.py +0 -0
- {witbitz_code-1.10.1 → witbitz_code-1.11.0}/tests/test_folders.py +0 -0
- {witbitz_code-1.10.1 → witbitz_code-1.11.0}/tests/test_images.py +0 -0
- {witbitz_code-1.10.1 → witbitz_code-1.11.0}/tests/test_outputs.py +0 -0
- {witbitz_code-1.10.1 → witbitz_code-1.11.0}/tests/test_plugins.py +0 -0
- {witbitz_code-1.10.1 → witbitz_code-1.11.0}/tests/test_policy.py +0 -0
- {witbitz_code-1.10.1 → witbitz_code-1.11.0}/tests/test_ports.py +0 -0
- {witbitz_code-1.10.1 → witbitz_code-1.11.0}/tests/test_ports_win.py +0 -0
- {witbitz_code-1.10.1 → witbitz_code-1.11.0}/tests/test_registry.py +0 -0
- {witbitz_code-1.10.1 → witbitz_code-1.11.0}/tests/test_relay_codec.py +0 -0
- {witbitz_code-1.10.1 → witbitz_code-1.11.0}/tests/test_remind.py +0 -0
- {witbitz_code-1.10.1 → witbitz_code-1.11.0}/tests/test_seccheck.py +0 -0
- {witbitz_code-1.10.1 → witbitz_code-1.11.0}/tests/test_selfupdate.py +0 -0
- {witbitz_code-1.10.1 → witbitz_code-1.11.0}/tests/test_setup.py +0 -0
- {witbitz_code-1.10.1 → witbitz_code-1.11.0}/tests/test_tools_probe.py +0 -0
- {witbitz_code-1.10.1 → witbitz_code-1.11.0}/tests/test_toolsetup.py +0 -0
- {witbitz_code-1.10.1 → witbitz_code-1.11.0}/tests/test_ui.py +0 -0
- {witbitz_code-1.10.1 → witbitz_code-1.11.0}/tests/test_ui_page.py +0 -0
- {witbitz_code-1.10.1 → witbitz_code-1.11.0}/tests/test_uitools.py +0 -0
- {witbitz_code-1.10.1 → witbitz_code-1.11.0}/tests/test_uninstall.py +0 -0
- {witbitz_code-1.10.1 → witbitz_code-1.11.0}/tests/test_winagent.py +0 -0
- {witbitz_code-1.10.1 → witbitz_code-1.11.0}/tests/test_winbg.py +0 -0
- {witbitz_code-1.10.1 → witbitz_code-1.11.0}/tests/test_winconsole.py +0 -0
- {witbitz_code-1.10.1 → witbitz_code-1.11.0}/tests/test_windows_files.py +0 -0
|
@@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"
|
|
|
4
4
|
|
|
5
5
|
[project]
|
|
6
6
|
name = "witbitz-code"
|
|
7
|
-
version = "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"
|
|
@@ -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
|
|
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",
|
|
167
|
-
port = int(port_n) if truthy(port_n) and port_n == port_n and float(port_n).is_integer() else
|
|
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",
|
|
339
|
-
port = int(port_n) if truthy(port_n) and port_n == port_n and float(port_n).is_integer() else
|
|
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
|
|
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
|
|
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
|
|
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 =
|
|
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 "
|
|
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
|
|
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
|
|
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(
|
|
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 {
|
|
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
|
|
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
|
|
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
|
|
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
|
|
@@ -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(
|
|
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
|
-
|
|
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": "
|
|
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", ""),
|
|
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": "
|
|
226
|
+
return {"name": app.display_name() + ".lnk", "args": "--no-browser", "target": _q(launcher)}
|
|
226
227
|
exe = which("witbitz-code")
|
|
227
|
-
return {"name": "
|
|
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:
|
|
@@ -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
|
-
|
|
337
|
-
assert run(pair.main([], call=call, link_fn=link_fn2, keys=keys, **
|
|
338
|
-
assert
|
|
339
|
-
|
|
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
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{witbitz_code-1.10.1 → witbitz_code-1.11.0}/src/witbitz_code/opencode_plugins/witbitz-browse-cdp.mjs
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{witbitz_code-1.10.1 → witbitz_code-1.11.0}/src/witbitz_code/opencode_plugins/witbitz-browser.js
RENAMED
|
File without changes
|
|
File without changes
|
{witbitz_code-1.10.1 → witbitz_code-1.11.0}/src/witbitz_code/opencode_plugins/witbitz-notes.js
RENAMED
|
File without changes
|
{witbitz_code-1.10.1 → witbitz_code-1.11.0}/src/witbitz_code/opencode_plugins/witbitz-progress.js
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|