witbitz-code 1.9.2__tar.gz → 1.9.4__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.9.2/src/witbitz_code.egg-info → witbitz_code-1.9.4}/PKG-INFO +1 -1
- {witbitz_code-1.9.2 → witbitz_code-1.9.4}/pyproject.toml +1 -1
- {witbitz_code-1.9.2 → witbitz_code-1.9.4}/src/witbitz_code/__init__.py +1 -1
- {witbitz_code-1.9.2 → witbitz_code-1.9.4}/src/witbitz_code/confidential.py +46 -1
- {witbitz_code-1.9.2 → witbitz_code-1.9.4}/src/witbitz_code/ui.py +2 -0
- {witbitz_code-1.9.2 → witbitz_code-1.9.4}/src/witbitz_code/ui_static/app.js +24 -7
- {witbitz_code-1.9.2 → witbitz_code-1.9.4}/src/witbitz_code/ui_static/index.html +1 -0
- {witbitz_code-1.9.2 → witbitz_code-1.9.4/src/witbitz_code.egg-info}/PKG-INFO +1 -1
- {witbitz_code-1.9.2 → witbitz_code-1.9.4}/tests/test_codekeys.py +2 -1
- {witbitz_code-1.9.2 → witbitz_code-1.9.4}/tests/test_confidential.py +32 -0
- {witbitz_code-1.9.2 → witbitz_code-1.9.4}/tests/test_pair_flows.py +11 -11
- {witbitz_code-1.9.2 → witbitz_code-1.9.4}/tests/test_setup.py +4 -4
- {witbitz_code-1.9.2 → witbitz_code-1.9.4}/tests/test_ui.py +64 -9
- {witbitz_code-1.9.2 → witbitz_code-1.9.4}/tests/test_uninstall.py +2 -2
- {witbitz_code-1.9.2 → witbitz_code-1.9.4}/tests/test_wslstart.py +2 -2
- {witbitz_code-1.9.2 → witbitz_code-1.9.4}/LICENSE +0 -0
- {witbitz_code-1.9.2 → witbitz_code-1.9.4}/README.md +0 -0
- {witbitz_code-1.9.2 → witbitz_code-1.9.4}/setup.cfg +0 -0
- {witbitz_code-1.9.2 → witbitz_code-1.9.4}/src/witbitz_code/__main__.py +0 -0
- {witbitz_code-1.9.2 → witbitz_code-1.9.4}/src/witbitz_code/_js.py +0 -0
- {witbitz_code-1.9.2 → witbitz_code-1.9.4}/src/witbitz_code/account.py +0 -0
- {witbitz_code-1.9.2 → witbitz_code-1.9.4}/src/witbitz_code/asks.py +0 -0
- {witbitz_code-1.9.2 → witbitz_code-1.9.4}/src/witbitz_code/attachments.py +0 -0
- {witbitz_code-1.9.2 → witbitz_code-1.9.4}/src/witbitz_code/auto.py +0 -0
- {witbitz_code-1.9.2 → witbitz_code-1.9.4}/src/witbitz_code/auto_runner.py +0 -0
- {witbitz_code-1.9.2 → witbitz_code-1.9.4}/src/witbitz_code/browse.py +0 -0
- {witbitz_code-1.9.2 → witbitz_code-1.9.4}/src/witbitz_code/browsetree.py +0 -0
- {witbitz_code-1.9.2 → witbitz_code-1.9.4}/src/witbitz_code/cli.py +0 -0
- {witbitz_code-1.9.2 → witbitz_code-1.9.4}/src/witbitz_code/codekeys.py +0 -0
- {witbitz_code-1.9.2 → witbitz_code-1.9.4}/src/witbitz_code/codetools.py +0 -0
- {witbitz_code-1.9.2 → witbitz_code-1.9.4}/src/witbitz_code/compress.py +0 -0
- {witbitz_code-1.9.2 → witbitz_code-1.9.4}/src/witbitz_code/computers.py +0 -0
- {witbitz_code-1.9.2 → witbitz_code-1.9.4}/src/witbitz_code/confidential_models.json +0 -0
- {witbitz_code-1.9.2 → witbitz_code-1.9.4}/src/witbitz_code/connector.py +0 -0
- {witbitz_code-1.9.2 → witbitz_code-1.9.4}/src/witbitz_code/devicelink.py +0 -0
- {witbitz_code-1.9.2 → witbitz_code-1.9.4}/src/witbitz_code/dirs.py +0 -0
- {witbitz_code-1.9.2 → witbitz_code-1.9.4}/src/witbitz_code/folders.py +0 -0
- {witbitz_code-1.9.2 → witbitz_code-1.9.4}/src/witbitz_code/gateway.py +0 -0
- {witbitz_code-1.9.2 → witbitz_code-1.9.4}/src/witbitz_code/icons/witbitz-code-wsl.ico +0 -0
- {witbitz_code-1.9.2 → witbitz_code-1.9.4}/src/witbitz_code/icons/witbitz-code.ico +0 -0
- {witbitz_code-1.9.2 → witbitz_code-1.9.4}/src/witbitz_code/images.py +0 -0
- {witbitz_code-1.9.2 → witbitz_code-1.9.4}/src/witbitz_code/keys.py +0 -0
- {witbitz_code-1.9.2 → witbitz_code-1.9.4}/src/witbitz_code/link.py +0 -0
- {witbitz_code-1.9.2 → witbitz_code-1.9.4}/src/witbitz_code/mkdir.py +0 -0
- {witbitz_code-1.9.2 → witbitz_code-1.9.4}/src/witbitz_code/models.py +0 -0
- {witbitz_code-1.9.2 → witbitz_code-1.9.4}/src/witbitz_code/net.py +0 -0
- {witbitz_code-1.9.2 → witbitz_code-1.9.4}/src/witbitz_code/offered_models.json +0 -0
- {witbitz_code-1.9.2 → witbitz_code-1.9.4}/src/witbitz_code/opencode_plugins/notes-init.md +0 -0
- {witbitz_code-1.9.2 → witbitz_code-1.9.4}/src/witbitz_code/opencode_plugins/witbitz-browse-cdp.mjs +0 -0
- {witbitz_code-1.9.2 → witbitz_code-1.9.4}/src/witbitz_code/opencode_plugins/witbitz-browse-local.mjs +0 -0
- {witbitz_code-1.9.2 → witbitz_code-1.9.4}/src/witbitz_code/opencode_plugins/witbitz-browse-publisher.mjs +0 -0
- {witbitz_code-1.9.2 → witbitz_code-1.9.4}/src/witbitz_code/opencode_plugins/witbitz-browse-tree.mjs +0 -0
- {witbitz_code-1.9.2 → witbitz_code-1.9.4}/src/witbitz_code/opencode_plugins/witbitz-browser.js +0 -0
- {witbitz_code-1.9.2 → witbitz_code-1.9.4}/src/witbitz_code/opencode_plugins/witbitz-media-server.mjs +0 -0
- {witbitz_code-1.9.2 → witbitz_code-1.9.4}/src/witbitz_code/opencode_plugins/witbitz-notes.js +0 -0
- {witbitz_code-1.9.2 → witbitz_code-1.9.4}/src/witbitz_code/opencode_plugins/witbitz-progress.js +0 -0
- {witbitz_code-1.9.2 → witbitz_code-1.9.4}/src/witbitz_code/outputs.py +0 -0
- {witbitz_code-1.9.2 → witbitz_code-1.9.4}/src/witbitz_code/pair.py +0 -0
- {witbitz_code-1.9.2 → witbitz_code-1.9.4}/src/witbitz_code/pairings.py +0 -0
- {witbitz_code-1.9.2 → witbitz_code-1.9.4}/src/witbitz_code/payers.py +0 -0
- {witbitz_code-1.9.2 → witbitz_code-1.9.4}/src/witbitz_code/plugins.py +0 -0
- {witbitz_code-1.9.2 → witbitz_code-1.9.4}/src/witbitz_code/policy.py +0 -0
- {witbitz_code-1.9.2 → witbitz_code-1.9.4}/src/witbitz_code/ports.py +0 -0
- {witbitz_code-1.9.2 → witbitz_code-1.9.4}/src/witbitz_code/qr.py +0 -0
- {witbitz_code-1.9.2 → witbitz_code-1.9.4}/src/witbitz_code/receipt.py +0 -0
- {witbitz_code-1.9.2 → witbitz_code-1.9.4}/src/witbitz_code/relay.py +0 -0
- {witbitz_code-1.9.2 → witbitz_code-1.9.4}/src/witbitz_code/remind.py +0 -0
- {witbitz_code-1.9.2 → witbitz_code-1.9.4}/src/witbitz_code/seccheck.py +0 -0
- {witbitz_code-1.9.2 → witbitz_code-1.9.4}/src/witbitz_code/seen.py +0 -0
- {witbitz_code-1.9.2 → witbitz_code-1.9.4}/src/witbitz_code/selfupdate.py +0 -0
- {witbitz_code-1.9.2 → witbitz_code-1.9.4}/src/witbitz_code/tinfoil_read.py +0 -0
- {witbitz_code-1.9.2 → witbitz_code-1.9.4}/src/witbitz_code/tools_catalog.json +0 -0
- {witbitz_code-1.9.2 → witbitz_code-1.9.4}/src/witbitz_code/tools_probe.py +0 -0
- {witbitz_code-1.9.2 → witbitz_code-1.9.4}/src/witbitz_code/toolsetup.py +0 -0
- {witbitz_code-1.9.2 → witbitz_code-1.9.4}/src/witbitz_code/ui_static/app.css +0 -0
- {witbitz_code-1.9.2 → witbitz_code-1.9.4}/src/witbitz_code/uitools.py +0 -0
- {witbitz_code-1.9.2 → witbitz_code-1.9.4}/src/witbitz_code/uninstall.py +0 -0
- {witbitz_code-1.9.2 → witbitz_code-1.9.4}/src/witbitz_code/winagent.py +0 -0
- {witbitz_code-1.9.2 → witbitz_code-1.9.4}/src/witbitz_code/winbg.py +0 -0
- {witbitz_code-1.9.2 → witbitz_code-1.9.4}/src/witbitz_code/winconsole.py +0 -0
- {witbitz_code-1.9.2 → witbitz_code-1.9.4}/src/witbitz_code/wizard.py +0 -0
- {witbitz_code-1.9.2 → witbitz_code-1.9.4}/src/witbitz_code/wslstart.py +0 -0
- {witbitz_code-1.9.2 → witbitz_code-1.9.4}/src/witbitz_code.egg-info/SOURCES.txt +0 -0
- {witbitz_code-1.9.2 → witbitz_code-1.9.4}/src/witbitz_code.egg-info/dependency_links.txt +0 -0
- {witbitz_code-1.9.2 → witbitz_code-1.9.4}/src/witbitz_code.egg-info/entry_points.txt +0 -0
- {witbitz_code-1.9.2 → witbitz_code-1.9.4}/src/witbitz_code.egg-info/requires.txt +0 -0
- {witbitz_code-1.9.2 → witbitz_code-1.9.4}/src/witbitz_code.egg-info/top_level.txt +0 -0
- {witbitz_code-1.9.2 → witbitz_code-1.9.4}/tests/test_asks.py +0 -0
- {witbitz_code-1.9.2 → witbitz_code-1.9.4}/tests/test_attachments.py +0 -0
- {witbitz_code-1.9.2 → witbitz_code-1.9.4}/tests/test_auto.py +0 -0
- {witbitz_code-1.9.2 → witbitz_code-1.9.4}/tests/test_browse.py +0 -0
- {witbitz_code-1.9.2 → witbitz_code-1.9.4}/tests/test_browsetree.py +0 -0
- {witbitz_code-1.9.2 → witbitz_code-1.9.4}/tests/test_cli.py +0 -0
- {witbitz_code-1.9.2 → witbitz_code-1.9.4}/tests/test_codetools.py +0 -0
- {witbitz_code-1.9.2 → witbitz_code-1.9.4}/tests/test_connector_e2e.py +0 -0
- {witbitz_code-1.9.2 → witbitz_code-1.9.4}/tests/test_devicelink.py +0 -0
- {witbitz_code-1.9.2 → witbitz_code-1.9.4}/tests/test_dirs.py +0 -0
- {witbitz_code-1.9.2 → witbitz_code-1.9.4}/tests/test_files_and_compress.py +0 -0
- {witbitz_code-1.9.2 → witbitz_code-1.9.4}/tests/test_folders.py +0 -0
- {witbitz_code-1.9.2 → witbitz_code-1.9.4}/tests/test_images.py +0 -0
- {witbitz_code-1.9.2 → witbitz_code-1.9.4}/tests/test_outputs.py +0 -0
- {witbitz_code-1.9.2 → witbitz_code-1.9.4}/tests/test_plugins.py +0 -0
- {witbitz_code-1.9.2 → witbitz_code-1.9.4}/tests/test_policy.py +0 -0
- {witbitz_code-1.9.2 → witbitz_code-1.9.4}/tests/test_ports.py +0 -0
- {witbitz_code-1.9.2 → witbitz_code-1.9.4}/tests/test_ports_win.py +0 -0
- {witbitz_code-1.9.2 → witbitz_code-1.9.4}/tests/test_registry.py +0 -0
- {witbitz_code-1.9.2 → witbitz_code-1.9.4}/tests/test_relay_codec.py +0 -0
- {witbitz_code-1.9.2 → witbitz_code-1.9.4}/tests/test_remind.py +0 -0
- {witbitz_code-1.9.2 → witbitz_code-1.9.4}/tests/test_seccheck.py +0 -0
- {witbitz_code-1.9.2 → witbitz_code-1.9.4}/tests/test_selfupdate.py +0 -0
- {witbitz_code-1.9.2 → witbitz_code-1.9.4}/tests/test_tools_probe.py +0 -0
- {witbitz_code-1.9.2 → witbitz_code-1.9.4}/tests/test_toolsetup.py +0 -0
- {witbitz_code-1.9.2 → witbitz_code-1.9.4}/tests/test_uitools.py +0 -0
- {witbitz_code-1.9.2 → witbitz_code-1.9.4}/tests/test_winagent.py +0 -0
- {witbitz_code-1.9.2 → witbitz_code-1.9.4}/tests/test_winbg.py +0 -0
- {witbitz_code-1.9.2 → witbitz_code-1.9.4}/tests/test_winconsole.py +0 -0
- {witbitz_code-1.9.2 → witbitz_code-1.9.4}/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.9.
|
|
7
|
+
version = "1.9.4"
|
|
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"
|
|
@@ -261,6 +261,28 @@ def without_words(frame: str) -> str:
|
|
|
261
261
|
return f"data: {dumps({**ev, 'choices': [{**ch, 'delta': rest}, *ev['choices'][1:]]})}\n\n"
|
|
262
262
|
|
|
263
263
|
|
|
264
|
+
# The confidential default (⇄ tools/code-confidential.mjs CONFIDENTIAL_DEFAULT): what answers when a pick has no verified-enclave
|
|
265
|
+
# route right now. The note says so once per session — a Code task makes many calls.
|
|
266
|
+
CONFIDENTIAL_DEFAULT = "deepseek/deepseek-v4.1-flash"
|
|
267
|
+
_NOTED: dict = {}
|
|
268
|
+
|
|
269
|
+
|
|
270
|
+
def fallback_note(frm: str, by: str, session_id: str = "") -> str:
|
|
271
|
+
if not frm:
|
|
272
|
+
return ""
|
|
273
|
+
k = session_id + "\0" + frm
|
|
274
|
+
if k in _NOTED:
|
|
275
|
+
return ""
|
|
276
|
+
_NOTED[k] = True
|
|
277
|
+
if len(_NOTED) > 500:
|
|
278
|
+
_NOTED.pop(next(iter(_NOTED)))
|
|
279
|
+
return f"\n\n_Answered by {by} — {frm} isn't available confidentially right now._"
|
|
280
|
+
|
|
281
|
+
|
|
282
|
+
def note_frame(note: str, model: str) -> str:
|
|
283
|
+
return "data: " + dumps({"id": "witbitz-note", "object": "chat.completion.chunk", "model": model, "choices": [{"index": 0, "delta": {"content": note}}]}) + "\n\n"
|
|
284
|
+
|
|
285
|
+
|
|
264
286
|
def completion_of(events: list, model: str) -> dict:
|
|
265
287
|
"""The streamed events as one chat.completion — for a caller that asked without `stream`."""
|
|
266
288
|
content = reasoning = ""
|
|
@@ -652,7 +674,10 @@ class ConfidentialProxy:
|
|
|
652
674
|
except Exception: # noqa: BLE001 — progress must never fail a call
|
|
653
675
|
pass
|
|
654
676
|
|
|
677
|
+
fell = {"from": ""} # the pick's label when TrustedRouter had no verified-enclave route for it and the default answered
|
|
678
|
+
|
|
655
679
|
async def once(words_shown: bool = False) -> None:
|
|
680
|
+
nonlocal body, conf, label
|
|
656
681
|
ends = self._proof_ends.get(body["model"])
|
|
657
682
|
hold = attempt["n"] > 1 or (ends is not None and self._now() / 1000 >= ends - HOLD_BEFORE_LAPSE_SEC)
|
|
658
683
|
n = self._nonce()
|
|
@@ -670,6 +695,16 @@ class ConfidentialProxy:
|
|
|
670
695
|
used = allowance_used(up.status_code, text)
|
|
671
696
|
if used:
|
|
672
697
|
raise used
|
|
698
|
+
# No verified-enclave route for the pick (2026-10-20, ⇄ tools/code-confidential.mjs): refused BEFORE any upstream
|
|
699
|
+
# saw the request — the same request goes again as the confidential default, attested and checked, once.
|
|
700
|
+
if (up.status_code == 400 and "no route candidates" in text.lower() and not fell["from"] and body.get("model") != CONFIDENTIAL_DEFAULT
|
|
701
|
+
and CONFIDENTIAL_DEFAULT in self.models and not res.headers_sent):
|
|
702
|
+
fell["from"] = conf["label"]
|
|
703
|
+
self.log(f"code-confidential: {body['model']} has no verified-enclave route now → {CONFIDENTIAL_DEFAULT} answers")
|
|
704
|
+
body = {**body, "model": CONFIDENTIAL_DEFAULT}
|
|
705
|
+
conf = self.models[CONFIDENTIAL_DEFAULT]
|
|
706
|
+
label = f"{conf['label']} (confidential)"
|
|
707
|
+
return await once(words_shown)
|
|
673
708
|
if res.headers_sent:
|
|
674
709
|
raise Refusal(502, f"{label}: TrustedRouter answered {up.status_code} when asked again ({text[:160]})", "upstream_error")
|
|
675
710
|
await res.head(up.status_code, {"content-type": up.headers.get("content-type", "application/json")})
|
|
@@ -710,15 +745,25 @@ class ConfidentialProxy:
|
|
|
710
745
|
exp = ((v.get("claims") or {}).get("upstream") or {}).get("verification_expires_at")
|
|
711
746
|
if isinstance(exp, (int, float)):
|
|
712
747
|
self._proof_ends[body["model"]] = exp
|
|
748
|
+
note = fallback_note(fell["from"], conf["label"], session_id)
|
|
713
749
|
if wants_stream:
|
|
750
|
+
noted = not note
|
|
714
751
|
for f in answer.held():
|
|
752
|
+
if not noted and re.search(r'"finish_reason":"|^data: \[DONE\]', f, re.M):
|
|
753
|
+
await res.write(note_frame(note, body["model"])) # the note goes in before the answer ends
|
|
754
|
+
noted = True
|
|
715
755
|
out = without_words(f) if words_shown else f
|
|
716
756
|
if out:
|
|
717
757
|
await res.write(out)
|
|
758
|
+
if not noted:
|
|
759
|
+
await res.write(note_frame(note, body["model"]))
|
|
718
760
|
await res.end()
|
|
719
761
|
return
|
|
720
762
|
await res.head(200, {"content-type": "application/json"})
|
|
721
|
-
|
|
763
|
+
done = completion_of(answer.events(), body["model"])
|
|
764
|
+
if note and done.get("choices") and done["choices"][0].get("message") is not None:
|
|
765
|
+
done["choices"][0]["message"]["content"] = (done["choices"][0]["message"].get("content") or "") + note
|
|
766
|
+
await res.end(dumps(done))
|
|
722
767
|
|
|
723
768
|
try:
|
|
724
769
|
if not conf.get("vision"):
|
|
@@ -382,6 +382,8 @@ class Manager:
|
|
|
382
382
|
await self.start_serving()
|
|
383
383
|
return {"ok": False, "why": next((s for s in reversed(said) if "✖" in s or "pass --account" in s), "could not unpair").replace("witbitz-code: ", "", 1)}
|
|
384
384
|
self.pair_state = {"state": "idle", "n": self.pair_state.get("n", 0)}
|
|
385
|
+
if self.pairings(): # one account of several unpaired: the others stay reachable (the owner, 2026-10-22: several accounts)
|
|
386
|
+
await self.start_serving()
|
|
385
387
|
return {"ok": True}
|
|
386
388
|
|
|
387
389
|
async def set_login(self, on: bool) -> dict:
|
|
@@ -60,6 +60,13 @@ function render(s) {
|
|
|
60
60
|
const who = document.createElement('div'); who.className = 'who'; who.textContent = `${p.name} → ${p.account || 'your account'}`
|
|
61
61
|
const keys = document.createElement('div'); keys.className = 'keys'; keys.textContent = p.keys || ''
|
|
62
62
|
li.append(who, keys)
|
|
63
|
+
// SEVERAL ACCOUNTS (the owner, 2026-10-22: "can we make it so it can handle multiple accounts?"): each one leaves on its own
|
|
64
|
+
if (s.paired.length > 1 && p.account) {
|
|
65
|
+
const out = document.createElement('button'); out.type = 'button'; out.className = 'ghost danger small'; out.textContent = 'Unpair ' + p.account
|
|
66
|
+
out.disabled = !!s.busy
|
|
67
|
+
out.onclick = () => act('/api/unpair', { account: p.account }, `Unpair ${p.account}? Code in that account stops reaching this computer; the other accounts keep it.`)
|
|
68
|
+
li.append(out)
|
|
69
|
+
}
|
|
63
70
|
list.append(li)
|
|
64
71
|
}
|
|
65
72
|
|
|
@@ -70,8 +77,10 @@ function render(s) {
|
|
|
70
77
|
$('start').textContent = run === 'elsewhere' ? 'Run it here' : 'Start'
|
|
71
78
|
show($('stop'), paired && run !== 'stopped')
|
|
72
79
|
show($('pairAgain'), paired && !waiting)
|
|
80
|
+
show($('pairAdd'), paired && !waiting)
|
|
73
81
|
show($('unpair'), paired && !waiting)
|
|
74
|
-
|
|
82
|
+
$('unpair').textContent = s.paired.length > 1 ? 'Unpair all' : 'Unpair'
|
|
83
|
+
for (const id of ['pair', 'start', 'stop', 'pairAgain', 'pairAdd', 'unpair', 'pairCancel', 'pairRetry']) $(id).disabled = busy
|
|
75
84
|
|
|
76
85
|
show($('pairCard'), waiting || s.pair.state === 'failed')
|
|
77
86
|
if (waiting) {
|
|
@@ -121,7 +130,7 @@ async function refresh(fresh = false) {
|
|
|
121
130
|
|
|
122
131
|
async function act(path, body, ask) {
|
|
123
132
|
if (ask && !window.confirm(ask)) return false
|
|
124
|
-
for (const id of ['pair', 'start', 'stop', 'pairAgain', 'unpair']) $(id).disabled = true
|
|
133
|
+
for (const id of ['pair', 'start', 'stop', 'pairAgain', 'pairAdd', 'unpair']) $(id).disabled = true
|
|
125
134
|
let ok = false
|
|
126
135
|
try {
|
|
127
136
|
const r = await api(path, body)
|
|
@@ -257,7 +266,7 @@ async function quit() {
|
|
|
257
266
|
quitting = true
|
|
258
267
|
try { await api('/api/quit', {}) } catch { /* it is going away */ }
|
|
259
268
|
note('Witbitz Code stopped. Start menu → Witbitz Code starts it again.', 'bad')
|
|
260
|
-
for (const id of ['pair', 'start', 'stop', 'pairAgain', 'unpair', 'quit']) $(id).disabled = true
|
|
269
|
+
for (const id of ['pair', 'start', 'stop', 'pairAgain', 'pairAdd', 'unpair', 'quit']) $(id).disabled = true
|
|
261
270
|
}
|
|
262
271
|
|
|
263
272
|
document.addEventListener('DOMContentLoaded', () => {
|
|
@@ -268,17 +277,25 @@ document.addEventListener('DOMContentLoaded', () => {
|
|
|
268
277
|
$('pair').onclick = () => act('/api/pair', {})
|
|
269
278
|
$('start').onclick = () => act('/api/serve', { on: true })
|
|
270
279
|
$('stop').onclick = () => act('/api/serve', { on: false }, 'Stop? Code in Spaces will not reach this computer until you start it again.')
|
|
271
|
-
$('unpair').onclick = () => {
|
|
272
|
-
const
|
|
273
|
-
if (account
|
|
274
|
-
|
|
280
|
+
$('unpair').onclick = async () => {
|
|
281
|
+
const accounts = ((state && state.paired) || []).map((p) => p.account).filter(Boolean)
|
|
282
|
+
if (accounts.length > 1) { // every account, one at a time (the command unpairs one account per call)
|
|
283
|
+
if (!window.confirm(`Unpair all ${accounts.length} accounts? Code in each of them stops reaching this computer, and its coding keys go back.`)) return
|
|
284
|
+
for (const account of accounts) if (!(await act('/api/unpair', { account }))) return
|
|
285
|
+
return
|
|
286
|
+
}
|
|
287
|
+
act('/api/unpair', { account: accounts[0] || '' }, 'Unpair this computer? It leaves your Spaces account, its coding keys go back, and Code on every device drops it.')
|
|
275
288
|
}
|
|
289
|
+
|
|
276
290
|
$('pairAgain').onclick = async () => {
|
|
277
291
|
const account = accountOf()
|
|
278
292
|
if (account === null) return
|
|
279
293
|
if (!window.confirm('Pair again? This computer is unpaired first, then shows a new code to scan.')) return
|
|
280
294
|
if (await act('/api/unpair', { account })) act('/api/pair', {})
|
|
281
295
|
}
|
|
296
|
+
// ANOTHER ACCOUNT ON THIS COMPUTER: a new code beside the pairings there are (pairings.json holds one per account, on the
|
|
297
|
+
// same OpenCode); nothing is unpaired. Code here pauses while the code is up and comes back when it is scanned or cancelled.
|
|
298
|
+
$('pairAdd').onclick = () => act('/api/pair', {}, 'Add another account? A new code appears — scan it from the other account: Code in Spaces → Computers → Add a computer. Code on this computer pauses until it is scanned or cancelled.')
|
|
282
299
|
$('pairCancel').onclick = () => act('/api/pair/cancel', {})
|
|
283
300
|
$('pairRetry').onclick = () => act('/api/pair', {})
|
|
284
301
|
$('login').onchange = (e) => act('/api/login', { on: e.target.checked })
|
|
@@ -25,6 +25,7 @@
|
|
|
25
25
|
<button id="start" type="button" hidden>Start</button>
|
|
26
26
|
<button id="stop" type="button" class="ghost" hidden>Stop</button>
|
|
27
27
|
<button id="pairAgain" type="button" class="ghost" hidden>Pair again</button>
|
|
28
|
+
<button id="pairAdd" type="button" class="ghost" hidden>Add another account</button>
|
|
28
29
|
<button id="unpair" type="button" class="ghost danger" hidden>Unpair</button>
|
|
29
30
|
</div>
|
|
30
31
|
<p id="note" class="note" hidden></p>
|
|
@@ -10,7 +10,7 @@ import json
|
|
|
10
10
|
|
|
11
11
|
import httpx
|
|
12
12
|
import pytest
|
|
13
|
-
from conftest import requires_node, run_node
|
|
13
|
+
from conftest import requires_server_keys, requires_node, run_node
|
|
14
14
|
|
|
15
15
|
from witbitz_code import codekeys, confidential as cf, models as mdl
|
|
16
16
|
from witbitz_code.payers import SessionPayers
|
|
@@ -47,6 +47,7 @@ def test_a_secret_the_server_seals_to_this_computer_opens_here_and_nowhere_else(
|
|
|
47
47
|
|
|
48
48
|
|
|
49
49
|
@requires_node
|
|
50
|
+
@requires_server_keys
|
|
50
51
|
def test_this_computers_signature_is_the_one_the_server_accepts_and_only_for_its_op():
|
|
51
52
|
ident = codekeys.new_identity()
|
|
52
53
|
entry = {**ident, "keyId": f"{U}.{K}"}
|
|
@@ -410,3 +410,35 @@ def test_the_months_allowance_used_is_a_402_that_says_so_never_a_429_opencode_wo
|
|
|
410
410
|
with_proxy(t, net=LimitNet(meta, stream.decode("utf-8")), models=models)
|
|
411
411
|
assert cf.allowance_used(429, '{"error":{"message":"rate limited"}}') is None, "a real rate limit stays a rate limit"
|
|
412
412
|
assert cf.allowance_used(400, json.dumps(SPEND_LIMIT)) is None
|
|
413
|
+
|
|
414
|
+
|
|
415
|
+
class NoRouteThenReplay(FakeNet):
|
|
416
|
+
"""TrustedRouter refuses any model but the default ("No route candidates" — before any upstream sees it); the default
|
|
417
|
+
answers with the captured stream."""
|
|
418
|
+
|
|
419
|
+
def handler(self, req: httpx.Request):
|
|
420
|
+
if "googleapis.com/service_accounts" not in str(req.url) and json.loads(req.content.decode("utf-8")).get("model") != cf.CONFIDENTIAL_DEFAULT:
|
|
421
|
+
self.calls.append({"url": str(req.url), "headers": dict(req.headers), "raw": list(req.headers.raw), "body": req.content.decode("utf-8")})
|
|
422
|
+
return httpx.Response(400, json={"error": {"message": "No route candidates match the requested provider filters"}})
|
|
423
|
+
return super().handler(req)
|
|
424
|
+
|
|
425
|
+
|
|
426
|
+
@needs_fixture
|
|
427
|
+
def test_a_pick_with_no_verified_enclave_route_is_answered_by_the_default_with_one_note_per_session():
|
|
428
|
+
meta, stream, request = fixture()
|
|
429
|
+
net = NoRouteThenReplay(meta, stream.decode("utf-8"))
|
|
430
|
+
logs = []
|
|
431
|
+
glm = {**SAY_OK, "model": "z-ai/glm-5.3"}
|
|
432
|
+
|
|
433
|
+
async def t(post, net_, _p):
|
|
434
|
+
r = await post(glm, {"x-session-id": "ses_fb"})
|
|
435
|
+
text = r.text
|
|
436
|
+
assert [json.loads(c["body"])["model"] for c in net.calls] == ["z-ai/glm-5.3", "deepseek/deepseek-v4.1-flash"], "the pick, then the default — once"
|
|
437
|
+
assert net.calls[1]["body"].encode() == request, "the same request, only the model changed — the receipt signs it"
|
|
438
|
+
assert '"content":"OK"' in text
|
|
439
|
+
assert "Answered by DeepSeek V4.1 Flash — GLM 5.3 isn't available confidentially right now." in text
|
|
440
|
+
assert text.index("Answered by") < text.index("[DONE]")
|
|
441
|
+
again = (await post(glm, {"x-session-id": "ses_fb"})).text
|
|
442
|
+
assert "Answered by" not in again and '"content":"OK"' in again, "said once per session"
|
|
443
|
+
assert any("has no verified-enclave route now" in line for line in logs)
|
|
444
|
+
with_proxy(t, net=net, log=logs.append)
|
|
@@ -277,18 +277,18 @@ def test_status_output_is_json_free_of_secrets_even_for_js_written_files():
|
|
|
277
277
|
|
|
278
278
|
|
|
279
279
|
def test_rename_relabels_the_computer_in_the_account_and_here_with_no_scan(monkeypatch):
|
|
280
|
-
# WSL's pairing and Windows' were two "
|
|
281
|
-
api = MockApi(docs={"computers": with_computer(None, "cmp_wsl_1", {"name": "
|
|
282
|
-
entry = {"account": "a@
|
|
280
|
+
# WSL's pairing and Windows' were two "my-laptop" in Code (the owner, 2026-10-09); setup renames WSL's "… (WSL)"
|
|
281
|
+
api = MockApi(docs={"computers": with_computer(None, "cmp_wsl_1", {"name": "my-laptop", "relay": RELAY, "secret": SECRET}, 500)})
|
|
282
|
+
entry = {"account": "a@example.com", "idx": IDX, "computerId": "cmp_wsl_1", "secret": SECRET, "name": "my-laptop", "relay": RELAY,
|
|
283
283
|
"opencodeUrl": "http://127.0.0.1:4096"}
|
|
284
284
|
pp.write_pairings({"v": 1, "pairings": [entry]})
|
|
285
|
-
assert run(pair.rename(entry, "
|
|
285
|
+
assert run(pair.rename(entry, "my-laptop (WSL)", call=api.call, out=lambda m: None, err=lambda m: None)) is True
|
|
286
286
|
reg = norm_registry(api.plain("computers"))
|
|
287
|
-
assert reg["computers"]["cmp_wsl_1"]["name"] == "
|
|
288
|
-
assert [p["name"] for p in pp.read_pairings()["pairings"]] == ["
|
|
287
|
+
assert reg["computers"]["cmp_wsl_1"]["name"] == "my-laptop (WSL)" and reg["computers"]["cmp_wsl_1"]["secret"] == SECRET
|
|
288
|
+
assert [p["name"] for p in pp.read_pairings()["pairings"]] == ["my-laptop (WSL)"]
|
|
289
289
|
refused = MockApi(read_status=500)
|
|
290
|
-
assert run(pair.rename({**entry, "name": "
|
|
291
|
-
assert [p["name"] for p in pp.read_pairings()["pairings"]] == ["
|
|
290
|
+
assert run(pair.rename({**entry, "name": "my-laptop (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"]] == ["my-laptop (WSL)"], "nothing renamed here when the account refused"
|
|
292
292
|
|
|
293
293
|
|
|
294
294
|
def test_a_new_pairing_on_wsl_is_named_for_wsl(monkeypatch):
|
|
@@ -296,8 +296,8 @@ def test_a_new_pairing_on_wsl_is_named_for_wsl(monkeypatch):
|
|
|
296
296
|
|
|
297
297
|
from witbitz_code import ports
|
|
298
298
|
|
|
299
|
-
monkeypatch.setattr(socket, "gethostname", lambda: "
|
|
299
|
+
monkeypatch.setattr(socket, "gethostname", lambda: "my-laptop")
|
|
300
300
|
monkeypatch.setattr(ports, "on_wsl", lambda: True)
|
|
301
|
-
assert pp.hostname() == "
|
|
301
|
+
assert pp.hostname() == "my-laptop (WSL)"
|
|
302
302
|
monkeypatch.setattr(ports, "on_wsl", lambda: False)
|
|
303
|
-
assert pp.hostname() == "
|
|
303
|
+
assert pp.hostname() == "my-laptop"
|
|
@@ -108,7 +108,7 @@ class Fake:
|
|
|
108
108
|
async def pair(port):
|
|
109
109
|
self.paired.append(port)
|
|
110
110
|
if not any(p.get("computerId") == "c" for p in self._pairings):
|
|
111
|
-
self._pairings.append({"name": "desk", "account": "a@
|
|
111
|
+
self._pairings.append({"name": "desk", "account": "a@example.com", "opencodeUrl": f"http://127.0.0.1:{port}", "idx": {"room": "r"}, "computerId": "c"})
|
|
112
112
|
if self._gives:
|
|
113
113
|
self._keyed.add("c")
|
|
114
114
|
async def listening(port):
|
|
@@ -213,14 +213,14 @@ def test_s_stops_setup_and_leaves_the_running_one_alone():
|
|
|
213
213
|
def test_a_computer_removed_from_the_account_is_paired_again_and_shows_the_qr():
|
|
214
214
|
# the owner, 2026-10-08: reinstalled after the computer was removed and "it does not present the QR code" — its pairing
|
|
215
215
|
# and keys were still on disk, so setup said "✓ Paired" over keys the account had already taken back
|
|
216
|
-
f = Fake(pairings=[{"name": "desk", "account": "a@
|
|
216
|
+
f = Fake(pairings=[{"name": "desk", "account": "a@example.com", "opencodeUrl": "http://127.0.0.1:4096", "idx": {"room": "r"}, "computerId": "c"}], keyed={"c"})
|
|
217
217
|
asked = []
|
|
218
218
|
async def keys_state(p):
|
|
219
219
|
asked.append(p["computerId"])
|
|
220
220
|
return "live" if f.paired else "revoked"
|
|
221
221
|
r = run(wizard.run_setup(f.deps(keys_state=keys_state, forget_keys=lambda p: f._keyed.discard(p["computerId"]))))
|
|
222
222
|
assert f.paired == [4096] and r["paired"] and r["keys"] and f.served == [4096]
|
|
223
|
-
assert any('"desk" → a@
|
|
223
|
+
assert any('"desk" → a@example.com was removed from your Witbitz account' in line for line in f.said)
|
|
224
224
|
assert any("scan the code below" in line for line in f.said)
|
|
225
225
|
assert not any(line.startswith(" ✓ Paired") for line in f.said[:f.said.index(next(x for x in f.said if "was removed" in x))])
|
|
226
226
|
|
|
@@ -248,7 +248,7 @@ def test_windows_and_wsl_each_run_their_own_the_windows_one_moves_off_the_port_w
|
|
|
248
248
|
|
|
249
249
|
|
|
250
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 "
|
|
251
|
+
# WSL calls itself by the Windows computer's name: Windows' and WSL's pairings were two "my-laptop" in Code
|
|
252
252
|
f = Fake(pairings=[DESK], keyed={"c"})
|
|
253
253
|
renamed = []
|
|
254
254
|
|
|
@@ -11,7 +11,7 @@ import httpx
|
|
|
11
11
|
|
|
12
12
|
from witbitz_code import ui
|
|
13
13
|
|
|
14
|
-
DESK = {"name": "
|
|
14
|
+
DESK = {"name": "my-laptop", "account": "a@example.com", "opencodeUrl": "http://127.0.0.1:4097", "idx": {"room": "r"}, "computerId": "c"}
|
|
15
15
|
|
|
16
16
|
|
|
17
17
|
def run(coro):
|
|
@@ -39,11 +39,11 @@ class World:
|
|
|
39
39
|
self.pair_calls.append(argv)
|
|
40
40
|
if "--unpair" in argv:
|
|
41
41
|
self.pairings = []
|
|
42
|
-
out("witbitz-code: ✓ removed \"
|
|
42
|
+
out("witbitz-code: ✓ removed \"my-laptop\" from a@example.com — every device drops it on its next sync")
|
|
43
43
|
return 0
|
|
44
44
|
await self.scan.wait()
|
|
45
45
|
self.pairings = [{**DESK, "opencodeUrl": f"http://127.0.0.1:{argv[argv.index('--port') + 1]}"}]
|
|
46
|
-
out("witbitz-code: ✓ paired \"
|
|
46
|
+
out("witbitz-code: ✓ paired \"my-laptop\" with a@example.com (pairings.json)")
|
|
47
47
|
return 0
|
|
48
48
|
|
|
49
49
|
def manager(self, listening=()):
|
|
@@ -58,7 +58,7 @@ class World:
|
|
|
58
58
|
keys_line=keys_line, login_spec=spec, login_state=lambda s: "on" if self.login["on"] else "off",
|
|
59
59
|
login_enable=lambda s: (self.login.__setitem__("on", True), (True, ""))[1],
|
|
60
60
|
login_disable=lambda s: self.login.__setitem__("on", False) or True,
|
|
61
|
-
label={"label": "
|
|
61
|
+
label={"label": "my-laptop", "kind": "Windows"}, log=deque(maxlen=60))
|
|
62
62
|
|
|
63
63
|
|
|
64
64
|
async def with_server(m):
|
|
@@ -113,8 +113,8 @@ def test_status_start_and_stop_serving_on_the_paired_port():
|
|
|
113
113
|
try:
|
|
114
114
|
async with httpx.AsyncClient() as c:
|
|
115
115
|
st = (await c.get(f"{base}/api/status", headers=authed(srv))).json()
|
|
116
|
-
assert st["computer"]["label"] == "
|
|
117
|
-
assert st["paired"] == [{"name": "
|
|
116
|
+
assert st["computer"]["label"] == "my-laptop" and st["serving"]["state"] == "stopped"
|
|
117
|
+
assert st["paired"] == [{"name": "my-laptop", "account": "a@example.com", "port": 4097, "keys": "coding keys: live · models this month $1.2 of $50 · tools up to $5"}]
|
|
118
118
|
assert (await c.post(f"{base}/api/serve", headers=authed(srv), json={"on": True})).json() == {"ok": True}
|
|
119
119
|
assert await until(lambda: _state(c, base, srv, "online"))
|
|
120
120
|
assert w.serves == [["--port", "4097"]], "the port this computer is paired for"
|
|
@@ -187,11 +187,11 @@ def test_a_cancelled_pairing_goes_back_to_serving_and_the_login_switch_writes_th
|
|
|
187
187
|
|
|
188
188
|
|
|
189
189
|
def test_the_page_names_which_computer_it_controls(monkeypatch):
|
|
190
|
-
monkeypatch.setattr(ui.socket, "gethostname", lambda: "
|
|
190
|
+
monkeypatch.setattr(ui.socket, "gethostname", lambda: "my-laptop")
|
|
191
191
|
monkeypatch.setattr(ui.ports, "on_wsl", lambda: True)
|
|
192
192
|
monkeypatch.setattr(ui.sys, "platform", "linux")
|
|
193
193
|
monkeypatch.setenv("WSL_DISTRO_NAME", "UbuntuD")
|
|
194
|
-
assert ui.computer_label() == {"label": "UbuntuD (WSL) on
|
|
194
|
+
assert ui.computer_label() == {"label": "UbuntuD (WSL) on my-laptop", "kind": "WSL UbuntuD"}
|
|
195
195
|
|
|
196
196
|
|
|
197
197
|
def _other_window_manager(w, held):
|
|
@@ -208,7 +208,7 @@ def _other_window_manager(w, held):
|
|
|
208
208
|
|
|
209
209
|
m = ui.Manager(serve=w.serve, is_listening=is_listening, pair=w.pair, load_pairings=lambda: list(w.pairings),
|
|
210
210
|
port_owners=lambda q: [{"pid": 19824, "cmd": "python.exe"}] if q in held else [],
|
|
211
|
-
stop_pid=stop_pid, login_spec=lambda: None, label={"label": "
|
|
211
|
+
stop_pid=stop_pid, login_spec=lambda: None, label={"label": "my-laptop", "kind": "Windows"}, log=deque(maxlen=60))
|
|
212
212
|
return m, stopped
|
|
213
213
|
|
|
214
214
|
|
|
@@ -334,3 +334,58 @@ def test_opening_the_page_asks_for_a_release_again_a_status_poll_does_not():
|
|
|
334
334
|
await m.shutdown()
|
|
335
335
|
await srv.close()
|
|
336
336
|
run(t())
|
|
337
|
+
|
|
338
|
+
|
|
339
|
+
class TwoAccounts(World):
|
|
340
|
+
"""A second account pairs beside the first (pairings.json holds one entry per account), and unpairs alone."""
|
|
341
|
+
|
|
342
|
+
async def pair(self, argv, *, link_fn=None, out=print, err=print):
|
|
343
|
+
self.pair_calls.append(argv)
|
|
344
|
+
if "--unpair" in argv:
|
|
345
|
+
who = argv[argv.index("--account") + 1] if "--account" in argv else ""
|
|
346
|
+
self.pairings = [p for p in self.pairings if who and p["account"] != who]
|
|
347
|
+
out(f"witbitz-code: ✓ removed \"my-laptop\" from {who or 'every account'}")
|
|
348
|
+
return 0
|
|
349
|
+
await self.scan.wait()
|
|
350
|
+
port = argv[argv.index("--port") + 1]
|
|
351
|
+
self.pairings = self.pairings + [{**DESK, "account": "b@example.com", "idx": {"room": "r2"}, "opencodeUrl": f"http://127.0.0.1:{port}"}]
|
|
352
|
+
out("witbitz-code: ✓ paired \"my-laptop\" with b@example.com (pairings.json)")
|
|
353
|
+
return 0
|
|
354
|
+
|
|
355
|
+
|
|
356
|
+
def test_another_account_pairs_beside_the_first_and_one_unpairs_while_the_other_keeps_serving():
|
|
357
|
+
# The owner, 2026-10-22: "can we make it so it can handle multiple accounts?" — the page's Add another account
|
|
358
|
+
async def go():
|
|
359
|
+
w = TwoAccounts([DESK])
|
|
360
|
+
m = w.manager(listening={4096, 4196})
|
|
361
|
+
srv, base = await with_server(m)
|
|
362
|
+
try:
|
|
363
|
+
await m.start_serving()
|
|
364
|
+
async with httpx.AsyncClient() as c:
|
|
365
|
+
assert (await c.post(f"{base}/api/pair", headers=authed(srv), json={})).json() == {"ok": True}
|
|
366
|
+
assert w.pair_calls[-1] == ["--port", "4097"], "the same OpenCode as the first account's — nothing unpaired"
|
|
367
|
+
w.scan.set()
|
|
368
|
+
assert await until(lambda: _state(c, base, srv, "online"))
|
|
369
|
+
st = (await c.get(f"{base}/api/status", headers=authed(srv))).json()
|
|
370
|
+
assert sorted(p["account"] for p in st["paired"]) == ["a@example.com", "b@example.com"]
|
|
371
|
+
# one account leaves; the other stays paired AND reachable
|
|
372
|
+
assert (await c.post(f"{base}/api/unpair", headers=authed(srv), json={"account": "b@example.com"})).json() == {"ok": True}
|
|
373
|
+
assert w.pair_calls[-1] == ["--unpair", "--account", "b@example.com"]
|
|
374
|
+
assert [p["account"] for p in w.pairings] == ["a@example.com"]
|
|
375
|
+
assert await until(lambda: _state(c, base, srv, "online")), "serving resumes for the account that stayed"
|
|
376
|
+
finally:
|
|
377
|
+
await m.shutdown()
|
|
378
|
+
await srv.close()
|
|
379
|
+
|
|
380
|
+
run(go())
|
|
381
|
+
|
|
382
|
+
|
|
383
|
+
def test_the_page_offers_another_account_and_an_unpair_per_account():
|
|
384
|
+
from pathlib import Path
|
|
385
|
+
root = Path(ui.__file__).parent / "ui_static"
|
|
386
|
+
html, js = (root / "index.html").read_text(), (root / "app.js").read_text()
|
|
387
|
+
assert 'id="pairAdd"' in html and "Add another account" in html
|
|
388
|
+
assert "show($('pairAdd'), paired && !waiting)" in js
|
|
389
|
+
assert "$('pairAdd').onclick = () => act('/api/pair', {}" in js, "a new code beside the pairings there are — no unpair first"
|
|
390
|
+
assert "if (s.paired.length > 1 && p.account)" in js and "act('/api/unpair', { account: p.account }" in js
|
|
391
|
+
assert "for (const account of accounts) if (!(await act('/api/unpair', { account }))) return" in js, "Unpair all = each account in turn"
|
|
@@ -75,7 +75,7 @@ class Fake:
|
|
|
75
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
|
-
PAIR = {"name": "desk", "account": "a@
|
|
78
|
+
PAIR = {"name": "desk", "account": "a@example.com", "idx": {"room": "r"}, "computerId": "c"}
|
|
79
79
|
|
|
80
80
|
|
|
81
81
|
def test_saying_no_changes_nothing():
|
|
@@ -207,7 +207,7 @@ def test_what_runs_witbitz_code_is_the_page_s_manager_and_the_paired_ports_owner
|
|
|
207
207
|
|
|
208
208
|
|
|
209
209
|
def test_windows_finds_opencode_by_name():
|
|
210
|
-
out = '"opencode.exe","19228","Console","1","95,000 K","Running","
|
|
210
|
+
out = '"opencode.exe","19228","Console","1","95,000 K","Running","MY-LAPTOP\\shter","0:00:03","N/A"\n' \
|
|
211
211
|
'"opencode.exe","555","Console","1","1 K","Running","OTHER\\bob","0:00:01","N/A"\n'
|
|
212
212
|
assert un.win_opencode_processes(run=lambda argv: out, user="shter") == [{"pid": 19228, "cmd": "opencode.exe"}]
|
|
213
213
|
|
|
@@ -12,7 +12,7 @@ from witbitz_code import wslstart
|
|
|
12
12
|
|
|
13
13
|
def test_the_shortcut_runs_this_distro_this_user_from_home_then_serve():
|
|
14
14
|
assert wslstart.shortcut_name("UbuntuD") == "Witbitz Code (WSL UbuntuD).lnk" and wslstart.shortcut_name("") == "Witbitz Code (WSL).lnk"
|
|
15
|
-
assert wslstart.wsl_args("UbuntuD", "ubuntu", ["/home/
|
|
15
|
+
assert wslstart.wsl_args("UbuntuD", "ubuntu", ["/home/dana/.local/bin/witbitz-code"]) == "-d UbuntuD -u ubuntu --cd ~ -- /home/dana/.local/bin/witbitz-code serve"
|
|
16
16
|
assert wslstart.wsl_args("", "a b", ["/x y/witbitz-code"]) == '-u "a b" --cd ~ -- "/x y/witbitz-code" serve'
|
|
17
17
|
s = wslstart.enable_script("it's.lnk", "-d X -- /w serve")
|
|
18
18
|
assert "'it''s.lnk'" in s and "$s.WindowStyle = 7" in s and "System32\\wsl.exe" in s and "[Environment]::GetFolderPath('Startup')" in s
|
|
@@ -32,7 +32,7 @@ def test_state_enable_and_disable_read_what_powershell_says():
|
|
|
32
32
|
|
|
33
33
|
@requires_node
|
|
34
34
|
def test_the_javascript_writes_the_same_shortcut():
|
|
35
|
-
cases = [["UbuntuD", "ubuntu", ["/home/
|
|
35
|
+
cases = [["UbuntuD", "ubuntu", ["/home/dana/.local/bin/witbitz-code"]], ["", "a b", ["/usr/bin/node", "/x y/witbitz-code.mjs"]]]
|
|
36
36
|
js = run_node("""
|
|
37
37
|
import { shortcutName, wslArgs, enableScript, stateScript, disableScript } from '@TOOLS@/code-wsl-start.mjs'
|
|
38
38
|
OUT(INPUT.cases.map(([d, u, e]) => [shortcutName(d), wslArgs(d, u, e), enableScript(shortcutName(d), wslArgs(d, u, e)), stateScript(shortcutName(d)), disableScript(shortcutName(d))]))
|
|
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
|
{witbitz_code-1.9.2 → witbitz_code-1.9.4}/src/witbitz_code/opencode_plugins/witbitz-browse-cdp.mjs
RENAMED
|
File without changes
|
{witbitz_code-1.9.2 → witbitz_code-1.9.4}/src/witbitz_code/opencode_plugins/witbitz-browse-local.mjs
RENAMED
|
File without changes
|
|
File without changes
|
{witbitz_code-1.9.2 → witbitz_code-1.9.4}/src/witbitz_code/opencode_plugins/witbitz-browse-tree.mjs
RENAMED
|
File without changes
|
{witbitz_code-1.9.2 → witbitz_code-1.9.4}/src/witbitz_code/opencode_plugins/witbitz-browser.js
RENAMED
|
File without changes
|
{witbitz_code-1.9.2 → witbitz_code-1.9.4}/src/witbitz_code/opencode_plugins/witbitz-media-server.mjs
RENAMED
|
File without changes
|
{witbitz_code-1.9.2 → witbitz_code-1.9.4}/src/witbitz_code/opencode_plugins/witbitz-notes.js
RENAMED
|
File without changes
|
{witbitz_code-1.9.2 → witbitz_code-1.9.4}/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
|