witbitz-code 1.3.7__tar.gz → 1.3.8__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.3.7/src/witbitz_code.egg-info → witbitz_code-1.3.8}/PKG-INFO +5 -1
- {witbitz_code-1.3.7 → witbitz_code-1.3.8}/README.md +4 -0
- {witbitz_code-1.3.7 → witbitz_code-1.3.8}/pyproject.toml +1 -1
- {witbitz_code-1.3.7 → witbitz_code-1.3.8}/src/witbitz_code/__init__.py +1 -1
- {witbitz_code-1.3.7 → witbitz_code-1.3.8}/src/witbitz_code/cli.py +5 -1
- {witbitz_code-1.3.7 → witbitz_code-1.3.8}/src/witbitz_code/pair.py +20 -0
- {witbitz_code-1.3.7 → witbitz_code-1.3.8}/src/witbitz_code/pairings.py +7 -1
- {witbitz_code-1.3.7 → witbitz_code-1.3.8}/src/witbitz_code/ports.py +28 -0
- {witbitz_code-1.3.7 → witbitz_code-1.3.8}/src/witbitz_code/toolsetup.py +14 -2
- {witbitz_code-1.3.7 → witbitz_code-1.3.8}/src/witbitz_code/wizard.py +39 -3
- {witbitz_code-1.3.7 → witbitz_code-1.3.8/src/witbitz_code.egg-info}/PKG-INFO +5 -1
- {witbitz_code-1.3.7 → witbitz_code-1.3.8}/tests/test_pair_flows.py +27 -0
- {witbitz_code-1.3.7 → witbitz_code-1.3.8}/tests/test_ports_win.py +26 -0
- {witbitz_code-1.3.7 → witbitz_code-1.3.8}/tests/test_setup.py +51 -7
- {witbitz_code-1.3.7 → witbitz_code-1.3.8}/tests/test_toolsetup.py +22 -1
- {witbitz_code-1.3.7 → witbitz_code-1.3.8}/LICENSE +0 -0
- {witbitz_code-1.3.7 → witbitz_code-1.3.8}/setup.cfg +0 -0
- {witbitz_code-1.3.7 → witbitz_code-1.3.8}/src/witbitz_code/__main__.py +0 -0
- {witbitz_code-1.3.7 → witbitz_code-1.3.8}/src/witbitz_code/_js.py +0 -0
- {witbitz_code-1.3.7 → witbitz_code-1.3.8}/src/witbitz_code/account.py +0 -0
- {witbitz_code-1.3.7 → witbitz_code-1.3.8}/src/witbitz_code/asks.py +0 -0
- {witbitz_code-1.3.7 → witbitz_code-1.3.8}/src/witbitz_code/attachments.py +0 -0
- {witbitz_code-1.3.7 → witbitz_code-1.3.8}/src/witbitz_code/auto.py +0 -0
- {witbitz_code-1.3.7 → witbitz_code-1.3.8}/src/witbitz_code/auto_runner.py +0 -0
- {witbitz_code-1.3.7 → witbitz_code-1.3.8}/src/witbitz_code/codekeys.py +0 -0
- {witbitz_code-1.3.7 → witbitz_code-1.3.8}/src/witbitz_code/codetools.py +0 -0
- {witbitz_code-1.3.7 → witbitz_code-1.3.8}/src/witbitz_code/compress.py +0 -0
- {witbitz_code-1.3.7 → witbitz_code-1.3.8}/src/witbitz_code/computers.py +0 -0
- {witbitz_code-1.3.7 → witbitz_code-1.3.8}/src/witbitz_code/confidential.py +0 -0
- {witbitz_code-1.3.7 → witbitz_code-1.3.8}/src/witbitz_code/confidential_models.json +0 -0
- {witbitz_code-1.3.7 → witbitz_code-1.3.8}/src/witbitz_code/connector.py +0 -0
- {witbitz_code-1.3.7 → witbitz_code-1.3.8}/src/witbitz_code/devicelink.py +0 -0
- {witbitz_code-1.3.7 → witbitz_code-1.3.8}/src/witbitz_code/folders.py +0 -0
- {witbitz_code-1.3.7 → witbitz_code-1.3.8}/src/witbitz_code/gateway.py +0 -0
- {witbitz_code-1.3.7 → witbitz_code-1.3.8}/src/witbitz_code/keys.py +0 -0
- {witbitz_code-1.3.7 → witbitz_code-1.3.8}/src/witbitz_code/link.py +0 -0
- {witbitz_code-1.3.7 → witbitz_code-1.3.8}/src/witbitz_code/models.py +0 -0
- {witbitz_code-1.3.7 → witbitz_code-1.3.8}/src/witbitz_code/net.py +0 -0
- {witbitz_code-1.3.7 → witbitz_code-1.3.8}/src/witbitz_code/offered_models.json +0 -0
- {witbitz_code-1.3.7 → witbitz_code-1.3.8}/src/witbitz_code/opencode_plugins/notes-init.md +0 -0
- {witbitz_code-1.3.7 → witbitz_code-1.3.8}/src/witbitz_code/opencode_plugins/witbitz-media-server.mjs +0 -0
- {witbitz_code-1.3.7 → witbitz_code-1.3.8}/src/witbitz_code/opencode_plugins/witbitz-notes.js +0 -0
- {witbitz_code-1.3.7 → witbitz_code-1.3.8}/src/witbitz_code/opencode_plugins/witbitz-progress.js +0 -0
- {witbitz_code-1.3.7 → witbitz_code-1.3.8}/src/witbitz_code/outputs.py +0 -0
- {witbitz_code-1.3.7 → witbitz_code-1.3.8}/src/witbitz_code/payers.py +0 -0
- {witbitz_code-1.3.7 → witbitz_code-1.3.8}/src/witbitz_code/plugins.py +0 -0
- {witbitz_code-1.3.7 → witbitz_code-1.3.8}/src/witbitz_code/policy.py +0 -0
- {witbitz_code-1.3.7 → witbitz_code-1.3.8}/src/witbitz_code/qr.py +0 -0
- {witbitz_code-1.3.7 → witbitz_code-1.3.8}/src/witbitz_code/receipt.py +0 -0
- {witbitz_code-1.3.7 → witbitz_code-1.3.8}/src/witbitz_code/relay.py +0 -0
- {witbitz_code-1.3.7 → witbitz_code-1.3.8}/src/witbitz_code/seen.py +0 -0
- {witbitz_code-1.3.7 → witbitz_code-1.3.8}/src/witbitz_code/tinfoil_read.py +0 -0
- {witbitz_code-1.3.7 → witbitz_code-1.3.8}/src/witbitz_code/tools_catalog.json +0 -0
- {witbitz_code-1.3.7 → witbitz_code-1.3.8}/src/witbitz_code/tools_probe.py +0 -0
- {witbitz_code-1.3.7 → witbitz_code-1.3.8}/src/witbitz_code/uninstall.py +0 -0
- {witbitz_code-1.3.7 → witbitz_code-1.3.8}/src/witbitz_code/winagent.py +0 -0
- {witbitz_code-1.3.7 → witbitz_code-1.3.8}/src/witbitz_code/winconsole.py +0 -0
- {witbitz_code-1.3.7 → witbitz_code-1.3.8}/src/witbitz_code.egg-info/SOURCES.txt +0 -0
- {witbitz_code-1.3.7 → witbitz_code-1.3.8}/src/witbitz_code.egg-info/dependency_links.txt +0 -0
- {witbitz_code-1.3.7 → witbitz_code-1.3.8}/src/witbitz_code.egg-info/entry_points.txt +0 -0
- {witbitz_code-1.3.7 → witbitz_code-1.3.8}/src/witbitz_code.egg-info/requires.txt +0 -0
- {witbitz_code-1.3.7 → witbitz_code-1.3.8}/src/witbitz_code.egg-info/top_level.txt +0 -0
- {witbitz_code-1.3.7 → witbitz_code-1.3.8}/tests/test_asks.py +0 -0
- {witbitz_code-1.3.7 → witbitz_code-1.3.8}/tests/test_attachments.py +0 -0
- {witbitz_code-1.3.7 → witbitz_code-1.3.8}/tests/test_auto.py +0 -0
- {witbitz_code-1.3.7 → witbitz_code-1.3.8}/tests/test_cli.py +0 -0
- {witbitz_code-1.3.7 → witbitz_code-1.3.8}/tests/test_codekeys.py +0 -0
- {witbitz_code-1.3.7 → witbitz_code-1.3.8}/tests/test_codetools.py +0 -0
- {witbitz_code-1.3.7 → witbitz_code-1.3.8}/tests/test_confidential.py +0 -0
- {witbitz_code-1.3.7 → witbitz_code-1.3.8}/tests/test_connector_e2e.py +0 -0
- {witbitz_code-1.3.7 → witbitz_code-1.3.8}/tests/test_devicelink.py +0 -0
- {witbitz_code-1.3.7 → witbitz_code-1.3.8}/tests/test_files_and_compress.py +0 -0
- {witbitz_code-1.3.7 → witbitz_code-1.3.8}/tests/test_folders.py +0 -0
- {witbitz_code-1.3.7 → witbitz_code-1.3.8}/tests/test_outputs.py +0 -0
- {witbitz_code-1.3.7 → witbitz_code-1.3.8}/tests/test_plugins.py +0 -0
- {witbitz_code-1.3.7 → witbitz_code-1.3.8}/tests/test_policy.py +0 -0
- {witbitz_code-1.3.7 → witbitz_code-1.3.8}/tests/test_ports.py +0 -0
- {witbitz_code-1.3.7 → witbitz_code-1.3.8}/tests/test_registry.py +0 -0
- {witbitz_code-1.3.7 → witbitz_code-1.3.8}/tests/test_relay_codec.py +0 -0
- {witbitz_code-1.3.7 → witbitz_code-1.3.8}/tests/test_tools_probe.py +0 -0
- {witbitz_code-1.3.7 → witbitz_code-1.3.8}/tests/test_uninstall.py +0 -0
- {witbitz_code-1.3.7 → witbitz_code-1.3.8}/tests/test_winagent.py +0 -0
- {witbitz_code-1.3.7 → witbitz_code-1.3.8}/tests/test_winconsole.py +0 -0
- {witbitz_code-1.3.7 → witbitz_code-1.3.8}/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.3.
|
|
3
|
+
Version: 1.3.8
|
|
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
|
|
@@ -88,6 +88,10 @@ The same thing one step at a time: install OpenCode (`curl -fsSL https://opencod
|
|
|
88
88
|
|
|
89
89
|
`pair`, `rotate` and `unpair` also accept `--dry-run`.
|
|
90
90
|
|
|
91
|
+
**Windows and WSL on one computer** are two computers to Spaces, and both can run: WSL shares Windows' ports, so the second
|
|
92
|
+
one `setup` meets gets its own (it asks — 4097, and 4197 for its model proxy) and `serve` remembers it. WSL's is named
|
|
93
|
+
"<computer> (WSL)"; one paired before 1.3.8 is renamed by its next `setup`.
|
|
94
|
+
|
|
91
95
|
**More than one account.** Each account that scans the QR code gets its own pairing, with its own secret and relay
|
|
92
96
|
channel. OpenCode has no users, so every paired account reaches the same sessions, files and shell. That's fine when
|
|
93
97
|
all the accounts are yours. If a second account belongs to **another person**, run a separate OpenCode for them
|
|
@@ -62,6 +62,10 @@ The same thing one step at a time: install OpenCode (`curl -fsSL https://opencod
|
|
|
62
62
|
|
|
63
63
|
`pair`, `rotate` and `unpair` also accept `--dry-run`.
|
|
64
64
|
|
|
65
|
+
**Windows and WSL on one computer** are two computers to Spaces, and both can run: WSL shares Windows' ports, so the second
|
|
66
|
+
one `setup` meets gets its own (it asks — 4097, and 4197 for its model proxy) and `serve` remembers it. WSL's is named
|
|
67
|
+
"<computer> (WSL)"; one paired before 1.3.8 is renamed by its next `setup`.
|
|
68
|
+
|
|
65
69
|
**More than one account.** Each account that scans the QR code gets its own pairing, with its own secret and relay
|
|
66
70
|
channel. OpenCode has no users, so every paired account reaches the same sessions, files and shell. That's fine when
|
|
67
71
|
all the accounts are yours. If a second account belongs to **another person**, run a separate OpenCode for them
|
|
@@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"
|
|
|
4
4
|
|
|
5
5
|
[project]
|
|
6
6
|
name = "witbitz-code"
|
|
7
|
-
version = "1.3.
|
|
7
|
+
version = "1.3.8"
|
|
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"
|
|
@@ -156,6 +156,10 @@ async def serve(args: list[str]) -> int:
|
|
|
156
156
|
if not everything:
|
|
157
157
|
_err("witbitz-code: this computer is not paired yet — run: witbitz-code setup")
|
|
158
158
|
return 1
|
|
159
|
+
# No --port: the port this computer is paired for (setup moves a pairing off a port WSL's or Windows' copy holds)
|
|
160
|
+
paired_ports = sorted({wizard.port_of(p) for p in everything})
|
|
161
|
+
if "--port" not in args and len(paired_ports) == 1:
|
|
162
|
+
port = paired_ports[0]
|
|
159
163
|
# One OpenCode per port, one connector per OpenCode: two `serve`s (one per account) must never both answer a channel.
|
|
160
164
|
pairings = pairings_for_port(everything, port)
|
|
161
165
|
if not pairings:
|
|
@@ -311,7 +315,7 @@ async def setup(args: list[str]) -> int:
|
|
|
311
315
|
d = wizard.Deps(
|
|
312
316
|
say=_err, ask=lambda q: keys.read_line(q), secret=lambda q: keys.read_line(q, hidden=True),
|
|
313
317
|
port=port, port_explicit="--port" in args,
|
|
314
|
-
pair=lambda p: pair_cmd.main(["--port", str(p)]),
|
|
318
|
+
pair=lambda p: pair_cmd.main(["--port", str(p)]), rename=lambda p, name: pair_cmd.rename(p, name),
|
|
315
319
|
keys_state=keys_state,
|
|
316
320
|
run_tools=lambda plan: toolsetup.run_plan(plan, say=_err), refresh_path=refresh_path,
|
|
317
321
|
is_listening=is_listening, serve_here=lambda p: serve(["--port", str(p)]),
|
|
@@ -124,6 +124,26 @@ async def _link_by_qr(link_fn: LinkFn | None, out: Callable, err: Callable) -> d
|
|
|
124
124
|
return account
|
|
125
125
|
|
|
126
126
|
|
|
127
|
+
async def rename(pairing: dict, name: str, *, call: net.ApiCall | None = None, out: Callable[[str], Any] = _out,
|
|
128
|
+
err: Callable[[str], Any] = _err) -> bool:
|
|
129
|
+
"""A pairing's label: in the account's computers (devices show it at their next sync), then in the pairings file. No scan."""
|
|
130
|
+
client = None
|
|
131
|
+
if call is None:
|
|
132
|
+
client = net.new_client()
|
|
133
|
+
call = net.make_api_call(client)
|
|
134
|
+
try:
|
|
135
|
+
if not await _publish_entry(call, {**pairing, "name": name}, out, err):
|
|
136
|
+
return False
|
|
137
|
+
finally:
|
|
138
|
+
if client is not None:
|
|
139
|
+
await client.aclose()
|
|
140
|
+
doc = pp.read_pairings()
|
|
141
|
+
doc["pairings"] = [({**x, "name": name} if isinstance(x, dict) and x.get("computerId") == pairing.get("computerId") else x)
|
|
142
|
+
for x in doc.get("pairings") or []]
|
|
143
|
+
pp.write_pairings(doc)
|
|
144
|
+
return True
|
|
145
|
+
|
|
146
|
+
|
|
127
147
|
async def main(argv: list[str], *, call: net.ApiCall | None = None, link_fn: LinkFn | None = None,
|
|
128
148
|
out: Callable[[str], Any] = _out, err: Callable[[str], Any] = _err, keys: Any = None) -> int:
|
|
129
149
|
"""pair / --status / --unpair / --rotate. Returns the exit code. `keys`: the coding-key calls (codekeys.mint /
|
|
@@ -91,7 +91,13 @@ def new_computer_id() -> str:
|
|
|
91
91
|
|
|
92
92
|
|
|
93
93
|
def hostname() -> str:
|
|
94
|
-
|
|
94
|
+
"""The label a new pairing gets: this computer's name — on WSL "<name> (WSL)": WSL calls itself by the Windows computer's
|
|
95
|
+
name, and the Windows side and the Linux side are two computers in the account (the owner, 2026-10-09: "one running in
|
|
96
|
+
WSL and one in windows … noted as separate devices")."""
|
|
97
|
+
from .ports import on_wsl # noqa: PLC0415
|
|
98
|
+
|
|
99
|
+
h = socket.gethostname()
|
|
100
|
+
return f"{h} (WSL)" if on_wsl() else h
|
|
95
101
|
|
|
96
102
|
|
|
97
103
|
# ── the pairings doc (pure) ───────────────────────────────────────────────────────────────────────────────────────────
|
|
@@ -137,3 +137,31 @@ def win_owners(port: int, *, run: Callable[[list[str]], str] = _run_text, user:
|
|
|
137
137
|
if row and row["pid"] == pid and (not user or row["user"].rsplit("\\", 1)[-1].lower() == user.lower()):
|
|
138
138
|
hits.append({"pid": pid, "cmd": row["image"]})
|
|
139
139
|
return hits
|
|
140
|
+
|
|
141
|
+
|
|
142
|
+
def _run_status(argv: list[str], env: dict | None = None) -> tuple[int, str]:
|
|
143
|
+
try:
|
|
144
|
+
r = subprocess.run(argv, capture_output=True, text=True, errors="replace", timeout=15, env=env)
|
|
145
|
+
return r.returncode, r.stdout or ""
|
|
146
|
+
except (OSError, subprocess.SubprocessError):
|
|
147
|
+
return -1, ""
|
|
148
|
+
|
|
149
|
+
|
|
150
|
+
def wsl_running(run: Callable[[list[str]], tuple[int, str]] | None = None) -> list[str]:
|
|
151
|
+
"""Windows: the WSL distros running now. WSL_UTF8=1, or wsl.exe answers in UTF-16; a distro name has no spaces, so a
|
|
152
|
+
message line ("There are no running distributions.") is never taken for one."""
|
|
153
|
+
run = run or (lambda argv: _run_status(argv, {**os.environ, "WSL_UTF8": "1"}))
|
|
154
|
+
code, out = run(["wsl", "-l", "--running", "-q"])
|
|
155
|
+
if code != 0:
|
|
156
|
+
return []
|
|
157
|
+
return [n for n in (ln.strip() for ln in out.replace("\x00", "").splitlines()) if re.fullmatch(r"[A-Za-z0-9._-]+", n)]
|
|
158
|
+
|
|
159
|
+
|
|
160
|
+
def held_in_wsl(port: int, *, platform: str | None = None, run: Callable[[list[str]], str] = _run_text,
|
|
161
|
+
wsl: Callable[[], list[str]] = wsl_running) -> list[str]:
|
|
162
|
+
"""Windows: the running WSL distros, when `port` answers but no Windows program listens on it — Linux and Windows share
|
|
163
|
+
localhost ports (WSL's mirrored networking) and Windows cannot name the Linux program (the owner's laptop, 2026-10-09:
|
|
164
|
+
netstat showed nothing on 4096, UbuntuD was running an earlier witbitz-code serve). [] off Windows."""
|
|
165
|
+
if (sys.platform if platform is None else platform) != "win32" or parse_netstat_listeners(run(["netstat", "-ano"]), port):
|
|
166
|
+
return []
|
|
167
|
+
return wsl()
|
|
@@ -119,6 +119,15 @@ def install_release(step: dict, *, client: httpx.Client | None = None, dest: str
|
|
|
119
119
|
return str(to)
|
|
120
120
|
|
|
121
121
|
|
|
122
|
+
# winget's "no newer version" (APPINSTALLER_CLI_ERROR_UPDATE_NOT_APPLICABLE, 0x8A15002B) and "already installed"
|
|
123
|
+
# (0x8A150061): the tool is HERE (the owner's laptop, 2026-10-09: Pandoc and ImageMagick "ended with 2316632107"). = runPlan
|
|
124
|
+
WINGET_ALREADY = (0x8A15002B, 0x8A150061)
|
|
125
|
+
|
|
126
|
+
|
|
127
|
+
def already_installed(argv: list[str], code: int) -> bool:
|
|
128
|
+
return argv[:1] == ["winget"] and code != -1 and (code & 0xFFFFFFFF) in WINGET_ALREADY
|
|
129
|
+
|
|
130
|
+
|
|
122
131
|
def run_plan(steps: list[dict], *, say: Callable[[str], Any] = print, run: Callable[[list[str]], int] = run_inherit,
|
|
123
132
|
release: Callable[[dict], str] = install_release) -> dict:
|
|
124
133
|
"""Carry out the plan → {done, failed, auto, none} (tool ids). Says each step as it goes. = code-tools-install.mjs runPlan"""
|
|
@@ -127,8 +136,11 @@ def run_plan(steps: list[dict], *, say: Callable[[str], Any] = print, run: Calla
|
|
|
127
136
|
if s["kind"] == "cmd":
|
|
128
137
|
say(f" $ {s['argv'][2] if s['argv'][0] == 'sh' else ' '.join(s['argv'])}")
|
|
129
138
|
code = run(s["argv"])
|
|
130
|
-
|
|
131
|
-
if code
|
|
139
|
+
here = code != 0 and already_installed(s["argv"], code)
|
|
140
|
+
(out["done"] if code == 0 or here else out["failed"]).extend(s["ids"])
|
|
141
|
+
if here:
|
|
142
|
+
say(f" ✓ {', '.join(label(i) for i in s['ids'])}: already installed")
|
|
143
|
+
elif code != 0:
|
|
132
144
|
say(f" ✖ {', '.join(label(i) for i in s['ids'])}: that {'could not start' if code == -1 else f'ended with {code}'}")
|
|
133
145
|
elif s["kind"] == "release":
|
|
134
146
|
say(f" Downloading {label(s['id'])} ({s['mb']} MB) from its publisher, checked against its SHA-256 …")
|
|
@@ -14,6 +14,7 @@ from __future__ import annotations
|
|
|
14
14
|
import asyncio
|
|
15
15
|
import os
|
|
16
16
|
import shutil
|
|
17
|
+
import socket
|
|
17
18
|
import subprocess
|
|
18
19
|
import sys
|
|
19
20
|
from collections.abc import Awaitable, Callable
|
|
@@ -75,6 +76,9 @@ class Deps:
|
|
|
75
76
|
forget_keys: Callable[[dict], Any] = lambda p: codekeys.forget(p.get("computerId") or "")
|
|
76
77
|
is_listening: Callable[[int], Awaitable[bool]] = None # type: ignore[assignment]
|
|
77
78
|
port_owners: Callable[[int], list] = ports.port_owners # [{pid, cmd}] — this user's processes on a port (ports.py)
|
|
79
|
+
held_in_wsl: Callable[[int], list] = ports.held_in_wsl # Windows: running WSL distros when no Windows program holds the port
|
|
80
|
+
bare_host: Callable[[], str] = socket.gethostname # the computer's own name (WSL reports Windows')
|
|
81
|
+
rename: Callable[[dict, str], Awaitable[bool]] | None = None # pair.rename: a pairing's label, here and in the account
|
|
78
82
|
stop_process: Callable[[int], bool] = lambda pid: __import__("witbitz_code.uninstall", fromlist=["stop_process"]).stop_process(pid)
|
|
79
83
|
wsl: bool = field(default_factory=ports.on_wsl)
|
|
80
84
|
# setup's Tools step (toolsetup.py): what is here, carrying out the plan, and PATH after installs (Windows: the registry)
|
|
@@ -158,6 +162,11 @@ async def run_setup(d: Deps) -> dict:
|
|
|
158
162
|
d.say(f" ✖ This computer is paired, but for OpenCode on another port. Run setup again with that port: witbitz-code setup --port {d.port_of(everything[0])}"
|
|
159
163
|
if everything else " ✖ Pairing did not finish. Run setup again to retry.")
|
|
160
164
|
return {**result, "stopped": "pair"}
|
|
165
|
+
if d.wsl and d.rename: # WSL calls itself by the Windows computer's name: two computers in the account, one name (1.3.7 and before)
|
|
166
|
+
for p in [x for x in mine if x.get("name") == d.bare_host()]:
|
|
167
|
+
if await d.rename(p, f"{p['name']} (WSL)"):
|
|
168
|
+
d.say(f' ✓ Renamed this computer "{p["name"]} (WSL)" — Windows on the same machine keeps "{p["name"]}"')
|
|
169
|
+
mine = on(d.all_pairings(), port)
|
|
161
170
|
d.say(f" ✓ Paired: {named(mine)}{f' (OpenCode on port {port})' if port != 4096 else ''}")
|
|
162
171
|
result["paired"] = True
|
|
163
172
|
|
|
@@ -201,9 +210,24 @@ async def run_setup(d: Deps) -> dict:
|
|
|
201
210
|
if all(o["pid"] != x["pid"] for x in owners):
|
|
202
211
|
owners.append(o)
|
|
203
212
|
if not owners:
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
213
|
+
# Not this person's program here — on Windows most likely the witbitz-code in WSL (Linux and Windows share these
|
|
214
|
+
# ports; the owner, 2026-10-09: "I want one running in WSL and one in windows"). Both can run: this one moves.
|
|
215
|
+
free = await _free_port(d, port + 1)
|
|
216
|
+
distros = d.held_in_wsl(busy[0])
|
|
217
|
+
if distros:
|
|
218
|
+
d.say(f" Port {busy[0]} is used inside WSL (Linux on this computer — running: {', '.join(distros)}), most likely by the")
|
|
219
|
+
d.say(" witbitz-code there: Windows and WSL share these ports. Both can run — this one just needs its own.")
|
|
220
|
+
else:
|
|
221
|
+
d.say(f" Port {busy[0]} is taken by a program that is not yours{' — on WSL, Windows and the other Linux distros on this computer share its ports' if d.wsl else ''}.")
|
|
222
|
+
if not _yes(d.ask(f" Use port {free} (and {free + 100} for the model proxy) here instead? [Y/n] ")):
|
|
223
|
+
d.say(f" Stopped. To use another port later: witbitz-code setup --port {free}"
|
|
224
|
+
+ (" — or stop WSL's copy: wsl --shutdown" if distros else ""))
|
|
225
|
+
return {**result, "stopped": "busy"}
|
|
226
|
+
for p in on(d.all_pairings(), port):
|
|
227
|
+
d.move_pairing(p, free)
|
|
228
|
+
d.say(f" ✓ This computer now uses port {free} — `witbitz-code serve` remembers it")
|
|
229
|
+
port = free
|
|
230
|
+
continue
|
|
207
231
|
listed = "; ".join(f"{(o.get('cmd') or 'a program')[:70]} (process {o['pid']})" for o in owners)
|
|
208
232
|
d.say(f" Already running here: {listed}.")
|
|
209
233
|
d.say(" That is an earlier witbitz-code or an OpenCode started some other way — it would keep answering your phone with")
|
|
@@ -240,8 +264,20 @@ def _describe(step: dict) -> str:
|
|
|
240
264
|
return f" {toolsetup.label(step['id'])}: nothing here installs it" + (f" ({step['note']})" if step.get("note") else "")
|
|
241
265
|
|
|
242
266
|
|
|
267
|
+
async def _free_port(d: Deps, start: int) -> int:
|
|
268
|
+
"""The first port from `start` that is free together with its model proxy's (port + 100)."""
|
|
269
|
+
for p in range(max(start, 1024), 65436):
|
|
270
|
+
if not await d.is_listening(p) and not await d.is_listening(p + 100):
|
|
271
|
+
return p
|
|
272
|
+
return start
|
|
273
|
+
|
|
274
|
+
|
|
243
275
|
async def _tools_step(d: Deps) -> str:
|
|
244
276
|
"""→ 'here' | 'installed' | 'partly' | 'skipped' | 'unavailable' | 'failed' (an install that breaks never ends setup)."""
|
|
277
|
+
try: # Windows: what an earlier setup installed is on the registry's PATH, not yet on this window's — read it FIRST, or
|
|
278
|
+
d.refresh_path() # winget is asked again for tools that are here (the owner's laptop, 2026-10-09: Pandoc, ImageMagick)
|
|
279
|
+
except Exception: # noqa: BLE001
|
|
280
|
+
pass
|
|
245
281
|
try:
|
|
246
282
|
report = d.probe_tools()
|
|
247
283
|
except Exception: # noqa: BLE001 — a probe that fails never stops setup
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: witbitz-code
|
|
3
|
-
Version: 1.3.
|
|
3
|
+
Version: 1.3.8
|
|
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
|
|
@@ -88,6 +88,10 @@ The same thing one step at a time: install OpenCode (`curl -fsSL https://opencod
|
|
|
88
88
|
|
|
89
89
|
`pair`, `rotate` and `unpair` also accept `--dry-run`.
|
|
90
90
|
|
|
91
|
+
**Windows and WSL on one computer** are two computers to Spaces, and both can run: WSL shares Windows' ports, so the second
|
|
92
|
+
one `setup` meets gets its own (it asks — 4097, and 4197 for its model proxy) and `serve` remembers it. WSL's is named
|
|
93
|
+
"<computer> (WSL)"; one paired before 1.3.8 is renamed by its next `setup`.
|
|
94
|
+
|
|
91
95
|
**More than one account.** Each account that scans the QR code gets its own pairing, with its own secret and relay
|
|
92
96
|
channel. OpenCode has no users, so every paired account reaches the same sessions, files and shell. That's fine when
|
|
93
97
|
all the accounts are yours. If a second account belongs to **another person**, run a separate OpenCode for them
|
|
@@ -274,3 +274,30 @@ def test_status_output_is_json_free_of_secrets_even_for_js_written_files():
|
|
|
274
274
|
run(pair.main(["--status"], **cap.kw()))
|
|
275
275
|
assert SECRET not in cap.text() and "MK" not in cap.text()
|
|
276
276
|
assert json.loads(pp.pairings_path().read_text())["pairings"][0]["secret"] == SECRET
|
|
277
|
+
|
|
278
|
+
|
|
279
|
+
def test_rename_relabels_the_computer_in_the_account_and_here_with_no_scan(monkeypatch):
|
|
280
|
+
# WSL's pairing and Windows' were two "AlonASUS" in Code (the owner, 2026-10-09); setup renames WSL's "… (WSL)"
|
|
281
|
+
api = MockApi(docs={"computers": with_computer(None, "cmp_wsl_1", {"name": "AlonASUS", "relay": RELAY, "secret": SECRET}, 500)})
|
|
282
|
+
entry = {"account": "a@x.com", "idx": IDX, "computerId": "cmp_wsl_1", "secret": SECRET, "name": "AlonASUS", "relay": RELAY,
|
|
283
|
+
"opencodeUrl": "http://127.0.0.1:4096"}
|
|
284
|
+
pp.write_pairings({"v": 1, "pairings": [entry]})
|
|
285
|
+
assert run(pair.rename(entry, "AlonASUS (WSL)", call=api.call, out=lambda m: None, err=lambda m: None)) is True
|
|
286
|
+
reg = norm_registry(api.plain("computers"))
|
|
287
|
+
assert reg["computers"]["cmp_wsl_1"]["name"] == "AlonASUS (WSL)" and reg["computers"]["cmp_wsl_1"]["secret"] == SECRET
|
|
288
|
+
assert [p["name"] for p in pp.read_pairings()["pairings"]] == ["AlonASUS (WSL)"]
|
|
289
|
+
refused = MockApi(read_status=500)
|
|
290
|
+
assert run(pair.rename({**entry, "name": "AlonASUS (WSL)"}, "x", call=refused.call, out=lambda m: None, err=lambda m: None)) is False
|
|
291
|
+
assert [p["name"] for p in pp.read_pairings()["pairings"]] == ["AlonASUS (WSL)"], "nothing renamed here when the account refused"
|
|
292
|
+
|
|
293
|
+
|
|
294
|
+
def test_a_new_pairing_on_wsl_is_named_for_wsl(monkeypatch):
|
|
295
|
+
import socket
|
|
296
|
+
|
|
297
|
+
from witbitz_code import ports
|
|
298
|
+
|
|
299
|
+
monkeypatch.setattr(socket, "gethostname", lambda: "AlonASUS")
|
|
300
|
+
monkeypatch.setattr(ports, "on_wsl", lambda: True)
|
|
301
|
+
assert pp.hostname() == "AlonASUS (WSL)"
|
|
302
|
+
monkeypatch.setattr(ports, "on_wsl", lambda: False)
|
|
303
|
+
assert pp.hostname() == "AlonASUS"
|
|
@@ -81,3 +81,29 @@ def test_stopping_on_windows_is_taskkill_of_the_tree_and_never_os_kill(monkeypat
|
|
|
81
81
|
monkeypatch.setattr(sys, "platform", "win32")
|
|
82
82
|
monkeypatch.setattr(uninstall, "win_stop_process", lambda pid: stopped.append(pid) or True)
|
|
83
83
|
assert uninstall.stop_process(5150) is True and stopped == [5150]
|
|
84
|
+
|
|
85
|
+
|
|
86
|
+
def test_wsl_is_named_only_when_no_windows_program_holds_the_port():
|
|
87
|
+
wsl = lambda: ["UbuntuD"] # noqa: E731
|
|
88
|
+
assert ports.held_in_wsl(4096, platform="win32", run=lambda a: NETSTAT, wsl=wsl) == [], "opencode.exe holds 4096 here"
|
|
89
|
+
assert ports.held_in_wsl(4097, platform="win32", run=lambda a: NETSTAT, wsl=wsl) == ["UbuntuD"]
|
|
90
|
+
assert ports.held_in_wsl(4097, platform="linux", run=lambda a: NETSTAT, wsl=wsl) == []
|
|
91
|
+
|
|
92
|
+
|
|
93
|
+
def test_running_distros_are_read_whatever_wsl_prints():
|
|
94
|
+
assert ports.wsl_running(lambda a: (0, "UbuntuD\r\nDebian\r\n")) == ["UbuntuD", "Debian"]
|
|
95
|
+
assert ports.wsl_running(lambda a: (0, "U\x00b\x00u\x00n\x00t\x00u\x00D\x00\r\x00\n\x00")) == ["UbuntuD"], "UTF-16 read as text"
|
|
96
|
+
assert ports.wsl_running(lambda a: (0, "There are no running distributions.\r\n")) == []
|
|
97
|
+
assert ports.wsl_running(lambda a: (1, "UbuntuD")) == [], "an error answer names nothing"
|
|
98
|
+
|
|
99
|
+
|
|
100
|
+
@requires_node
|
|
101
|
+
def test_the_javascript_names_wsl_the_same_way():
|
|
102
|
+
js = run_node("""
|
|
103
|
+
import { heldInWsl, wslRunning } from '@TOOLS@/code-ports-win.mjs'
|
|
104
|
+
OUT({ held: [4096, 4097].map((p) => heldInWsl(p, { platform: 'win32', run: () => INPUT.netstat, wsl: () => ['UbuntuD'] })),
|
|
105
|
+
off: heldInWsl(4097, { platform: 'linux', run: () => INPUT.netstat, wsl: () => ['UbuntuD'] }),
|
|
106
|
+
names: INPUT.outs.map(([status, out]) => wslRunning({ run: () => ({ status, out }) })) })
|
|
107
|
+
""", {"netstat": NETSTAT, "outs": [[0, "UbuntuD\r\nDebian\r\n"], [0, "U\x00b\x00u\x00n\x00t\x00u\x00D\x00\r\x00\n\x00"], [0, "There are no running distributions.\r\n"], [1, "UbuntuD"]]})
|
|
108
|
+
assert js["held"] == [[], ["UbuntuD"]] and js["off"] == []
|
|
109
|
+
assert js["names"] == [["UbuntuD", "Debian"], ["UbuntuD"], [], []]
|
|
@@ -101,6 +101,9 @@ class Fake:
|
|
|
101
101
|
self._oc, self._pairings, self._keyed, self._gives = opencode, list(pairings or []), set(keyed), pair_gives_keys
|
|
102
102
|
self._answers, self._listening = list(answers), listening
|
|
103
103
|
|
|
104
|
+
def _move(self, p, port):
|
|
105
|
+
self._pairings = [({**x, "opencodeUrl": f"http://127.0.0.1:{port}"} if x.get("computerId") == p.get("computerId") else x) for x in self._pairings]
|
|
106
|
+
|
|
104
107
|
def deps(self, **over):
|
|
105
108
|
async def pair(port):
|
|
106
109
|
self.paired.append(port)
|
|
@@ -109,19 +112,19 @@ class Fake:
|
|
|
109
112
|
if self._gives:
|
|
110
113
|
self._keyed.add("c")
|
|
111
114
|
async def listening(port):
|
|
112
|
-
return self._listening
|
|
115
|
+
return self._listening(port) if callable(self._listening) else self._listening
|
|
113
116
|
async def serve_here(port):
|
|
114
117
|
self.served.append(port)
|
|
115
118
|
return 0
|
|
116
119
|
return wizard.Deps(
|
|
117
120
|
say=self.said.append, ask=lambda q: self._answers.pop(0) if self._answers else "", secret=lambda q: self.asked_secret.append(q) or "",
|
|
118
121
|
find_opencode=lambda: "/usr/bin/opencode" if self._oc else "", can_install_opencode=lambda: False,
|
|
119
|
-
all_pairings=lambda: list(self._pairings), pair=pair, move_pairing=
|
|
122
|
+
all_pairings=lambda: list(self._pairings), pair=pair, move_pairing=self._move,
|
|
120
123
|
code_keys=lambda p: {"caps": {"modelsUsd": 50, "toolsUsd": 5}} if p.get("computerId") in self._keyed else None,
|
|
121
124
|
serve_here=serve_here,
|
|
122
125
|
# never this machine's real processes: the real lookup finds whatever listens on 4096 HERE (an OpenCode does)
|
|
123
126
|
# and never this machine's tools: nothing is missing, nothing is installed (the Tools step has its own tests)
|
|
124
|
-
**{"is_listening": listening, "port_owners": lambda port: [], "stop_process": lambda pid: False, "wsl": False,
|
|
127
|
+
**{"is_listening": listening, "port_owners": lambda port: [], "held_in_wsl": lambda port: [], "stop_process": lambda pid: False, "wsl": False,
|
|
125
128
|
"probe_tools": lambda: {"tools": {}, "platform": {"os": "", "pm": ""}}, "run_tools": lambda plan: {}, "refresh_path": lambda: None, **over})
|
|
126
129
|
|
|
127
130
|
|
|
@@ -166,12 +169,19 @@ def test_paired_for_another_port_uses_it():
|
|
|
166
169
|
assert r["port"] == 4097 and f.served == [4097] and f.paired == []
|
|
167
170
|
|
|
168
171
|
|
|
169
|
-
|
|
170
|
-
|
|
172
|
+
DESK = {"name": "desk", "opencodeUrl": "http://127.0.0.1:4096", "idx": {"room": "r"}, "computerId": "c"}
|
|
173
|
+
HELD = lambda p: p in (4096, 4196) # noqa: E731 — OpenCode's port and the proxy's, held by someone else
|
|
174
|
+
|
|
175
|
+
|
|
176
|
+
def test_a_port_taken_by_a_program_that_is_not_yours_moves_this_computer_to_a_free_pair_and_serves():
|
|
177
|
+
f = Fake(pairings=[DESK], keyed={"c"}, listening=HELD, answers=[""])
|
|
171
178
|
r = run(wizard.run_setup(f.deps()))
|
|
172
|
-
assert
|
|
179
|
+
assert f.served == [4097] and wizard.port_of(f._pairings[0]) == 4097, "the pairing moves — no new scan"
|
|
173
180
|
assert any("Port 4096 is taken by a program that is not yours" in line for line in f.said)
|
|
174
|
-
assert any("
|
|
181
|
+
assert any("now uses port 4097" in line for line in f.said) and "stopped" not in r
|
|
182
|
+
no = Fake(pairings=[DESK], keyed={"c"}, listening=HELD, answers=["n"])
|
|
183
|
+
assert run(wizard.run_setup(no.deps()))["stopped"] == "busy" and no.served == []
|
|
184
|
+
assert any("witbitz-code setup --port 4097" in line for line in no.said)
|
|
175
185
|
|
|
176
186
|
|
|
177
187
|
def test_an_earlier_witbitz_code_still_running_is_named_and_stopped_both_ports_then_this_one_serves():
|
|
@@ -222,3 +232,37 @@ def test_keys_that_cannot_be_checked_are_not_taken_for_a_removal_and_setup_says_
|
|
|
222
232
|
r = run(wizard.run_setup(f.deps(keys_state=keys_state, forget_keys=lambda p: f._keyed.discard(p["computerId"]))))
|
|
223
233
|
assert f.paired == [] and r["keys"] and f.served == [4096]
|
|
224
234
|
assert any("Could not check them with your account just now — the signature does not match this computer (401)" in line for line in f.said)
|
|
235
|
+
|
|
236
|
+
|
|
237
|
+
def test_windows_and_wsl_each_run_their_own_the_windows_one_moves_off_the_port_wsl_holds():
|
|
238
|
+
# the owner's laptop, 2026-10-09: 4096 answered, netstat showed no Windows program, UbuntuD ran witbitz-code serve —
|
|
239
|
+
# "I want one running in WSL and one in windows"
|
|
240
|
+
f = Fake(pairings=[DESK], keyed={"c"}, listening=HELD, answers=[""])
|
|
241
|
+
run(wizard.run_setup(f.deps(held_in_wsl=lambda port: ["UbuntuD"])))
|
|
242
|
+
assert f.served == [4097]
|
|
243
|
+
assert any("Port 4096 is used inside WSL (Linux on this computer — running: UbuntuD)" in line for line in f.said)
|
|
244
|
+
assert not any("not yours" in line for line in f.said)
|
|
245
|
+
no = Fake(pairings=[DESK], keyed={"c"}, listening=HELD, answers=["n"])
|
|
246
|
+
assert run(wizard.run_setup(no.deps(held_in_wsl=lambda port: ["UbuntuD"])))["stopped"] == "busy"
|
|
247
|
+
assert any("wsl --shutdown" in line for line in no.said)
|
|
248
|
+
|
|
249
|
+
|
|
250
|
+
def test_on_wsl_a_pairing_named_like_the_windows_computer_is_renamed_once():
|
|
251
|
+
# WSL calls itself by the Windows computer's name: Windows' and WSL's pairings were two "AlonASUS" in Code
|
|
252
|
+
f = Fake(pairings=[DESK], keyed={"c"})
|
|
253
|
+
renamed = []
|
|
254
|
+
|
|
255
|
+
async def rename(p, name):
|
|
256
|
+
renamed.append((p["computerId"], name))
|
|
257
|
+
f._pairings = [{**x, "name": name} for x in f._pairings]
|
|
258
|
+
return True
|
|
259
|
+
|
|
260
|
+
run(wizard.run_setup(f.deps(wsl=True, bare_host=lambda: "desk", rename=rename)))
|
|
261
|
+
assert renamed == [("c", "desk (WSL)")] and any('Renamed this computer "desk (WSL)"' in line for line in f.said)
|
|
262
|
+
assert any('✓ Paired: "desk (WSL)"' in line for line in f.said)
|
|
263
|
+
again = Fake(pairings=[{**DESK, "name": "desk (WSL)"}], keyed={"c"})
|
|
264
|
+
run(wizard.run_setup(again.deps(wsl=True, bare_host=lambda: "desk", rename=rename)))
|
|
265
|
+
assert len(renamed) == 1, "a renamed one is left alone"
|
|
266
|
+
off = Fake(pairings=[DESK], keyed={"c"})
|
|
267
|
+
run(wizard.run_setup(off.deps(wsl=False, bare_host=lambda: "desk", rename=rename)))
|
|
268
|
+
assert len(renamed) == 1, "Windows keeps the computer's own name"
|
|
@@ -127,7 +127,7 @@ def step(tools: Tools, answers: list[str]) -> tuple[str, list[str]]:
|
|
|
127
127
|
def test_setup_asks_once_installs_mechanically_and_checks_again():
|
|
128
128
|
t = Tools(["ffmpeg", "poppler", "tesseract", "whisper"], {"os": "win32", "pm": "winget"})
|
|
129
129
|
r, said = step(t, [""])
|
|
130
|
-
assert r == "installed" and t.refreshed ==
|
|
130
|
+
assert r == "installed" and t.refreshed == 2, "once before the first look, once after the installs"
|
|
131
131
|
assert [s["kind"] for s in t.plans[0]] == ["cmd", "release", "cmd"], "Whisper is not asked for on Windows: it comes by itself"
|
|
132
132
|
assert any("Missing here: ffmpeg, Poppler, Tesseract." in s for s in said) and any("42 MB download" in s for s in said)
|
|
133
133
|
|
|
@@ -203,3 +203,24 @@ def test_no_module_uses_a_name_it_never_defined():
|
|
|
203
203
|
for f in sorted(Path(witbitz_code.__file__).parent.glob("*.py")):
|
|
204
204
|
pyflakes.api.check(f.read_text(encoding="utf-8"), str(f), rep)
|
|
205
205
|
assert rep.found == []
|
|
206
|
+
|
|
207
|
+
|
|
208
|
+
def test_tools_an_earlier_setup_installed_are_seen_before_anything_is_asked():
|
|
209
|
+
# the owner's laptop, 2026-10-09: this window's PATH predated the installs, so Pandoc and ImageMagick were "missing"
|
|
210
|
+
# and winget was asked again; the registry's PATH (refresh_path) had them all along
|
|
211
|
+
t = Tools(["pandoc", "imagemagick", "jq"], {"os": "win32", "pm": "winget"})
|
|
212
|
+
said: list[str] = []
|
|
213
|
+
d = wizard.Deps(say=said.append, ask=lambda q: "", secret=lambda q: "", probe_tools=t.probe, run_tools=t.run,
|
|
214
|
+
refresh_path=lambda: t.missing.difference_update({"pandoc", "imagemagick"}))
|
|
215
|
+
assert asyncio.run(wizard._tools_step(d)) == "installed"
|
|
216
|
+
assert [i for st in t.plans[0] for i in (st.get("ids") or [st.get("id")])] == ["jq"]
|
|
217
|
+
|
|
218
|
+
|
|
219
|
+
def test_wingets_already_installed_is_done_not_a_failure():
|
|
220
|
+
said: list[str] = []
|
|
221
|
+
codes = {"Pandoc": 2316632107, "ImageMagick": -1978335189, "qpdf": 1} # 0x8A15002B unsigned, then signed; a real failure
|
|
222
|
+
steps = [{"kind": "cmd", "ids": [i], "argv": ["winget", "install", "--id", n]} for i, n in (("pandoc", "Pandoc"), ("imagemagick", "ImageMagick"), ("qpdf", "qpdf"))]
|
|
223
|
+
out = ts.run_plan(steps, say=said.append, run=lambda argv: codes[argv[-1]])
|
|
224
|
+
assert out["done"] == ["pandoc", "imagemagick"] and out["failed"] == ["qpdf"]
|
|
225
|
+
assert " ✓ Pandoc: already installed" in said and " ✓ ImageMagick: already installed" in said
|
|
226
|
+
assert ts.run_plan([{"kind": "cmd", "ids": ["jq"], "argv": ["brew", "install", "jq"]}], say=said.append, run=lambda a: 2316632107)["failed"] == ["jq"], "only winget's code means that"
|
|
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.3.7 → witbitz_code-1.3.8}/src/witbitz_code/opencode_plugins/witbitz-media-server.mjs
RENAMED
|
File without changes
|
{witbitz_code-1.3.7 → witbitz_code-1.3.8}/src/witbitz_code/opencode_plugins/witbitz-notes.js
RENAMED
|
File without changes
|
{witbitz_code-1.3.7 → witbitz_code-1.3.8}/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
|