witbitz-code 1.6.0__tar.gz → 1.6.2__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- {witbitz_code-1.6.0/src/witbitz_code.egg-info → witbitz_code-1.6.2}/PKG-INFO +1 -1
- {witbitz_code-1.6.0 → witbitz_code-1.6.2}/pyproject.toml +2 -2
- {witbitz_code-1.6.0 → witbitz_code-1.6.2}/src/witbitz_code/__init__.py +1 -1
- witbitz_code-1.6.2/src/witbitz_code/icons/witbitz-code-wsl.ico +0 -0
- witbitz_code-1.6.2/src/witbitz_code/icons/witbitz-code.ico +0 -0
- {witbitz_code-1.6.0 → witbitz_code-1.6.2}/src/witbitz_code/uninstall.py +72 -5
- {witbitz_code-1.6.0 → witbitz_code-1.6.2}/src/witbitz_code/wslstart.py +83 -12
- {witbitz_code-1.6.0 → witbitz_code-1.6.2/src/witbitz_code.egg-info}/PKG-INFO +1 -1
- {witbitz_code-1.6.0 → witbitz_code-1.6.2}/src/witbitz_code.egg-info/SOURCES.txt +2 -0
- {witbitz_code-1.6.0 → witbitz_code-1.6.2}/tests/test_uninstall.py +37 -1
- {witbitz_code-1.6.0 → witbitz_code-1.6.2}/tests/test_wslstart.py +39 -4
- {witbitz_code-1.6.0 → witbitz_code-1.6.2}/LICENSE +0 -0
- {witbitz_code-1.6.0 → witbitz_code-1.6.2}/README.md +0 -0
- {witbitz_code-1.6.0 → witbitz_code-1.6.2}/setup.cfg +0 -0
- {witbitz_code-1.6.0 → witbitz_code-1.6.2}/src/witbitz_code/__main__.py +0 -0
- {witbitz_code-1.6.0 → witbitz_code-1.6.2}/src/witbitz_code/_js.py +0 -0
- {witbitz_code-1.6.0 → witbitz_code-1.6.2}/src/witbitz_code/account.py +0 -0
- {witbitz_code-1.6.0 → witbitz_code-1.6.2}/src/witbitz_code/asks.py +0 -0
- {witbitz_code-1.6.0 → witbitz_code-1.6.2}/src/witbitz_code/attachments.py +0 -0
- {witbitz_code-1.6.0 → witbitz_code-1.6.2}/src/witbitz_code/auto.py +0 -0
- {witbitz_code-1.6.0 → witbitz_code-1.6.2}/src/witbitz_code/auto_runner.py +0 -0
- {witbitz_code-1.6.0 → witbitz_code-1.6.2}/src/witbitz_code/cli.py +0 -0
- {witbitz_code-1.6.0 → witbitz_code-1.6.2}/src/witbitz_code/codekeys.py +0 -0
- {witbitz_code-1.6.0 → witbitz_code-1.6.2}/src/witbitz_code/codetools.py +0 -0
- {witbitz_code-1.6.0 → witbitz_code-1.6.2}/src/witbitz_code/compress.py +0 -0
- {witbitz_code-1.6.0 → witbitz_code-1.6.2}/src/witbitz_code/computers.py +0 -0
- {witbitz_code-1.6.0 → witbitz_code-1.6.2}/src/witbitz_code/confidential.py +0 -0
- {witbitz_code-1.6.0 → witbitz_code-1.6.2}/src/witbitz_code/confidential_models.json +0 -0
- {witbitz_code-1.6.0 → witbitz_code-1.6.2}/src/witbitz_code/connector.py +0 -0
- {witbitz_code-1.6.0 → witbitz_code-1.6.2}/src/witbitz_code/devicelink.py +0 -0
- {witbitz_code-1.6.0 → witbitz_code-1.6.2}/src/witbitz_code/dirs.py +0 -0
- {witbitz_code-1.6.0 → witbitz_code-1.6.2}/src/witbitz_code/folders.py +0 -0
- {witbitz_code-1.6.0 → witbitz_code-1.6.2}/src/witbitz_code/gateway.py +0 -0
- {witbitz_code-1.6.0 → witbitz_code-1.6.2}/src/witbitz_code/keys.py +0 -0
- {witbitz_code-1.6.0 → witbitz_code-1.6.2}/src/witbitz_code/link.py +0 -0
- {witbitz_code-1.6.0 → witbitz_code-1.6.2}/src/witbitz_code/mkdir.py +0 -0
- {witbitz_code-1.6.0 → witbitz_code-1.6.2}/src/witbitz_code/models.py +0 -0
- {witbitz_code-1.6.0 → witbitz_code-1.6.2}/src/witbitz_code/net.py +0 -0
- {witbitz_code-1.6.0 → witbitz_code-1.6.2}/src/witbitz_code/offered_models.json +0 -0
- {witbitz_code-1.6.0 → witbitz_code-1.6.2}/src/witbitz_code/opencode_plugins/notes-init.md +0 -0
- {witbitz_code-1.6.0 → witbitz_code-1.6.2}/src/witbitz_code/opencode_plugins/witbitz-media-server.mjs +0 -0
- {witbitz_code-1.6.0 → witbitz_code-1.6.2}/src/witbitz_code/opencode_plugins/witbitz-notes.js +0 -0
- {witbitz_code-1.6.0 → witbitz_code-1.6.2}/src/witbitz_code/opencode_plugins/witbitz-progress.js +0 -0
- {witbitz_code-1.6.0 → witbitz_code-1.6.2}/src/witbitz_code/outputs.py +0 -0
- {witbitz_code-1.6.0 → witbitz_code-1.6.2}/src/witbitz_code/pair.py +0 -0
- {witbitz_code-1.6.0 → witbitz_code-1.6.2}/src/witbitz_code/pairings.py +0 -0
- {witbitz_code-1.6.0 → witbitz_code-1.6.2}/src/witbitz_code/payers.py +0 -0
- {witbitz_code-1.6.0 → witbitz_code-1.6.2}/src/witbitz_code/plugins.py +0 -0
- {witbitz_code-1.6.0 → witbitz_code-1.6.2}/src/witbitz_code/policy.py +0 -0
- {witbitz_code-1.6.0 → witbitz_code-1.6.2}/src/witbitz_code/ports.py +0 -0
- {witbitz_code-1.6.0 → witbitz_code-1.6.2}/src/witbitz_code/qr.py +0 -0
- {witbitz_code-1.6.0 → witbitz_code-1.6.2}/src/witbitz_code/receipt.py +0 -0
- {witbitz_code-1.6.0 → witbitz_code-1.6.2}/src/witbitz_code/relay.py +0 -0
- {witbitz_code-1.6.0 → witbitz_code-1.6.2}/src/witbitz_code/seen.py +0 -0
- {witbitz_code-1.6.0 → witbitz_code-1.6.2}/src/witbitz_code/tinfoil_read.py +0 -0
- {witbitz_code-1.6.0 → witbitz_code-1.6.2}/src/witbitz_code/tools_catalog.json +0 -0
- {witbitz_code-1.6.0 → witbitz_code-1.6.2}/src/witbitz_code/tools_probe.py +0 -0
- {witbitz_code-1.6.0 → witbitz_code-1.6.2}/src/witbitz_code/toolsetup.py +0 -0
- {witbitz_code-1.6.0 → witbitz_code-1.6.2}/src/witbitz_code/ui.py +0 -0
- {witbitz_code-1.6.0 → witbitz_code-1.6.2}/src/witbitz_code/ui_static/app.css +0 -0
- {witbitz_code-1.6.0 → witbitz_code-1.6.2}/src/witbitz_code/ui_static/app.js +0 -0
- {witbitz_code-1.6.0 → witbitz_code-1.6.2}/src/witbitz_code/ui_static/index.html +0 -0
- {witbitz_code-1.6.0 → witbitz_code-1.6.2}/src/witbitz_code/uitools.py +0 -0
- {witbitz_code-1.6.0 → witbitz_code-1.6.2}/src/witbitz_code/winagent.py +0 -0
- {witbitz_code-1.6.0 → witbitz_code-1.6.2}/src/witbitz_code/winbg.py +0 -0
- {witbitz_code-1.6.0 → witbitz_code-1.6.2}/src/witbitz_code/winconsole.py +0 -0
- {witbitz_code-1.6.0 → witbitz_code-1.6.2}/src/witbitz_code/wizard.py +0 -0
- {witbitz_code-1.6.0 → witbitz_code-1.6.2}/src/witbitz_code.egg-info/dependency_links.txt +0 -0
- {witbitz_code-1.6.0 → witbitz_code-1.6.2}/src/witbitz_code.egg-info/entry_points.txt +0 -0
- {witbitz_code-1.6.0 → witbitz_code-1.6.2}/src/witbitz_code.egg-info/requires.txt +0 -0
- {witbitz_code-1.6.0 → witbitz_code-1.6.2}/src/witbitz_code.egg-info/top_level.txt +0 -0
- {witbitz_code-1.6.0 → witbitz_code-1.6.2}/tests/test_asks.py +0 -0
- {witbitz_code-1.6.0 → witbitz_code-1.6.2}/tests/test_attachments.py +0 -0
- {witbitz_code-1.6.0 → witbitz_code-1.6.2}/tests/test_auto.py +0 -0
- {witbitz_code-1.6.0 → witbitz_code-1.6.2}/tests/test_cli.py +0 -0
- {witbitz_code-1.6.0 → witbitz_code-1.6.2}/tests/test_codekeys.py +0 -0
- {witbitz_code-1.6.0 → witbitz_code-1.6.2}/tests/test_codetools.py +0 -0
- {witbitz_code-1.6.0 → witbitz_code-1.6.2}/tests/test_confidential.py +0 -0
- {witbitz_code-1.6.0 → witbitz_code-1.6.2}/tests/test_connector_e2e.py +0 -0
- {witbitz_code-1.6.0 → witbitz_code-1.6.2}/tests/test_devicelink.py +0 -0
- {witbitz_code-1.6.0 → witbitz_code-1.6.2}/tests/test_dirs.py +0 -0
- {witbitz_code-1.6.0 → witbitz_code-1.6.2}/tests/test_files_and_compress.py +0 -0
- {witbitz_code-1.6.0 → witbitz_code-1.6.2}/tests/test_folders.py +0 -0
- {witbitz_code-1.6.0 → witbitz_code-1.6.2}/tests/test_outputs.py +0 -0
- {witbitz_code-1.6.0 → witbitz_code-1.6.2}/tests/test_pair_flows.py +0 -0
- {witbitz_code-1.6.0 → witbitz_code-1.6.2}/tests/test_plugins.py +0 -0
- {witbitz_code-1.6.0 → witbitz_code-1.6.2}/tests/test_policy.py +0 -0
- {witbitz_code-1.6.0 → witbitz_code-1.6.2}/tests/test_ports.py +0 -0
- {witbitz_code-1.6.0 → witbitz_code-1.6.2}/tests/test_ports_win.py +0 -0
- {witbitz_code-1.6.0 → witbitz_code-1.6.2}/tests/test_registry.py +0 -0
- {witbitz_code-1.6.0 → witbitz_code-1.6.2}/tests/test_relay_codec.py +0 -0
- {witbitz_code-1.6.0 → witbitz_code-1.6.2}/tests/test_setup.py +0 -0
- {witbitz_code-1.6.0 → witbitz_code-1.6.2}/tests/test_tools_probe.py +0 -0
- {witbitz_code-1.6.0 → witbitz_code-1.6.2}/tests/test_toolsetup.py +0 -0
- {witbitz_code-1.6.0 → witbitz_code-1.6.2}/tests/test_ui.py +0 -0
- {witbitz_code-1.6.0 → witbitz_code-1.6.2}/tests/test_uitools.py +0 -0
- {witbitz_code-1.6.0 → witbitz_code-1.6.2}/tests/test_winagent.py +0 -0
- {witbitz_code-1.6.0 → witbitz_code-1.6.2}/tests/test_winbg.py +0 -0
- {witbitz_code-1.6.0 → witbitz_code-1.6.2}/tests/test_winconsole.py +0 -0
- {witbitz_code-1.6.0 → witbitz_code-1.6.2}/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.6.
|
|
7
|
+
version = "1.6.2"
|
|
8
8
|
description = "Reach OpenCode on this computer from the Witbitz Spaces Code section, through an end-to-end sealed relay."
|
|
9
9
|
readme = "README.md"
|
|
10
10
|
requires-python = ">=3.10"
|
|
@@ -43,7 +43,7 @@ where = ["src"]
|
|
|
43
43
|
|
|
44
44
|
# Our OpenCode plugins, installed by `serve` (plugins.py) — copies of tools/opencode-plugins, kept current by embed.mjs
|
|
45
45
|
[tool.setuptools.package-data]
|
|
46
|
-
witbitz_code = ["opencode_plugins/*", "confidential_models.json", "offered_models.json", "tools_catalog.json", "ui_static/*"]
|
|
46
|
+
witbitz_code = ["opencode_plugins/*", "confidential_models.json", "offered_models.json", "tools_catalog.json", "ui_static/*", "icons/*"]
|
|
47
47
|
|
|
48
48
|
[tool.pytest.ini_options]
|
|
49
49
|
testpaths = ["tests"]
|
|
Binary file
|
|
Binary file
|
|
@@ -23,7 +23,7 @@ from dataclasses import dataclass, field
|
|
|
23
23
|
from pathlib import Path
|
|
24
24
|
from typing import Any
|
|
25
25
|
|
|
26
|
-
from . import keys, plugins
|
|
26
|
+
from . import keys, plugins, ports
|
|
27
27
|
from . import pairings as pp
|
|
28
28
|
from .wizard import find_opencode
|
|
29
29
|
|
|
@@ -138,6 +138,8 @@ def opencode_holders(data_dir: str) -> list[dict]:
|
|
|
138
138
|
cmd = ""
|
|
139
139
|
hits.append({"pid": int(p), "cmd": cmd})
|
|
140
140
|
return hits
|
|
141
|
+
if sys.platform == "win32": # no /proc, no lsof: every opencode.exe of this user's (the owner, 2026-10-09: the background
|
|
142
|
+
return win_opencode_processes() # copy's OpenCode held opencode.db and the delete died "WinError 32")
|
|
141
143
|
files = [os.path.join(data_dir, f) for f in ("opencode.db", "opencode.db-wal", "opencode.db-shm") if os.path.exists(os.path.join(data_dir, f))]
|
|
142
144
|
if not files or not shutil.which("lsof"):
|
|
143
145
|
return hits
|
|
@@ -150,6 +152,55 @@ def opencode_holders(data_dir: str) -> list[dict]:
|
|
|
150
152
|
return hits
|
|
151
153
|
|
|
152
154
|
|
|
155
|
+
def win_opencode_processes(*, run: Callable[[list[str]], str] | None = None, user: str | None = None) -> list[dict]:
|
|
156
|
+
"""Windows: this user's opencode.exe processes (tasklist, one CSV row each)."""
|
|
157
|
+
run = run or ports._run_text
|
|
158
|
+
user = os.environ.get("USERNAME", "") if user is None else user
|
|
159
|
+
hits = []
|
|
160
|
+
for line in run(["tasklist", "/V", "/FO", "CSV", "/NH", "/FI", "IMAGENAME eq opencode.exe"]).splitlines():
|
|
161
|
+
row = ports.parse_tasklist_row(line)
|
|
162
|
+
if row and (not user or row["user"].rsplit("\\", 1)[-1].lower() == user.lower()):
|
|
163
|
+
hits.append({"pid": row["pid"], "cmd": row["image"]})
|
|
164
|
+
return hits
|
|
165
|
+
|
|
166
|
+
|
|
167
|
+
def running_witbitz(*, lock: Path | None = None, pairings: Callable[[], list] | None = None,
|
|
168
|
+
owners: Callable[[int], list] | None = None, alive: Callable[[int], bool] | None = None) -> list[dict]:
|
|
169
|
+
"""What runs witbitz-code here now: the page's manager (its ui.json names it — in the background it has no window to
|
|
170
|
+
close) and whatever holds each paired OpenCode port or its model proxy's (port + 100): a serve, its OpenCode."""
|
|
171
|
+
lock = lock or pp.pairings_path().parent / "ui.json"
|
|
172
|
+
pairings = pairings or (lambda: pp.load_pairings(log=lambda _m: None))
|
|
173
|
+
owners = owners or ports.port_owners
|
|
174
|
+
alive = alive or _alive
|
|
175
|
+
found: list[dict] = []
|
|
176
|
+
try:
|
|
177
|
+
pid = json.loads(lock.read_text(encoding="utf-8")).get("pid")
|
|
178
|
+
if isinstance(pid, int) and pid != os.getpid() and alive(pid):
|
|
179
|
+
found.append({"pid": pid, "cmd": "the Witbitz Code page"})
|
|
180
|
+
except (OSError, ValueError, AttributeError):
|
|
181
|
+
pass
|
|
182
|
+
for p in pairings():
|
|
183
|
+
tail = str(p.get("opencodeUrl") or "").rsplit(":", 1)[-1].rstrip("/")
|
|
184
|
+
port = int(tail) if tail.isdigit() else 0
|
|
185
|
+
for q in ([port, port + 100] if port else []):
|
|
186
|
+
for o in owners(q):
|
|
187
|
+
if o["pid"] != os.getpid() and all(o["pid"] != x["pid"] for x in found):
|
|
188
|
+
found.append({"pid": int(o["pid"]), "cmd": str(o.get("cmd") or "")[:70]})
|
|
189
|
+
return found
|
|
190
|
+
|
|
191
|
+
|
|
192
|
+
def _alive(pid: int) -> bool:
|
|
193
|
+
if sys.platform == "win32":
|
|
194
|
+
return _win_alive(pid)
|
|
195
|
+
try:
|
|
196
|
+
os.kill(pid, 0)
|
|
197
|
+
return True
|
|
198
|
+
except ProcessLookupError:
|
|
199
|
+
return False
|
|
200
|
+
except PermissionError:
|
|
201
|
+
return False # another user's: not ours
|
|
202
|
+
|
|
203
|
+
|
|
153
204
|
def _win_alive(pid: int) -> bool:
|
|
154
205
|
"""Windows: is `pid` still running? (OpenProcess + GetExitCodeProcess; access denied = another user's, still running.)"""
|
|
155
206
|
import ctypes # noqa: PLC0415 — Windows only
|
|
@@ -287,6 +338,7 @@ class Deps:
|
|
|
287
338
|
sessions: Callable[[], dict] = None # type: ignore[assignment]
|
|
288
339
|
delete_sessions: Callable[[], None] = None # type: ignore[assignment]
|
|
289
340
|
opencode_processes: Callable[[], list] = lambda: opencode_holders(str(keys.auth_path().parent))
|
|
341
|
+
running: Callable[[], list] = running_witbitz
|
|
290
342
|
stop_process: Callable[[int], bool] = stop_process
|
|
291
343
|
opencode: Callable[[], dict] = opencode_here
|
|
292
344
|
remove_opencode_program: Callable[[dict], dict] = remove_opencode_program
|
|
@@ -332,6 +384,12 @@ async def run_uninstall(d: Deps) -> dict:
|
|
|
332
384
|
how = {"installer": "from OpenCode's installer, in ~/.opencode", "npm": "with npm", "brew": "with Homebrew"}.get(oc["kind"])
|
|
333
385
|
remove_oc = ask(d.remove_opencode, f"Also remove OpenCode itself (installed {how})? [y/N] ") if oc["kind"] not in ("none", "other") else False
|
|
334
386
|
|
|
387
|
+
# The running witbitz-code first — the page's manager in the background, a serve, their OpenCode: it holds the files
|
|
388
|
+
# about to go (Windows refuses to delete an open file) and would keep answering the phone. Continue already said yes.
|
|
389
|
+
for p in d.running():
|
|
390
|
+
d.say(f"✓ Stopped the running witbitz-code: {p['cmd'] or 'a program'} (process {p['pid']})" if d.stop_process(p["pid"])
|
|
391
|
+
else f"✖ Could not stop {p['cmd'] or 'a program'} (process {p['pid']}) — close it, then run uninstall again")
|
|
392
|
+
|
|
335
393
|
# Sessions: an OpenCode that has its database open would write it back — never delete under it.
|
|
336
394
|
while remove_sessions:
|
|
337
395
|
procs = d.opencode_processes()
|
|
@@ -376,8 +434,11 @@ async def run_uninstall(d: Deps) -> dict:
|
|
|
376
434
|
d.remove_path(notes["root"])
|
|
377
435
|
d.say(f"✓ Deleted {notes['root']}")
|
|
378
436
|
if remove_sessions:
|
|
379
|
-
d.delete_sessions()
|
|
380
|
-
|
|
437
|
+
stuck = d.delete_sessions() or []
|
|
438
|
+
if stuck:
|
|
439
|
+
d.say(f"✖ Could not delete {', '.join(stuck[:4])} in {sessions['dir']} — another program still has it open; close OpenCode and delete it yourself")
|
|
440
|
+
else:
|
|
441
|
+
d.say(f"✓ Deleted the OpenCode sessions in {sessions['dir']} (provider logins kept)")
|
|
381
442
|
oc_gone = False
|
|
382
443
|
if remove_oc:
|
|
383
444
|
try:
|
|
@@ -454,12 +515,18 @@ def sessions_info() -> dict:
|
|
|
454
515
|
return {"dir": str(d), "exists": exists}
|
|
455
516
|
|
|
456
517
|
|
|
457
|
-
def delete_sessions() ->
|
|
518
|
+
def delete_sessions() -> list[str]:
|
|
519
|
+
"""→ the names that could not be deleted (Windows: a file another program still has open), never a crash."""
|
|
458
520
|
d = keys.auth_path().parent
|
|
521
|
+
left = []
|
|
459
522
|
for f in d.iterdir():
|
|
460
523
|
if f.name == "auth.json":
|
|
461
524
|
continue
|
|
462
|
-
|
|
525
|
+
try:
|
|
526
|
+
shutil.rmtree(f, ignore_errors=True) if f.is_dir() and not f.is_symlink() else f.unlink(missing_ok=True)
|
|
527
|
+
except OSError:
|
|
528
|
+
left.append(f.name)
|
|
529
|
+
return left
|
|
463
530
|
|
|
464
531
|
|
|
465
532
|
def saved_keys() -> list[str]:
|
|
@@ -19,6 +19,7 @@ from __future__ import annotations
|
|
|
19
19
|
import base64
|
|
20
20
|
import getpass
|
|
21
21
|
import os
|
|
22
|
+
import re
|
|
22
23
|
import shutil
|
|
23
24
|
import subprocess
|
|
24
25
|
import sys
|
|
@@ -50,6 +51,31 @@ def wsl_args(distro: str, user: str, cmd: list[str], verb: list[str] | None = No
|
|
|
50
51
|
return _join(parts)
|
|
51
52
|
|
|
52
53
|
|
|
54
|
+
_SAFE_DIR = re.compile(r"[A-Za-z0-9_./+:@-]+")
|
|
55
|
+
|
|
56
|
+
|
|
57
|
+
def carried_path(path: str | None = None) -> str:
|
|
58
|
+
"""This terminal's PATH for a start that runs the command directly — wsl.exe's `--` reads no ~/.bashrc, so a hidden copy
|
|
59
|
+
had only the system folders: no OpenCode (nvm's, ~/.opencode/bin), no witbitz-code (the owner, 2026-10-09: "OpenCode is
|
|
60
|
+
not installed" and "Start when I log in — not available"). Only plain folder names cross the Windows command line."""
|
|
61
|
+
parts = (os.environ.get("PATH", "") if path is None else path).split(":")
|
|
62
|
+
return ":".join(dict.fromkeys(p for p in parts if p and _SAFE_DIR.fullmatch(p) and ":" not in p))
|
|
63
|
+
|
|
64
|
+
|
|
65
|
+
def wsl_command(exe: str, path: str | None = None) -> list[str]:
|
|
66
|
+
"""[env, PATH=…, exe] — the command a hidden start runs, with this terminal's PATH."""
|
|
67
|
+
return ["env", "PATH=" + carried_path(path), exe]
|
|
68
|
+
|
|
69
|
+
|
|
70
|
+
def this_program() -> str:
|
|
71
|
+
"""witbitz-code on PATH, else the program running now (a hidden copy started before 1.6.2 had no ~/.local/bin)."""
|
|
72
|
+
exe = shutil.which("witbitz-code")
|
|
73
|
+
if exe:
|
|
74
|
+
return exe
|
|
75
|
+
argv0 = os.path.abspath(sys.argv[0]) if sys.argv and sys.argv[0] else ""
|
|
76
|
+
return argv0 if argv0 and os.path.basename(argv0) == "witbitz-code" and os.path.exists(argv0) else ""
|
|
77
|
+
|
|
78
|
+
|
|
53
79
|
def headless_args(distro: str, user: str, cmd: list[str], verb: list[str]) -> str:
|
|
54
80
|
"""conhost.exe's arguments: no window, then wsl.exe with wsl_args."""
|
|
55
81
|
return "--headless wsl.exe " + wsl_args(distro, user, cmd, verb)
|
|
@@ -59,11 +85,52 @@ def _q(s: str) -> str: # a PowerShell single-quoted string
|
|
|
59
85
|
return "'" + s.replace("'", "''") + "'"
|
|
60
86
|
|
|
61
87
|
|
|
62
|
-
def enable_script(name: str, args: str, folder: str = STARTUP, target: str = WSL_EXE) -> str:
|
|
63
|
-
"""`target` is a PowerShell expression (wsl.exe), or a path made one with _q (native Windows: the launcher).
|
|
88
|
+
def enable_script(name: str, args: str, folder: str = STARTUP, target: str = WSL_EXE, icon: str = "") -> str:
|
|
89
|
+
"""`target` is a PowerShell expression (wsl.exe), or a path made one with _q (native Windows: the launcher). `icon`: a
|
|
90
|
+
PowerShell expression for an .ico (else the target's own icon — a console window's, for conhost)."""
|
|
64
91
|
return (f"$p = Join-Path ({folder}) {_q(name)}; $s = (New-Object -ComObject WScript.Shell).CreateShortcut($p); "
|
|
65
92
|
f"$s.TargetPath = {target}; $s.Arguments = {_q(args)}; $s.WorkingDirectory = $env:USERPROFILE; $s.WindowStyle = 7; "
|
|
66
|
-
"
|
|
93
|
+
+ (f"if (Test-Path ({icon})) {{ $s.IconLocation = ({icon}) + ',0' }}; " if icon else "")
|
|
94
|
+
+ "$s.Description = 'Witbitz Code'; $s.Save(); 'ok'")
|
|
95
|
+
|
|
96
|
+
|
|
97
|
+
# The shortcuts' icons (icons/ in this package; the owner, 2026-10-09: the WSL entry "has a terminal icon"). WSL's is
|
|
98
|
+
# copied to a Windows folder of its own — not under %LOCALAPPDATA%\Witbitz, which the Windows copy's uninstall deletes.
|
|
99
|
+
ICON_DIR = "(Join-Path $env:LOCALAPPDATA 'Witbitz-WSL')"
|
|
100
|
+
WSL_ICON = f"(Join-Path {ICON_DIR} 'witbitz-code-wsl.ico')"
|
|
101
|
+
|
|
102
|
+
|
|
103
|
+
def icon_file(name: str) -> str:
|
|
104
|
+
return os.path.join(os.path.dirname(os.path.abspath(__file__)), "icons", name)
|
|
105
|
+
|
|
106
|
+
|
|
107
|
+
def linux_path(windows: str, run: Callable = subprocess.run) -> str:
|
|
108
|
+
"""wslpath -u — '' when it cannot say."""
|
|
109
|
+
try:
|
|
110
|
+
r = run(["wslpath", "-u", windows], capture_output=True, text=True, timeout=10)
|
|
111
|
+
return (r.stdout or "").strip() if r.returncode == 0 else ""
|
|
112
|
+
except (OSError, subprocess.SubprocessError):
|
|
113
|
+
return ""
|
|
114
|
+
|
|
115
|
+
|
|
116
|
+
def copy_icon(*, ps: Callable[[str], tuple[int, str]] | None = None, to_linux: Callable[[str], str] = linux_path,
|
|
117
|
+
src: str | None = None) -> bool:
|
|
118
|
+
"""Put WSL's icon in its Windows folder, written from THIS side through /mnt/c (Windows could not read the
|
|
119
|
+
\\\\wsl.localhost path of the package on the owner's laptop: "does not exist"). → whether it is there now."""
|
|
120
|
+
src = src or icon_file("witbitz-code-wsl.ico")
|
|
121
|
+
code, out = (ps or run_ps)("$env:LOCALAPPDATA")
|
|
122
|
+
local = out.splitlines()[-1].strip() if code == 0 and out else ""
|
|
123
|
+
if not re.fullmatch(r"[A-Za-z]:\\[^\x00-\x1f]+", local): # only a real Windows folder ("C:\Users\…\Local")
|
|
124
|
+
return False
|
|
125
|
+
dest_dir = to_linux(local)
|
|
126
|
+
if not dest_dir or not os.path.isabs(dest_dir) or not os.path.exists(src):
|
|
127
|
+
return False
|
|
128
|
+
try:
|
|
129
|
+
os.makedirs(os.path.join(dest_dir, "Witbitz-WSL"), exist_ok=True)
|
|
130
|
+
shutil.copyfile(src, os.path.join(dest_dir, "Witbitz-WSL", "witbitz-code-wsl.ico"))
|
|
131
|
+
return True
|
|
132
|
+
except OSError:
|
|
133
|
+
return False
|
|
67
134
|
|
|
68
135
|
|
|
69
136
|
def state_script(name: str, folder: str = STARTUP) -> str:
|
|
@@ -101,11 +168,12 @@ def startup_here() -> dict | None:
|
|
|
101
168
|
"""What the WSL shortcut would be — None off WSL, with no Windows PowerShell to write it, or no witbitz-code on PATH."""
|
|
102
169
|
if not ports.on_wsl() or not (os.path.exists(POWERSHELL) or shutil.which("powershell.exe")):
|
|
103
170
|
return None
|
|
104
|
-
exe =
|
|
171
|
+
exe = this_program()
|
|
105
172
|
if not exe:
|
|
106
173
|
return None
|
|
107
174
|
distro = os.environ.get("WSL_DISTRO_NAME", "")
|
|
108
|
-
return {"name": shortcut_name(distro), "args": headless_args(distro, getpass.getuser(),
|
|
175
|
+
return {"name": shortcut_name(distro), "args": headless_args(distro, getpass.getuser(), wsl_command(exe), UI_QUIET), "target": CONHOST,
|
|
176
|
+
"icon": WSL_ICON}
|
|
109
177
|
|
|
110
178
|
|
|
111
179
|
def menu_name(distro: str) -> str:
|
|
@@ -119,7 +187,7 @@ def launch_script(args: str) -> str:
|
|
|
119
187
|
|
|
120
188
|
|
|
121
189
|
def launch_background(*, ps: Callable[[str], tuple[int, str]] = run_ps, which: Callable[[str], str | None] = shutil.which,
|
|
122
|
-
on_wsl: Callable[[], bool] = ports.on_wsl) -> tuple[bool, str]:
|
|
190
|
+
on_wsl: Callable[[], bool] = ports.on_wsl, copy: Callable[..., bool] | None = None) -> tuple[bool, str]:
|
|
123
191
|
"""WSL: start the page's manager with no window, held by a hidden wsl.exe on the Windows side, and let it open the
|
|
124
192
|
page in the Windows browser — setup's hand-off (this terminal may then close). → (ok, why)."""
|
|
125
193
|
if not on_wsl():
|
|
@@ -127,14 +195,16 @@ def launch_background(*, ps: Callable[[str], tuple[int, str]] = run_ps, which: C
|
|
|
127
195
|
exe = which("witbitz-code")
|
|
128
196
|
if not exe:
|
|
129
197
|
return False, "witbitz-code is not on PATH"
|
|
198
|
+
copy = copy or copy_icon
|
|
130
199
|
distro = os.environ.get("WSL_DISTRO_NAME", "")
|
|
131
|
-
args = headless_args(distro, getpass.getuser(),
|
|
200
|
+
args = headless_args(distro, getpass.getuser(), wsl_command(exe), ["ui", "--hidden"])
|
|
132
201
|
# The Start-menu entry first (it opens this page again, starting it if it is not running), then the start itself.
|
|
133
|
-
|
|
202
|
+
icon = WSL_ICON if copy(ps=ps) else ""
|
|
203
|
+
menu = enable_script(menu_name(distro), args, folder=PROGRAMS, target=CONHOST, icon=icon)
|
|
134
204
|
# A login start written by 1.4.0–1.5.x opens a minimized window: an existing one is rewritten to start hidden too.
|
|
135
|
-
quiet = headless_args(distro, getpass.getuser(),
|
|
205
|
+
quiet = headless_args(distro, getpass.getuser(), wsl_command(exe), UI_QUIET)
|
|
136
206
|
login = (f"if (Test-Path (Join-Path ({STARTUP}) {_q(shortcut_name(distro))})) {{ "
|
|
137
|
-
f"{enable_script(shortcut_name(distro), quiet, target=CONHOST)} | Out-Null }}")
|
|
207
|
+
f"{enable_script(shortcut_name(distro), quiet, target=CONHOST, icon=icon)} | Out-Null }}")
|
|
138
208
|
code, out = ps(f"try {{ {menu} | Out-Null; {login} }} catch {{ }}; {launch_script(args)}")
|
|
139
209
|
ok = code == 0 and bool(out) and out.splitlines()[-1].strip() == "ok"
|
|
140
210
|
return ok, "" if ok else ("Windows PowerShell could not be started" if code == -1 else f"Windows PowerShell answered {code}")
|
|
@@ -148,7 +218,8 @@ def windows_startup_spec(env: dict | None = None, which: Callable[[str], str | N
|
|
|
148
218
|
env = os.environ if env is None else env
|
|
149
219
|
pyw = os.path.join(os.path.dirname(executable or sys.executable), "pythonw.exe")
|
|
150
220
|
if exists(pyw):
|
|
151
|
-
return {"name": "Witbitz Code.lnk", "args": _join(["-m", "witbitz_code", *WIN_QUIET]), "target": _q(pyw)
|
|
221
|
+
return {"name": "Witbitz Code.lnk", "args": _join(["-m", "witbitz_code", *WIN_QUIET]), "target": _q(pyw),
|
|
222
|
+
"icon": _q(icon_file("witbitz-code.ico"))}
|
|
152
223
|
launcher = os.path.join(env.get("LOCALAPPDATA", ""), "Witbitz", "Witbitz Code.cmd")
|
|
153
224
|
if env.get("LOCALAPPDATA") and exists(launcher):
|
|
154
225
|
return {"name": "Witbitz Code.lnk", "args": "--no-browser", "target": _q(launcher)}
|
|
@@ -171,7 +242,7 @@ def startup_state(spec: dict, *, ps: Callable[[str], tuple[int, str]] = run_ps)
|
|
|
171
242
|
|
|
172
243
|
|
|
173
244
|
def startup_enable(spec: dict, *, ps: Callable[[str], tuple[int, str]] = run_ps) -> tuple[bool, str]:
|
|
174
|
-
code, out = ps(enable_script(spec["name"], spec["args"], target=spec.get("target", WSL_EXE)))
|
|
245
|
+
code, out = ps(enable_script(spec["name"], spec["args"], target=spec.get("target", WSL_EXE), icon=spec.get("icon", "")))
|
|
175
246
|
ok = code == 0 and out.splitlines()[-1].strip() == "ok" if out else False
|
|
176
247
|
return ok, "" if ok else (f"Windows PowerShell answered {code}" if code != -1 else "Windows PowerShell could not be started")
|
|
177
248
|
|
|
@@ -56,6 +56,8 @@ src/witbitz_code.egg-info/dependency_links.txt
|
|
|
56
56
|
src/witbitz_code.egg-info/entry_points.txt
|
|
57
57
|
src/witbitz_code.egg-info/requires.txt
|
|
58
58
|
src/witbitz_code.egg-info/top_level.txt
|
|
59
|
+
src/witbitz_code/icons/witbitz-code-wsl.ico
|
|
60
|
+
src/witbitz_code/icons/witbitz-code.ico
|
|
59
61
|
src/witbitz_code/opencode_plugins/notes-init.md
|
|
60
62
|
src/witbitz_code/opencode_plugins/witbitz-media-server.mjs
|
|
61
63
|
src/witbitz_code/opencode_plugins/witbitz-notes.js
|
|
@@ -72,7 +72,7 @@ class Fake:
|
|
|
72
72
|
remove_opencode_program=lambda info: self.done.append(("opencode",)) or {"ok": True, "said": []},
|
|
73
73
|
package=lambda: {"kind": self._pkg, "hint": "python3 -m pip uninstall witbitz-code"},
|
|
74
74
|
remove_package=lambda info: self.done.append(("package",)) or {"ok": True},
|
|
75
|
-
**{"login_start": lambda: None, "menu_remove": lambda: False, **over}) # never this machine's Windows Startup folder or Start menu
|
|
75
|
+
**{"login_start": lambda: None, "menu_remove": lambda: False, "running": lambda: [], **over}) # never this machine's Windows Startup folder or Start menu
|
|
76
76
|
|
|
77
77
|
|
|
78
78
|
PAIR = {"name": "desk", "account": "a@x.com", "idx": {"room": "r"}, "computerId": "c"}
|
|
@@ -174,3 +174,39 @@ def test_on_wsl_the_windows_startup_shortcut_setup_made_goes_too():
|
|
|
174
174
|
assert gone == ["Witbitz Code (WSL UbuntuD).lnk", "menu"]
|
|
175
175
|
assert any('Removed "Witbitz Code (WSL UbuntuD)" from the Start menu' in line for line in f.said)
|
|
176
176
|
assert any('Removed the Windows Startup shortcut "Witbitz Code (WSL UbuntuD)"' in line for line in f.said)
|
|
177
|
+
|
|
178
|
+
|
|
179
|
+
def test_the_running_witbitz_code_is_stopped_before_anything_is_deleted():
|
|
180
|
+
"""The owner, 2026-10-09 (Windows): the background copy's OpenCode held opencode.db and the delete died WinError 32."""
|
|
181
|
+
f = Fake(pairs=[PAIR], sessions=True, answers=["y", "y"])
|
|
182
|
+
run(un.run_uninstall(f.deps(running=lambda: [{"pid": 8420, "cmd": "the Witbitz Code page"}, {"pid": 19228, "cmd": "opencode.exe"}])))
|
|
183
|
+
stops = [i for i, x in enumerate(f.done) if x[0] == "stop"]
|
|
184
|
+
assert [f.done[i] for i in stops] == [("stop", 8420), ("stop", 19228)]
|
|
185
|
+
assert stops[-1] < f.done.index(("sessions",)) and stops[-1] < f.done.index(("rm", "/h/.witbitz/code"))
|
|
186
|
+
assert any("Stopped the running witbitz-code: the Witbitz Code page (process 8420)" in line for line in f.said)
|
|
187
|
+
|
|
188
|
+
|
|
189
|
+
def test_a_session_file_still_open_is_said_not_a_crash():
|
|
190
|
+
f = Fake(sessions=True, answers=["y", "y"])
|
|
191
|
+
d = f.deps()
|
|
192
|
+
d.delete_sessions = lambda: ["opencode.db"]
|
|
193
|
+
r = run(un.run_uninstall(d))
|
|
194
|
+
assert r.get("done") is not False
|
|
195
|
+
assert any("Could not delete opencode.db" in line for line in f.said)
|
|
196
|
+
|
|
197
|
+
|
|
198
|
+
def test_what_runs_witbitz_code_is_the_page_s_manager_and_the_paired_ports_owners(tmp_path):
|
|
199
|
+
lock = tmp_path / "ui.json"
|
|
200
|
+
lock.write_text('{"port": 4290, "token": "t", "pid": 8420}')
|
|
201
|
+
owners = {4097: [{"pid": 19228, "cmd": "opencode.exe"}], 4197: [{"pid": 8420, "cmd": "python.exe"}]}
|
|
202
|
+
got = un.running_witbitz(lock=lock, pairings=lambda: [{"opencodeUrl": "http://127.0.0.1:4097"}],
|
|
203
|
+
owners=lambda q: owners.get(q, []), alive=lambda pid: pid == 8420)
|
|
204
|
+
assert got == [{"pid": 8420, "cmd": "the Witbitz Code page"}, {"pid": 19228, "cmd": "opencode.exe"}]
|
|
205
|
+
lock.write_text('{"pid": 8420}')
|
|
206
|
+
assert un.running_witbitz(lock=lock, pairings=lambda: [], owners=lambda q: [], alive=lambda pid: False) == []
|
|
207
|
+
|
|
208
|
+
|
|
209
|
+
def test_windows_finds_opencode_by_name():
|
|
210
|
+
out = '"opencode.exe","19228","Console","1","95,000 K","Running","ALONASUS\\shter","0:00:03","N/A"\n' \
|
|
211
|
+
'"opencode.exe","555","Console","1","1 K","Running","OTHER\\bob","0:00:01","N/A"\n'
|
|
212
|
+
assert un.win_opencode_processes(run=lambda argv: out, user="shter") == [{"pid": 19228, "cmd": "opencode.exe"}]
|
|
@@ -47,7 +47,8 @@ def test_the_windows_login_start_runs_pythonw_in_the_background_when_it_is_there
|
|
|
47
47
|
exe = r"C:\Users\a\AppData\Local\Witbitz\tools\witbitz-code\Scripts\python.exe"
|
|
48
48
|
pyw = __import__("os").path.join(__import__("os").path.dirname(exe), "pythonw.exe")
|
|
49
49
|
spec = ws.windows_startup_spec(env={"LOCALAPPDATA": r"C:\Users\a\AppData\Local"}, exists=lambda p: p == pyw, executable=exe)
|
|
50
|
-
assert spec == {"name": "Witbitz Code.lnk", "args": "-m witbitz_code ui --no-browser", "target": "'" + pyw + "'"}
|
|
50
|
+
assert {k: spec[k] for k in ("name", "args", "target")} == {"name": "Witbitz Code.lnk", "args": "-m witbitz_code ui --no-browser", "target": "'" + pyw + "'"}
|
|
51
|
+
assert spec["icon"].endswith("witbitz-code.ico'"), "the robot, not the launcher's icon"
|
|
51
52
|
launcher_only = ws.windows_startup_spec(env={"LOCALAPPDATA": "L"}, exists=lambda p: p.endswith("Witbitz Code.cmd"), executable=exe)
|
|
52
53
|
assert launcher_only["args"] == "--no-browser" and launcher_only["target"].endswith("Witbitz Code.cmd'")
|
|
53
54
|
|
|
@@ -56,12 +57,46 @@ def test_wsl_runs_headless_held_by_a_hidden_wsl_exe():
|
|
|
56
57
|
from witbitz_code import wslstart as ws
|
|
57
58
|
seen = []
|
|
58
59
|
ok, why = ws.launch_background(ps=lambda script: seen.append(script) or (0, "ok"), which=lambda n: "/home/a/.local/bin/witbitz-code",
|
|
59
|
-
on_wsl=lambda: True)
|
|
60
|
+
on_wsl=lambda: True, copy=lambda ps: False)
|
|
60
61
|
assert ok and why == ""
|
|
61
62
|
assert "GetFolderPath('Programs')" in seen[0] and "'Witbitz Code (WSL " in seen[0], "its own Start-menu entry"
|
|
62
63
|
assert seen[0].index("Programs") < seen[0].index("Start-Process"), "the entry is written before the start"
|
|
63
64
|
assert "if (Test-Path (Join-Path ([Environment]::GetFolderPath('Startup'))" in seen[0], "an older login start is rewritten hidden"
|
|
64
|
-
assert "conhost.exe" in seen[0] and "'--headless wsl.exe " in seen[0] and "
|
|
65
|
+
assert "conhost.exe" in seen[0] and "'--headless wsl.exe " in seen[0] and " /home/a/.local/bin/witbitz-code ui --hidden'" in seen[0] and "-- env PATH=" in seen[0]
|
|
65
66
|
assert ws.launch_background(ps=lambda s: (0, "ok"), on_wsl=lambda: False) == (False, "not in WSL")
|
|
66
|
-
assert ws.launch_background(ps=lambda s: (1, ""), which=lambda n: "/w", on_wsl=lambda: True) == (False, "Windows PowerShell answered 1")
|
|
67
|
+
assert ws.launch_background(ps=lambda s: (1, ""), which=lambda n: "/w", on_wsl=lambda: True, copy=lambda ps: False) == (False, "Windows PowerShell answered 1")
|
|
67
68
|
assert ws.headless_args("UbuntuD", "u", ["/w"], ws.UI_QUIET) == "--headless wsl.exe -d UbuntuD -u u --cd ~ -- /w ui --no-browser --hidden"
|
|
69
|
+
|
|
70
|
+
|
|
71
|
+
def test_a_hidden_start_carries_this_terminal_s_path():
|
|
72
|
+
"""wsl.exe -- reads no ~/.bashrc: the hidden copy found no OpenCode (nvm's) and no witbitz-code (the owner, 2026-10-09)."""
|
|
73
|
+
from witbitz_code import wslstart as ws
|
|
74
|
+
path = "/home/u/.nvm/versions/node/v24.3.0/bin:/home/u/.local/bin:/usr/bin:/mnt/c/Program Files/Git/cmd:/mnt/c/WINDOWS/system32:/usr/bin::/x;rm"
|
|
75
|
+
assert ws.carried_path(path) == "/home/u/.nvm/versions/node/v24.3.0/bin:/home/u/.local/bin:/usr/bin:/mnt/c/WINDOWS/system32"
|
|
76
|
+
assert ws.wsl_command("/home/u/.local/bin/witbitz-code", path)[:2] == ["env", "PATH=" + ws.carried_path(path)]
|
|
77
|
+
|
|
78
|
+
|
|
79
|
+
def test_the_shortcuts_carry_witbitz_icons_and_wsl_copies_its_own_to_windows(monkeypatch):
|
|
80
|
+
"""The owner, 2026-10-09: the WSL entry "has a terminal icon" (conhost's)."""
|
|
81
|
+
import os
|
|
82
|
+
from witbitz_code import wslstart as ws
|
|
83
|
+
assert os.path.getsize(ws.icon_file("witbitz-code.ico")) > 1000 and os.path.getsize(ws.icon_file("witbitz-code-wsl.ico")) > 1000
|
|
84
|
+
s = ws.enable_script("x.lnk", "-a", icon=ws.WSL_ICON)
|
|
85
|
+
assert "if (Test-Path ((Join-Path (Join-Path $env:LOCALAPPDATA 'Witbitz-WSL') 'witbitz-code-wsl.ico')))" in s and "IconLocation" in s
|
|
86
|
+
assert "IconLocation" not in ws.enable_script("x.lnk", "-a"), "no icon asked → the script is as before (Node parity)"
|
|
87
|
+
seen = []
|
|
88
|
+
ws.launch_background(ps=lambda script: seen.append(script) or (0, "ok"), which=lambda n: "/w", on_wsl=lambda: True, copy=lambda ps: True)
|
|
89
|
+
assert "IconLocation" in seen[0] and "Witbitz-WSL" in seen[0]
|
|
90
|
+
seen.clear()
|
|
91
|
+
ws.launch_background(ps=lambda script: seen.append(script) or (0, "ok"), which=lambda n: "/w", on_wsl=lambda: True, copy=lambda ps: False)
|
|
92
|
+
assert "IconLocation" not in seen[0], "no icon copied → the shortcut keeps its own icon"
|
|
93
|
+
|
|
94
|
+
|
|
95
|
+
def test_the_icon_is_written_into_windows_local_app_data_from_this_side(tmp_path):
|
|
96
|
+
from witbitz_code import wslstart as ws
|
|
97
|
+
win = tmp_path / "Local"
|
|
98
|
+
win.mkdir()
|
|
99
|
+
ok = ws.copy_icon(ps=lambda s: (0, "C:\\Users\\a\\AppData\\Local"), to_linux=lambda p: str(win) if p.endswith("Local") else "")
|
|
100
|
+
assert ok and (win / "Witbitz-WSL" / "witbitz-code-wsl.ico").read_bytes() == open(ws.icon_file("witbitz-code-wsl.ico"), "rb").read()
|
|
101
|
+
assert ws.copy_icon(ps=lambda s: (-1, ""), to_linux=lambda p: str(win)) is False
|
|
102
|
+
assert ws.copy_icon(ps=lambda s: (0, "ok"), to_linux=lambda p: "ok") is False, "not a Windows folder: never written"
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
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.6.0 → witbitz_code-1.6.2}/src/witbitz_code/opencode_plugins/witbitz-media-server.mjs
RENAMED
|
File without changes
|
{witbitz_code-1.6.0 → witbitz_code-1.6.2}/src/witbitz_code/opencode_plugins/witbitz-notes.js
RENAMED
|
File without changes
|
{witbitz_code-1.6.0 → witbitz_code-1.6.2}/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
|