witbitz-code 1.11.0__tar.gz → 1.12.0__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- {witbitz_code-1.11.0/src/witbitz_code.egg-info → witbitz_code-1.12.0}/PKG-INFO +1 -1
- {witbitz_code-1.11.0 → witbitz_code-1.12.0}/pyproject.toml +1 -1
- {witbitz_code-1.11.0 → witbitz_code-1.12.0}/src/witbitz_code/__init__.py +1 -1
- {witbitz_code-1.11.0 → witbitz_code-1.12.0}/src/witbitz_code/codekeys.py +55 -7
- {witbitz_code-1.11.0 → witbitz_code-1.12.0/src/witbitz_code.egg-info}/PKG-INFO +1 -1
- {witbitz_code-1.11.0 → witbitz_code-1.12.0}/tests/test_codekeys.py +35 -0
- {witbitz_code-1.11.0 → witbitz_code-1.12.0}/LICENSE +0 -0
- {witbitz_code-1.11.0 → witbitz_code-1.12.0}/README.md +0 -0
- {witbitz_code-1.11.0 → witbitz_code-1.12.0}/setup.cfg +0 -0
- {witbitz_code-1.11.0 → witbitz_code-1.12.0}/src/witbitz_code/__main__.py +0 -0
- {witbitz_code-1.11.0 → witbitz_code-1.12.0}/src/witbitz_code/_js.py +0 -0
- {witbitz_code-1.11.0 → witbitz_code-1.12.0}/src/witbitz_code/account.py +0 -0
- {witbitz_code-1.11.0 → witbitz_code-1.12.0}/src/witbitz_code/app.py +0 -0
- {witbitz_code-1.11.0 → witbitz_code-1.12.0}/src/witbitz_code/asks.py +0 -0
- {witbitz_code-1.11.0 → witbitz_code-1.12.0}/src/witbitz_code/attachments.py +0 -0
- {witbitz_code-1.11.0 → witbitz_code-1.12.0}/src/witbitz_code/auto.py +0 -0
- {witbitz_code-1.11.0 → witbitz_code-1.12.0}/src/witbitz_code/auto_runner.py +0 -0
- {witbitz_code-1.11.0 → witbitz_code-1.12.0}/src/witbitz_code/browse.py +0 -0
- {witbitz_code-1.11.0 → witbitz_code-1.12.0}/src/witbitz_code/browsetree.py +0 -0
- {witbitz_code-1.11.0 → witbitz_code-1.12.0}/src/witbitz_code/cli.py +0 -0
- {witbitz_code-1.11.0 → witbitz_code-1.12.0}/src/witbitz_code/codetools.py +0 -0
- {witbitz_code-1.11.0 → witbitz_code-1.12.0}/src/witbitz_code/compress.py +0 -0
- {witbitz_code-1.11.0 → witbitz_code-1.12.0}/src/witbitz_code/computers.py +0 -0
- {witbitz_code-1.11.0 → witbitz_code-1.12.0}/src/witbitz_code/confidential.py +0 -0
- {witbitz_code-1.11.0 → witbitz_code-1.12.0}/src/witbitz_code/confidential_models.json +0 -0
- {witbitz_code-1.11.0 → witbitz_code-1.12.0}/src/witbitz_code/connector.py +0 -0
- {witbitz_code-1.11.0 → witbitz_code-1.12.0}/src/witbitz_code/devicelink.py +0 -0
- {witbitz_code-1.11.0 → witbitz_code-1.12.0}/src/witbitz_code/dirs.py +0 -0
- {witbitz_code-1.11.0 → witbitz_code-1.12.0}/src/witbitz_code/folders.py +0 -0
- {witbitz_code-1.11.0 → witbitz_code-1.12.0}/src/witbitz_code/gateway.py +0 -0
- {witbitz_code-1.11.0 → witbitz_code-1.12.0}/src/witbitz_code/icons/witbitz-code-wsl.ico +0 -0
- {witbitz_code-1.11.0 → witbitz_code-1.12.0}/src/witbitz_code/icons/witbitz-code.ico +0 -0
- {witbitz_code-1.11.0 → witbitz_code-1.12.0}/src/witbitz_code/images.py +0 -0
- {witbitz_code-1.11.0 → witbitz_code-1.12.0}/src/witbitz_code/keys.py +0 -0
- {witbitz_code-1.11.0 → witbitz_code-1.12.0}/src/witbitz_code/link.py +0 -0
- {witbitz_code-1.11.0 → witbitz_code-1.12.0}/src/witbitz_code/mkdir.py +0 -0
- {witbitz_code-1.11.0 → witbitz_code-1.12.0}/src/witbitz_code/models.py +0 -0
- {witbitz_code-1.11.0 → witbitz_code-1.12.0}/src/witbitz_code/net.py +0 -0
- {witbitz_code-1.11.0 → witbitz_code-1.12.0}/src/witbitz_code/offered_models.json +0 -0
- {witbitz_code-1.11.0 → witbitz_code-1.12.0}/src/witbitz_code/opencode_plugins/notes-init.md +0 -0
- {witbitz_code-1.11.0 → witbitz_code-1.12.0}/src/witbitz_code/opencode_plugins/witbitz-browse-cdp.mjs +0 -0
- {witbitz_code-1.11.0 → witbitz_code-1.12.0}/src/witbitz_code/opencode_plugins/witbitz-browse-local.mjs +0 -0
- {witbitz_code-1.11.0 → witbitz_code-1.12.0}/src/witbitz_code/opencode_plugins/witbitz-browse-publisher.mjs +0 -0
- {witbitz_code-1.11.0 → witbitz_code-1.12.0}/src/witbitz_code/opencode_plugins/witbitz-browse-tree.mjs +0 -0
- {witbitz_code-1.11.0 → witbitz_code-1.12.0}/src/witbitz_code/opencode_plugins/witbitz-browser.js +0 -0
- {witbitz_code-1.11.0 → witbitz_code-1.12.0}/src/witbitz_code/opencode_plugins/witbitz-media-server.mjs +0 -0
- {witbitz_code-1.11.0 → witbitz_code-1.12.0}/src/witbitz_code/opencode_plugins/witbitz-notes.js +0 -0
- {witbitz_code-1.11.0 → witbitz_code-1.12.0}/src/witbitz_code/opencode_plugins/witbitz-progress.js +0 -0
- {witbitz_code-1.11.0 → witbitz_code-1.12.0}/src/witbitz_code/outputs.py +0 -0
- {witbitz_code-1.11.0 → witbitz_code-1.12.0}/src/witbitz_code/pair.py +0 -0
- {witbitz_code-1.11.0 → witbitz_code-1.12.0}/src/witbitz_code/pairings.py +0 -0
- {witbitz_code-1.11.0 → witbitz_code-1.12.0}/src/witbitz_code/payers.py +0 -0
- {witbitz_code-1.11.0 → witbitz_code-1.12.0}/src/witbitz_code/plugins.py +0 -0
- {witbitz_code-1.11.0 → witbitz_code-1.12.0}/src/witbitz_code/policy.py +0 -0
- {witbitz_code-1.11.0 → witbitz_code-1.12.0}/src/witbitz_code/ports.py +0 -0
- {witbitz_code-1.11.0 → witbitz_code-1.12.0}/src/witbitz_code/qr.py +0 -0
- {witbitz_code-1.11.0 → witbitz_code-1.12.0}/src/witbitz_code/receipt.py +0 -0
- {witbitz_code-1.11.0 → witbitz_code-1.12.0}/src/witbitz_code/relay.py +0 -0
- {witbitz_code-1.11.0 → witbitz_code-1.12.0}/src/witbitz_code/remind.py +0 -0
- {witbitz_code-1.11.0 → witbitz_code-1.12.0}/src/witbitz_code/seccheck.py +0 -0
- {witbitz_code-1.11.0 → witbitz_code-1.12.0}/src/witbitz_code/seen.py +0 -0
- {witbitz_code-1.11.0 → witbitz_code-1.12.0}/src/witbitz_code/selfupdate.py +0 -0
- {witbitz_code-1.11.0 → witbitz_code-1.12.0}/src/witbitz_code/tinfoil_read.py +0 -0
- {witbitz_code-1.11.0 → witbitz_code-1.12.0}/src/witbitz_code/tools_catalog.json +0 -0
- {witbitz_code-1.11.0 → witbitz_code-1.12.0}/src/witbitz_code/tools_probe.py +0 -0
- {witbitz_code-1.11.0 → witbitz_code-1.12.0}/src/witbitz_code/toolsetup.py +0 -0
- {witbitz_code-1.11.0 → witbitz_code-1.12.0}/src/witbitz_code/ui.py +0 -0
- {witbitz_code-1.11.0 → witbitz_code-1.12.0}/src/witbitz_code/ui_static/app.css +0 -0
- {witbitz_code-1.11.0 → witbitz_code-1.12.0}/src/witbitz_code/ui_static/app.js +0 -0
- {witbitz_code-1.11.0 → witbitz_code-1.12.0}/src/witbitz_code/ui_static/index.html +0 -0
- {witbitz_code-1.11.0 → witbitz_code-1.12.0}/src/witbitz_code/uitools.py +0 -0
- {witbitz_code-1.11.0 → witbitz_code-1.12.0}/src/witbitz_code/uninstall.py +0 -0
- {witbitz_code-1.11.0 → witbitz_code-1.12.0}/src/witbitz_code/winagent.py +0 -0
- {witbitz_code-1.11.0 → witbitz_code-1.12.0}/src/witbitz_code/winbg.py +0 -0
- {witbitz_code-1.11.0 → witbitz_code-1.12.0}/src/witbitz_code/winconsole.py +0 -0
- {witbitz_code-1.11.0 → witbitz_code-1.12.0}/src/witbitz_code/wizard.py +0 -0
- {witbitz_code-1.11.0 → witbitz_code-1.12.0}/src/witbitz_code/wslstart.py +0 -0
- {witbitz_code-1.11.0 → witbitz_code-1.12.0}/src/witbitz_code.egg-info/SOURCES.txt +0 -0
- {witbitz_code-1.11.0 → witbitz_code-1.12.0}/src/witbitz_code.egg-info/dependency_links.txt +0 -0
- {witbitz_code-1.11.0 → witbitz_code-1.12.0}/src/witbitz_code.egg-info/entry_points.txt +0 -0
- {witbitz_code-1.11.0 → witbitz_code-1.12.0}/src/witbitz_code.egg-info/requires.txt +0 -0
- {witbitz_code-1.11.0 → witbitz_code-1.12.0}/src/witbitz_code.egg-info/top_level.txt +0 -0
- {witbitz_code-1.11.0 → witbitz_code-1.12.0}/tests/test_asks.py +0 -0
- {witbitz_code-1.11.0 → witbitz_code-1.12.0}/tests/test_attachments.py +0 -0
- {witbitz_code-1.11.0 → witbitz_code-1.12.0}/tests/test_auto.py +0 -0
- {witbitz_code-1.11.0 → witbitz_code-1.12.0}/tests/test_browse.py +0 -0
- {witbitz_code-1.11.0 → witbitz_code-1.12.0}/tests/test_browsetree.py +0 -0
- {witbitz_code-1.11.0 → witbitz_code-1.12.0}/tests/test_cli.py +0 -0
- {witbitz_code-1.11.0 → witbitz_code-1.12.0}/tests/test_codetools.py +0 -0
- {witbitz_code-1.11.0 → witbitz_code-1.12.0}/tests/test_confidential.py +0 -0
- {witbitz_code-1.11.0 → witbitz_code-1.12.0}/tests/test_connector_e2e.py +0 -0
- {witbitz_code-1.11.0 → witbitz_code-1.12.0}/tests/test_devicelink.py +0 -0
- {witbitz_code-1.11.0 → witbitz_code-1.12.0}/tests/test_dirs.py +0 -0
- {witbitz_code-1.11.0 → witbitz_code-1.12.0}/tests/test_files_and_compress.py +0 -0
- {witbitz_code-1.11.0 → witbitz_code-1.12.0}/tests/test_folders.py +0 -0
- {witbitz_code-1.11.0 → witbitz_code-1.12.0}/tests/test_images.py +0 -0
- {witbitz_code-1.11.0 → witbitz_code-1.12.0}/tests/test_outputs.py +0 -0
- {witbitz_code-1.11.0 → witbitz_code-1.12.0}/tests/test_pair_flows.py +0 -0
- {witbitz_code-1.11.0 → witbitz_code-1.12.0}/tests/test_plugins.py +0 -0
- {witbitz_code-1.11.0 → witbitz_code-1.12.0}/tests/test_policy.py +0 -0
- {witbitz_code-1.11.0 → witbitz_code-1.12.0}/tests/test_ports.py +0 -0
- {witbitz_code-1.11.0 → witbitz_code-1.12.0}/tests/test_ports_win.py +0 -0
- {witbitz_code-1.11.0 → witbitz_code-1.12.0}/tests/test_registry.py +0 -0
- {witbitz_code-1.11.0 → witbitz_code-1.12.0}/tests/test_relay_codec.py +0 -0
- {witbitz_code-1.11.0 → witbitz_code-1.12.0}/tests/test_remind.py +0 -0
- {witbitz_code-1.11.0 → witbitz_code-1.12.0}/tests/test_seccheck.py +0 -0
- {witbitz_code-1.11.0 → witbitz_code-1.12.0}/tests/test_selfupdate.py +0 -0
- {witbitz_code-1.11.0 → witbitz_code-1.12.0}/tests/test_setup.py +0 -0
- {witbitz_code-1.11.0 → witbitz_code-1.12.0}/tests/test_tools_probe.py +0 -0
- {witbitz_code-1.11.0 → witbitz_code-1.12.0}/tests/test_toolsetup.py +0 -0
- {witbitz_code-1.11.0 → witbitz_code-1.12.0}/tests/test_ui.py +0 -0
- {witbitz_code-1.11.0 → witbitz_code-1.12.0}/tests/test_ui_page.py +0 -0
- {witbitz_code-1.11.0 → witbitz_code-1.12.0}/tests/test_uitools.py +0 -0
- {witbitz_code-1.11.0 → witbitz_code-1.12.0}/tests/test_uninstall.py +0 -0
- {witbitz_code-1.11.0 → witbitz_code-1.12.0}/tests/test_winagent.py +0 -0
- {witbitz_code-1.11.0 → witbitz_code-1.12.0}/tests/test_winbg.py +0 -0
- {witbitz_code-1.11.0 → witbitz_code-1.12.0}/tests/test_winconsole.py +0 -0
- {witbitz_code-1.11.0 → witbitz_code-1.12.0}/tests/test_windows_files.py +0 -0
- {witbitz_code-1.11.0 → witbitz_code-1.12.0}/tests/test_wslstart.py +0 -0
|
@@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"
|
|
|
4
4
|
|
|
5
5
|
[project]
|
|
6
6
|
name = "witbitz-code"
|
|
7
|
-
version = "1.
|
|
7
|
+
version = "1.12.0"
|
|
8
8
|
description = "Reach OpenCode on this computer from the Witbitz Spaces Code section, through an end-to-end sealed relay."
|
|
9
9
|
readme = "README.md"
|
|
10
10
|
requires-python = ">=3.10"
|
|
@@ -26,6 +26,7 @@ from cryptography.hazmat.primitives.asymmetric import ec, ed25519
|
|
|
26
26
|
from cryptography.hazmat.primitives.ciphers.aead import AESGCM
|
|
27
27
|
from cryptography.hazmat.primitives.kdf.hkdf import HKDF
|
|
28
28
|
|
|
29
|
+
from . import app as app_mod
|
|
29
30
|
from . import pairings as pp
|
|
30
31
|
|
|
31
32
|
API = "https://api.witbitz.chat/v1/spaces/code-keys"
|
|
@@ -194,7 +195,7 @@ def sign_in_left_s(token: str, now: float | None = None) -> float:
|
|
|
194
195
|
|
|
195
196
|
|
|
196
197
|
async def mint(client: httpx.AsyncClient, *, id_token: str, account: str, computer_id: str, name: str,
|
|
197
|
-
ident: dict | None = None) -> dict:
|
|
198
|
+
ident: dict | None = None, app: str | None = None) -> dict:
|
|
198
199
|
"""Ask the account for this computer's keys → the entry to save. Raises CodeKeyRefusal with a sentence."""
|
|
199
200
|
if not id_token:
|
|
200
201
|
raise CodeKeyRefusal("not_signed_in", REFUSALS["not_signed_in"])
|
|
@@ -202,19 +203,28 @@ async def mint(client: httpx.AsyncClient, *, id_token: str, account: str, comput
|
|
|
202
203
|
raise CodeKeyRefusal("signin_stale", REFUSALS["signin_stale"])
|
|
203
204
|
ident = ident or new_identity()
|
|
204
205
|
try:
|
|
206
|
+
# `app`: the Spaces app this pairing belongs to — each app keeps its own computers and allowance (the owner,
|
|
207
|
+
# 2026-10-23: "they should be completely separate"); the server files the keys under that app's tenant.
|
|
205
208
|
r = await client.post(api_base(), json={"idToken": id_token, "computerId": computer_id, "name": name,
|
|
206
|
-
"sealPub": seal_pub(ident), "signPub": sign_pub(ident)}, timeout=30)
|
|
209
|
+
"sealPub": seal_pub(ident), "signPub": sign_pub(ident), "app": app or app_mod.name()}, timeout=30)
|
|
207
210
|
except httpx.HTTPError as e:
|
|
208
211
|
raise CodeKeyRefusal("unreachable", f"could not reach Witbitz to get this computer's keys ({str(e)[:80]})") from e
|
|
209
212
|
if r.status_code != 200:
|
|
210
213
|
raise _refusal(r, account)
|
|
211
214
|
d = (r.json() or {}).get("data") or {}
|
|
212
|
-
entry = {"account": account, "computerId": computer_id, "name": name, "
|
|
213
|
-
|
|
215
|
+
entry = {"account": account, "computerId": computer_id, "name": name, **ident, "app": app or app_mod.name(), "at": int(time.time() * 1000)}
|
|
216
|
+
return _take(entry, d)
|
|
217
|
+
|
|
218
|
+
|
|
219
|
+
def _take(entry: dict, d: dict) -> dict:
|
|
220
|
+
"""A minted pair (a pairing, or a move to the computer's own app) into `entry`: its ids, caps and keys opened with the
|
|
221
|
+
computer's own identity. Raises CodeKeyRefusal when they cannot be opened."""
|
|
222
|
+
entry.update({"keyId": d.get("keyId"), "tenantId": d.get("tenantId"), "userId": d.get("userId"), "kid": d.get("kid"), "caps": d.get("caps") or {}})
|
|
223
|
+
entry.pop("tinfoil", None)
|
|
214
224
|
sealed = d.get("sealed") or {}
|
|
215
225
|
for provider in PROVIDERS:
|
|
216
226
|
if sealed.get(provider):
|
|
217
|
-
secret = open_sealed(sealed[provider],
|
|
227
|
+
secret = open_sealed(sealed[provider], entry, aad(entry["tenantId"], entry["userId"], entry["kid"], provider))
|
|
218
228
|
if secret is None:
|
|
219
229
|
raise CodeKeyRefusal("unsealable", "the keys Witbitz sent could not be opened on this computer — pair again")
|
|
220
230
|
entry[provider] = secret
|
|
@@ -223,6 +233,18 @@ async def mint(client: httpx.AsyncClient, *, id_token: str, account: str, comput
|
|
|
223
233
|
return entry
|
|
224
234
|
|
|
225
235
|
|
|
236
|
+
def app_of(entry: dict) -> str:
|
|
237
|
+
"""The Spaces app these keys are for: recorded at the mint since 2026-10-23; before that, the app of the pairing they
|
|
238
|
+
belong to (its `origin`), else this install's."""
|
|
239
|
+
if entry.get("app") in (app_mod.REGULAR, app_mod.VAULTED):
|
|
240
|
+
return entry["app"]
|
|
241
|
+
try:
|
|
242
|
+
p = next((x for x in pp.read_pairings()["pairings"] if x.get("computerId") == entry.get("computerId")), None)
|
|
243
|
+
except Exception: # noqa: BLE001 — a reading; the install's own app is the answer then
|
|
244
|
+
p = None
|
|
245
|
+
return app_mod.of_origin(p.get("origin")) if p and p.get("origin") else app_mod.name()
|
|
246
|
+
|
|
247
|
+
|
|
226
248
|
def _server_time(r: httpx.Response) -> float | None:
|
|
227
249
|
try:
|
|
228
250
|
return parsedate_to_datetime(r.headers.get("date") or "").timestamp()
|
|
@@ -230,14 +252,25 @@ def _server_time(r: httpx.Response) -> float | None:
|
|
|
230
252
|
return None
|
|
231
253
|
|
|
232
254
|
|
|
255
|
+
def _status_body(entry: dict) -> dict:
|
|
256
|
+
"""The status call, naming the app these keys are for and signing a move there ('move:<app>'): a computer the server has
|
|
257
|
+
under the other app's tenant moves to its own (every computer paired before 2026-10-23 was under the hosted app's)."""
|
|
258
|
+
b = signed_body("status", entry)
|
|
259
|
+
a = app_of(entry)
|
|
260
|
+
b["app"] = a
|
|
261
|
+
b["moveSig"] = _b64u(_sign_key(entry).sign(signed_message("move:" + a, entry["keyId"], b["ts"]).encode()))
|
|
262
|
+
return b
|
|
263
|
+
|
|
264
|
+
|
|
233
265
|
async def _signed_post(client: httpx.AsyncClient, path: str, op: str, entry: dict, timeout: float = 10) -> httpx.Response:
|
|
234
266
|
"""A signed call; refused while this computer's clock is off the server's, it is signed again to the server's time."""
|
|
235
|
-
|
|
267
|
+
body = (lambda: _status_body(entry)) if op == "status" else (lambda: signed_body(op, entry))
|
|
268
|
+
r = await client.post(api_base() + path, json=body(), timeout=timeout)
|
|
236
269
|
if r.status_code == 401:
|
|
237
270
|
server = _server_time(r)
|
|
238
271
|
if server is not None and abs(server - _now()) > SKEW_FIX_S:
|
|
239
272
|
_clock["skew"] = server - time.time()
|
|
240
|
-
r = await client.post(api_base() + path, json=
|
|
273
|
+
r = await client.post(api_base() + path, json=body(), timeout=timeout)
|
|
241
274
|
return r
|
|
242
275
|
|
|
243
276
|
|
|
@@ -259,6 +292,19 @@ async def status(client: httpx.AsyncClient, entry: dict) -> dict:
|
|
|
259
292
|
if r.status_code != 200:
|
|
260
293
|
return {"live": None, "why": _why(r)} # unknown — keep going as before, and say why
|
|
261
294
|
d = (r.json() or {}).get("data") or {}
|
|
295
|
+
moved = d.get("moved")
|
|
296
|
+
if isinstance(moved, dict) and moved.get("keyId"):
|
|
297
|
+
# MOVED TO ITS OWN APP: the server retired the old pair, so the new one is taken and SAVED here, whoever asked
|
|
298
|
+
# (serve's check, the page, `pair --status`) — keys left unsaved would be keys this computer no longer has.
|
|
299
|
+
try:
|
|
300
|
+
_take(entry, moved)
|
|
301
|
+
entry["app"] = app_of(entry)
|
|
302
|
+
save(entry)
|
|
303
|
+
d["changed"] = False # saved already
|
|
304
|
+
d["movedTo"] = entry["app"]
|
|
305
|
+
except CodeKeyRefusal as e:
|
|
306
|
+
return {"live": None, "why": str(e)}
|
|
307
|
+
return d
|
|
262
308
|
tf = (d.get("sealed") or {}).get("tinfoil")
|
|
263
309
|
if tf:
|
|
264
310
|
secret = open_sealed(tf, entry, aad(entry["tenantId"], entry["userId"], entry["kid"], "tinfoil"))
|
|
@@ -354,6 +400,8 @@ class LiveKeys:
|
|
|
354
400
|
say(f'witbitz-code: coding for {e.get("account")} is paused by Witbitz')
|
|
355
401
|
self.refuse[cid] = PAUSED_TEXT
|
|
356
402
|
elif st.get("live") is True:
|
|
403
|
+
if st.get("movedTo"):
|
|
404
|
+
say(f'witbitz-code: "{e.get("name")}" → {e.get("account")} now codes under the {app_mod.label(st["movedTo"])}\'s own allowance')
|
|
357
405
|
if self.refuse.pop(cid, None):
|
|
358
406
|
say(f'witbitz-code: coding for {e.get("account")} is back on')
|
|
359
407
|
if st.get("changed"):
|
|
@@ -349,3 +349,38 @@ def test_the_python_and_node_connectors_read_each_others_keys_and_payers_files()
|
|
|
349
349
|
pub.verify(codekeys._unb64u(out["body"]["sig"]), codekeys.signed_message("status", e["keyId"], 1800000000).encode()) # raises if not
|
|
350
350
|
assert codekeys.for_computer("cmp_js")["trustedrouter"] == "sk-js" and SessionPayers().payer("ses_J") == "cmp_js"
|
|
351
351
|
assert codekeys.signed_body("status", codekeys.for_computer("cmp_js"))["sig"], "Python signs with a Node-made key"
|
|
352
|
+
|
|
353
|
+
|
|
354
|
+
@requires_node
|
|
355
|
+
def test_a_status_that_names_its_app_moves_the_keys_there_and_saves_them(monkeypatch):
|
|
356
|
+
"""The owner, 2026-10-23: each app its own computers and allowance. The status names this pairing's app and signs the
|
|
357
|
+
move ('move:<app>'); the server answers with a fresh pair under that app, which is opened and SAVED at once (the old one
|
|
358
|
+
is retired server-side, so unsaved keys would be lost)."""
|
|
359
|
+
from cryptography.hazmat.primitives.asymmetric import ed25519 as ed
|
|
360
|
+
T2, U2, K2 = "t_" + "c" * 32, "u_" + "d" * 24, "k_" + "2" * 24
|
|
361
|
+
ident = codekeys.new_identity()
|
|
362
|
+
entry = {**ident, "account": "me@x", "computerId": "cmp_1", "name": "desk", "keyId": f"{U}.{K}", "tenantId": T, "userId": U,
|
|
363
|
+
"kid": K, "trustedrouter": "sk-tr-old", "app": "regular"}
|
|
364
|
+
codekeys.save(entry)
|
|
365
|
+
sealed = run_node("import { sealForEnclave } from '@REPO@/control-plane/src/lib/enclaveKeySeal.mjs';"
|
|
366
|
+
"OUT({ trustedrouter: await sealForEnclave('sk-tr-new', { tenantId: INPUT.t, userId: INPUT.u, kind: `code:${INPUT.k}:trustedrouter` }, INPUT.pub) })"
|
|
367
|
+
.replace("@REPO@", (__import__("conftest").REPO).as_uri()),
|
|
368
|
+
{"t": T2, "u": U2, "k": K2, "pub": codekeys.seal_pub(ident)})
|
|
369
|
+
seen = []
|
|
370
|
+
|
|
371
|
+
def handler(req):
|
|
372
|
+
b = json.loads(req.content)
|
|
373
|
+
seen.append(b)
|
|
374
|
+
pub = ed.Ed25519PublicKey.from_public_bytes(base64.urlsafe_b64decode(codekeys.sign_pub(ident) + "="))
|
|
375
|
+
pub.verify(base64.urlsafe_b64decode(b["moveSig"] + "=" * (-len(b["moveSig"]) % 4)), codekeys.signed_message("move:regular", b["keyId"], b["ts"]).encode())
|
|
376
|
+
return httpx.Response(200, json={"data": {"live": True, "moved": {"keyId": f"{U2}.{K2}", "tenantId": T2, "userId": U2, "kid": K2, "sealed": sealed, "caps": {"modelsUsd": 50}}}})
|
|
377
|
+
|
|
378
|
+
async def go():
|
|
379
|
+
async with client_for(handler) as c:
|
|
380
|
+
return await codekeys.status(c, entry)
|
|
381
|
+
st = run(go())
|
|
382
|
+
assert seen[0]["app"] == "regular"
|
|
383
|
+
assert st["live"] is True and st["movedTo"] == "regular"
|
|
384
|
+
saved = codekeys.for_computer("cmp_1")
|
|
385
|
+
assert (saved["keyId"], saved["tenantId"], saved["trustedrouter"]) == (f"{U2}.{K2}", T2, "sk-tr-new")
|
|
386
|
+
assert entry["trustedrouter"] == "sk-tr-new", "the running serve's entry follows too"
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
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.11.0 → witbitz_code-1.12.0}/src/witbitz_code/opencode_plugins/witbitz-browse-cdp.mjs
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{witbitz_code-1.11.0 → witbitz_code-1.12.0}/src/witbitz_code/opencode_plugins/witbitz-browser.js
RENAMED
|
File without changes
|
|
File without changes
|
{witbitz_code-1.11.0 → witbitz_code-1.12.0}/src/witbitz_code/opencode_plugins/witbitz-notes.js
RENAMED
|
File without changes
|
{witbitz_code-1.11.0 → witbitz_code-1.12.0}/src/witbitz_code/opencode_plugins/witbitz-progress.js
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|