witbitz-code 1.6.5__tar.gz → 1.6.7__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.5/src/witbitz_code.egg-info → witbitz_code-1.6.7}/PKG-INFO +2 -2
- {witbitz_code-1.6.5 → witbitz_code-1.6.7}/README.md +1 -1
- {witbitz_code-1.6.5 → witbitz_code-1.6.7}/pyproject.toml +1 -1
- {witbitz_code-1.6.5 → witbitz_code-1.6.7}/src/witbitz_code/__init__.py +1 -1
- {witbitz_code-1.6.5 → witbitz_code-1.6.7}/src/witbitz_code/attachments.py +5 -2
- {witbitz_code-1.6.5 → witbitz_code-1.6.7}/src/witbitz_code/cli.py +1 -1
- {witbitz_code-1.6.5 → witbitz_code-1.6.7}/src/witbitz_code/keys.py +1 -1
- {witbitz_code-1.6.5 → witbitz_code-1.6.7}/src/witbitz_code/link.py +2 -2
- {witbitz_code-1.6.5 → witbitz_code-1.6.7}/src/witbitz_code/mkdir.py +1 -1
- {witbitz_code-1.6.5 → witbitz_code-1.6.7}/src/witbitz_code/pair.py +1 -1
- {witbitz_code-1.6.5 → witbitz_code-1.6.7}/src/witbitz_code/pairings.py +13 -1
- {witbitz_code-1.6.5 → witbitz_code-1.6.7}/src/witbitz_code/ui_static/index.html +1 -1
- {witbitz_code-1.6.5 → witbitz_code-1.6.7}/src/witbitz_code/wizard.py +1 -1
- {witbitz_code-1.6.5 → witbitz_code-1.6.7/src/witbitz_code.egg-info}/PKG-INFO +2 -2
- {witbitz_code-1.6.5 → witbitz_code-1.6.7}/tests/test_windows_files.py +18 -0
- {witbitz_code-1.6.5 → witbitz_code-1.6.7}/tests/test_wslstart.py +2 -1
- {witbitz_code-1.6.5 → witbitz_code-1.6.7}/LICENSE +0 -0
- {witbitz_code-1.6.5 → witbitz_code-1.6.7}/setup.cfg +0 -0
- {witbitz_code-1.6.5 → witbitz_code-1.6.7}/src/witbitz_code/__main__.py +0 -0
- {witbitz_code-1.6.5 → witbitz_code-1.6.7}/src/witbitz_code/_js.py +0 -0
- {witbitz_code-1.6.5 → witbitz_code-1.6.7}/src/witbitz_code/account.py +0 -0
- {witbitz_code-1.6.5 → witbitz_code-1.6.7}/src/witbitz_code/asks.py +0 -0
- {witbitz_code-1.6.5 → witbitz_code-1.6.7}/src/witbitz_code/auto.py +0 -0
- {witbitz_code-1.6.5 → witbitz_code-1.6.7}/src/witbitz_code/auto_runner.py +0 -0
- {witbitz_code-1.6.5 → witbitz_code-1.6.7}/src/witbitz_code/codekeys.py +0 -0
- {witbitz_code-1.6.5 → witbitz_code-1.6.7}/src/witbitz_code/codetools.py +0 -0
- {witbitz_code-1.6.5 → witbitz_code-1.6.7}/src/witbitz_code/compress.py +0 -0
- {witbitz_code-1.6.5 → witbitz_code-1.6.7}/src/witbitz_code/computers.py +0 -0
- {witbitz_code-1.6.5 → witbitz_code-1.6.7}/src/witbitz_code/confidential.py +0 -0
- {witbitz_code-1.6.5 → witbitz_code-1.6.7}/src/witbitz_code/confidential_models.json +0 -0
- {witbitz_code-1.6.5 → witbitz_code-1.6.7}/src/witbitz_code/connector.py +0 -0
- {witbitz_code-1.6.5 → witbitz_code-1.6.7}/src/witbitz_code/devicelink.py +0 -0
- {witbitz_code-1.6.5 → witbitz_code-1.6.7}/src/witbitz_code/dirs.py +0 -0
- {witbitz_code-1.6.5 → witbitz_code-1.6.7}/src/witbitz_code/folders.py +0 -0
- {witbitz_code-1.6.5 → witbitz_code-1.6.7}/src/witbitz_code/gateway.py +0 -0
- {witbitz_code-1.6.5 → witbitz_code-1.6.7}/src/witbitz_code/icons/witbitz-code-wsl.ico +0 -0
- {witbitz_code-1.6.5 → witbitz_code-1.6.7}/src/witbitz_code/icons/witbitz-code.ico +0 -0
- {witbitz_code-1.6.5 → witbitz_code-1.6.7}/src/witbitz_code/models.py +0 -0
- {witbitz_code-1.6.5 → witbitz_code-1.6.7}/src/witbitz_code/net.py +0 -0
- {witbitz_code-1.6.5 → witbitz_code-1.6.7}/src/witbitz_code/offered_models.json +0 -0
- {witbitz_code-1.6.5 → witbitz_code-1.6.7}/src/witbitz_code/opencode_plugins/notes-init.md +0 -0
- {witbitz_code-1.6.5 → witbitz_code-1.6.7}/src/witbitz_code/opencode_plugins/witbitz-media-server.mjs +0 -0
- {witbitz_code-1.6.5 → witbitz_code-1.6.7}/src/witbitz_code/opencode_plugins/witbitz-notes.js +0 -0
- {witbitz_code-1.6.5 → witbitz_code-1.6.7}/src/witbitz_code/opencode_plugins/witbitz-progress.js +0 -0
- {witbitz_code-1.6.5 → witbitz_code-1.6.7}/src/witbitz_code/outputs.py +0 -0
- {witbitz_code-1.6.5 → witbitz_code-1.6.7}/src/witbitz_code/payers.py +0 -0
- {witbitz_code-1.6.5 → witbitz_code-1.6.7}/src/witbitz_code/plugins.py +0 -0
- {witbitz_code-1.6.5 → witbitz_code-1.6.7}/src/witbitz_code/policy.py +0 -0
- {witbitz_code-1.6.5 → witbitz_code-1.6.7}/src/witbitz_code/ports.py +0 -0
- {witbitz_code-1.6.5 → witbitz_code-1.6.7}/src/witbitz_code/qr.py +0 -0
- {witbitz_code-1.6.5 → witbitz_code-1.6.7}/src/witbitz_code/receipt.py +0 -0
- {witbitz_code-1.6.5 → witbitz_code-1.6.7}/src/witbitz_code/relay.py +0 -0
- {witbitz_code-1.6.5 → witbitz_code-1.6.7}/src/witbitz_code/seen.py +0 -0
- {witbitz_code-1.6.5 → witbitz_code-1.6.7}/src/witbitz_code/tinfoil_read.py +0 -0
- {witbitz_code-1.6.5 → witbitz_code-1.6.7}/src/witbitz_code/tools_catalog.json +0 -0
- {witbitz_code-1.6.5 → witbitz_code-1.6.7}/src/witbitz_code/tools_probe.py +0 -0
- {witbitz_code-1.6.5 → witbitz_code-1.6.7}/src/witbitz_code/toolsetup.py +0 -0
- {witbitz_code-1.6.5 → witbitz_code-1.6.7}/src/witbitz_code/ui.py +0 -0
- {witbitz_code-1.6.5 → witbitz_code-1.6.7}/src/witbitz_code/ui_static/app.css +0 -0
- {witbitz_code-1.6.5 → witbitz_code-1.6.7}/src/witbitz_code/ui_static/app.js +0 -0
- {witbitz_code-1.6.5 → witbitz_code-1.6.7}/src/witbitz_code/uitools.py +0 -0
- {witbitz_code-1.6.5 → witbitz_code-1.6.7}/src/witbitz_code/uninstall.py +0 -0
- {witbitz_code-1.6.5 → witbitz_code-1.6.7}/src/witbitz_code/winagent.py +0 -0
- {witbitz_code-1.6.5 → witbitz_code-1.6.7}/src/witbitz_code/winbg.py +0 -0
- {witbitz_code-1.6.5 → witbitz_code-1.6.7}/src/witbitz_code/winconsole.py +0 -0
- {witbitz_code-1.6.5 → witbitz_code-1.6.7}/src/witbitz_code/wslstart.py +0 -0
- {witbitz_code-1.6.5 → witbitz_code-1.6.7}/src/witbitz_code.egg-info/SOURCES.txt +0 -0
- {witbitz_code-1.6.5 → witbitz_code-1.6.7}/src/witbitz_code.egg-info/dependency_links.txt +0 -0
- {witbitz_code-1.6.5 → witbitz_code-1.6.7}/src/witbitz_code.egg-info/entry_points.txt +0 -0
- {witbitz_code-1.6.5 → witbitz_code-1.6.7}/src/witbitz_code.egg-info/requires.txt +0 -0
- {witbitz_code-1.6.5 → witbitz_code-1.6.7}/src/witbitz_code.egg-info/top_level.txt +0 -0
- {witbitz_code-1.6.5 → witbitz_code-1.6.7}/tests/test_asks.py +0 -0
- {witbitz_code-1.6.5 → witbitz_code-1.6.7}/tests/test_attachments.py +0 -0
- {witbitz_code-1.6.5 → witbitz_code-1.6.7}/tests/test_auto.py +0 -0
- {witbitz_code-1.6.5 → witbitz_code-1.6.7}/tests/test_cli.py +0 -0
- {witbitz_code-1.6.5 → witbitz_code-1.6.7}/tests/test_codekeys.py +0 -0
- {witbitz_code-1.6.5 → witbitz_code-1.6.7}/tests/test_codetools.py +0 -0
- {witbitz_code-1.6.5 → witbitz_code-1.6.7}/tests/test_confidential.py +0 -0
- {witbitz_code-1.6.5 → witbitz_code-1.6.7}/tests/test_connector_e2e.py +0 -0
- {witbitz_code-1.6.5 → witbitz_code-1.6.7}/tests/test_devicelink.py +0 -0
- {witbitz_code-1.6.5 → witbitz_code-1.6.7}/tests/test_dirs.py +0 -0
- {witbitz_code-1.6.5 → witbitz_code-1.6.7}/tests/test_files_and_compress.py +0 -0
- {witbitz_code-1.6.5 → witbitz_code-1.6.7}/tests/test_folders.py +0 -0
- {witbitz_code-1.6.5 → witbitz_code-1.6.7}/tests/test_outputs.py +0 -0
- {witbitz_code-1.6.5 → witbitz_code-1.6.7}/tests/test_pair_flows.py +0 -0
- {witbitz_code-1.6.5 → witbitz_code-1.6.7}/tests/test_plugins.py +0 -0
- {witbitz_code-1.6.5 → witbitz_code-1.6.7}/tests/test_policy.py +0 -0
- {witbitz_code-1.6.5 → witbitz_code-1.6.7}/tests/test_ports.py +0 -0
- {witbitz_code-1.6.5 → witbitz_code-1.6.7}/tests/test_ports_win.py +0 -0
- {witbitz_code-1.6.5 → witbitz_code-1.6.7}/tests/test_registry.py +0 -0
- {witbitz_code-1.6.5 → witbitz_code-1.6.7}/tests/test_relay_codec.py +0 -0
- {witbitz_code-1.6.5 → witbitz_code-1.6.7}/tests/test_setup.py +0 -0
- {witbitz_code-1.6.5 → witbitz_code-1.6.7}/tests/test_tools_probe.py +0 -0
- {witbitz_code-1.6.5 → witbitz_code-1.6.7}/tests/test_toolsetup.py +0 -0
- {witbitz_code-1.6.5 → witbitz_code-1.6.7}/tests/test_ui.py +0 -0
- {witbitz_code-1.6.5 → witbitz_code-1.6.7}/tests/test_uitools.py +0 -0
- {witbitz_code-1.6.5 → witbitz_code-1.6.7}/tests/test_uninstall.py +0 -0
- {witbitz_code-1.6.5 → witbitz_code-1.6.7}/tests/test_winagent.py +0 -0
- {witbitz_code-1.6.5 → witbitz_code-1.6.7}/tests/test_winbg.py +0 -0
- {witbitz_code-1.6.5 → witbitz_code-1.6.7}/tests/test_winconsole.py +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: witbitz-code
|
|
3
|
-
Version: 1.6.
|
|
3
|
+
Version: 1.6.7
|
|
4
4
|
Summary: Reach OpenCode on this computer from the Witbitz Spaces Code section, through an end-to-end sealed relay.
|
|
5
5
|
Author: Witbitz
|
|
6
6
|
License-Expression: MIT
|
|
@@ -50,7 +50,7 @@ witbitz-code setup
|
|
|
50
50
|
```
|
|
51
51
|
|
|
52
52
|
`setup` walks five steps and skips whatever is already done: it installs OpenCode if it is missing (it asks first), pairs
|
|
53
|
-
this computer (scan the QR code in Spaces → Settings → Back up & recovery → Add a device), which gives the computer **its
|
|
53
|
+
this computer (scan the QR code in Spaces → Settings → Privacy & security → Back up & recovery → Add a device — scan its code), which gives the computer **its
|
|
54
54
|
own keys from your Spaces account**, offers to install the tools the agent uses for audio, video, PDFs and scans (ffmpeg,
|
|
55
55
|
Poppler, Tesseract, … — with winget, Homebrew or one `sudo apt-get` line; it shows what it will run and asks once), then
|
|
56
56
|
starts `serve` in the window. Then open Code in Spaces. Leave it running (Ctrl-C
|
|
@@ -24,7 +24,7 @@ witbitz-code setup
|
|
|
24
24
|
```
|
|
25
25
|
|
|
26
26
|
`setup` walks five steps and skips whatever is already done: it installs OpenCode if it is missing (it asks first), pairs
|
|
27
|
-
this computer (scan the QR code in Spaces → Settings → Back up & recovery → Add a device), which gives the computer **its
|
|
27
|
+
this computer (scan the QR code in Spaces → Settings → Privacy & security → Back up & recovery → Add a device — scan its code), which gives the computer **its
|
|
28
28
|
own keys from your Spaces account**, offers to install the tools the agent uses for audio, video, PDFs and scans (ffmpeg,
|
|
29
29
|
Poppler, Tesseract, … — with winget, Homebrew or one `sudo apt-get` line; it shows what it will run and asks once), then
|
|
30
30
|
starts `serve` in the window. Then open Code in Spaces. Leave it running (Ctrl-C
|
|
@@ -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.7"
|
|
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"
|
|
@@ -15,12 +15,14 @@ import hashlib
|
|
|
15
15
|
import os
|
|
16
16
|
import re
|
|
17
17
|
import shutil
|
|
18
|
+
import sys
|
|
18
19
|
import time
|
|
19
20
|
from decimal import ROUND_HALF_UP, Decimal
|
|
20
21
|
from pathlib import Path
|
|
21
22
|
from typing import Any
|
|
22
23
|
|
|
23
24
|
from . import _js
|
|
25
|
+
from . import pairings as pp
|
|
24
26
|
|
|
25
27
|
NOTE_HEAD = "The user attached files. They are saved on this computer — open them with the read tool."
|
|
26
28
|
ATTACHMENT_ROUTE = "/witbitz/attachment"
|
|
@@ -178,8 +180,9 @@ def stage_message_body(body: Any, *, session_id: Any, root: Path, max_file_bytes
|
|
|
178
180
|
d = Path(root) / session_id
|
|
179
181
|
if Path(root).is_symlink() or d.is_symlink():
|
|
180
182
|
return {"error": {"status": 400, "message": "the attachments folder is a link — refusing to write through it"}}
|
|
181
|
-
|
|
182
|
-
|
|
183
|
+
pp.private_mkdir(d)
|
|
184
|
+
if sys.platform != "win32":
|
|
185
|
+
os.chmod(d, 0o700)
|
|
183
186
|
used = _folder_bytes(d)
|
|
184
187
|
total = _root_bytes(root)
|
|
185
188
|
entries: list[dict] = []
|
|
@@ -40,7 +40,7 @@ HELP = f"""witbitz-code {__version__} — reach OpenCode on this computer from t
|
|
|
40
40
|
the same in a page in your browser: status, pair / unpair / pair again, start and stop,
|
|
41
41
|
start when I log in (Windows and WSL); it serves while it runs. --background (Windows):
|
|
42
42
|
no window — the page's Quit stops it
|
|
43
|
-
pair [--name <name>] show a QR code; scan it in Spaces (Settings →
|
|
43
|
+
pair [--name <name>] show a QR code; scan it in Spaces (Settings → Privacy & security → Add a device)
|
|
44
44
|
serve [--port <n>] [--no-opencode]
|
|
45
45
|
start OpenCode on 127.0.0.1 with your account's keys, and the connector
|
|
46
46
|
status list this computer's pairings and their coding keys
|
|
@@ -119,7 +119,7 @@ def has_trustedrouter(env: dict | None = None) -> bool:
|
|
|
119
119
|
|
|
120
120
|
def save_trustedrouter_key(key: str) -> Path:
|
|
121
121
|
p = auth_path()
|
|
122
|
-
p.parent
|
|
122
|
+
pp.private_mkdir(p.parent)
|
|
123
123
|
text = p.read_text(encoding="utf-8") if p.exists() else ""
|
|
124
124
|
pp.write_private(p, with_auth_key(text, "trustedrouter", key))
|
|
125
125
|
return p
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
"""Make this computer an ADDED DEVICE of a Spaces account by device-link — tools/rc-link.mjs.
|
|
2
2
|
|
|
3
|
-
It shows a QR carrying an ephemeral PUBLIC key (no secret); a signed-in phone or desktop scans it (Settings →
|
|
4
|
-
|
|
3
|
+
It shows a QR carrying an ephemeral PUBLIC key (no secret); a signed-in phone or desktop scans it (Settings → Privacy & security →
|
|
4
|
+
Add a device) and seals the account to that key; this process opens the reply. The reply is relayed
|
|
5
5
|
content-blind through /api/link between two public keys the server cannot combine.
|
|
6
6
|
|
|
7
7
|
Unlike rc-link.mjs this never writes the account to disk: the pairing flow needs it only long enough to publish the
|
|
@@ -47,7 +47,7 @@ def mkdir_in(*, root: object, rel: object, name: object) -> tuple[int, str]:
|
|
|
47
47
|
return _error(403, "that lies outside the root")
|
|
48
48
|
target = P.join(real_parent, str(name))
|
|
49
49
|
try:
|
|
50
|
-
os.mkdir(target, 0o700)
|
|
50
|
+
os.mkdir(target) if win else os.mkdir(target, 0o700) # Windows: inherit — 0o700 makes an owner-only access list (pairings.private_mkdir)
|
|
51
51
|
except FileExistsError:
|
|
52
52
|
return _error(409, "already there")
|
|
53
53
|
except OSError as e:
|
|
@@ -112,7 +112,7 @@ async def _publish_entry(call: net.ApiCall, e: dict, out: Callable, err: Callabl
|
|
|
112
112
|
async def _link_by_qr(link_fn: LinkFn | None, out: Callable, err: Callable) -> dict | None:
|
|
113
113
|
"""The QR ritual: show the Add-a-device QR and wait for a signed-in device to seal the account to this computer. The
|
|
114
114
|
account stays in memory; an expired or failed scan changes nothing."""
|
|
115
|
-
out("witbitz-code: on your phone open Spaces → Settings →
|
|
115
|
+
out("witbitz-code: on your phone open Spaces → Settings → Privacy & security → Add a device, and scan the QR below "
|
|
116
116
|
"with the account to pair.\n")
|
|
117
117
|
account = await (link_fn() if link_fn else link.link_node())
|
|
118
118
|
if not account:
|
|
@@ -165,10 +165,22 @@ def read_pairings(path: Path | None = None) -> dict:
|
|
|
165
165
|
return norm_pairings(None)
|
|
166
166
|
|
|
167
167
|
|
|
168
|
+
def private_mkdir(path: Path, mode: int = 0o700, *, parents: bool = True) -> None:
|
|
169
|
+
"""A folder only this user reads — 0700 off Windows. On Windows NO mode: Python 3.12.4+ turns 0o700 into an access list of
|
|
170
|
+
SYSTEM, Administrators and the folder's OWNER, and a folder made from an Administrator window is owned by Administrators —
|
|
171
|
+
the person's own non-admin windows could then not open it (the owner's second desktop, 2026-10-09: uninstall died
|
|
172
|
+
"[WinError 5] Access is denied: …\\.witbitz\\code\\pairings.json"). The folder inherits the user profile's access instead,
|
|
173
|
+
which is already the person's alone."""
|
|
174
|
+
if sys.platform == "win32":
|
|
175
|
+
path.mkdir(parents=parents, exist_ok=True)
|
|
176
|
+
else:
|
|
177
|
+
path.mkdir(parents=parents, exist_ok=True, mode=mode)
|
|
178
|
+
|
|
179
|
+
|
|
168
180
|
def write_private(path: Path, text: str, dir_mode: int = 0o700) -> None:
|
|
169
181
|
"""Write a 0600 file atomically: the secret is never readable by others, not even for the moment between write and
|
|
170
182
|
chmod, and a crash never leaves half a file."""
|
|
171
|
-
path.parent
|
|
183
|
+
private_mkdir(path.parent, dir_mode)
|
|
172
184
|
fd, tmp = tempfile.mkstemp(dir=path.parent, prefix=f".{path.name}.")
|
|
173
185
|
try:
|
|
174
186
|
with os.fdopen(fd, "w", encoding="utf-8", newline="") as f:
|
|
@@ -33,7 +33,7 @@
|
|
|
33
33
|
<section class="card" id="pairCard" hidden>
|
|
34
34
|
<h2>Pair with your Spaces account</h2>
|
|
35
35
|
<ol class="steps">
|
|
36
|
-
<li>On your phone, open <b>Spaces → Settings →
|
|
36
|
+
<li>On your phone, open <b>Spaces → Settings → Privacy & security → Add a device</b>.</li>
|
|
37
37
|
<li>Scan this code with the account that should reach this computer.</li>
|
|
38
38
|
</ol>
|
|
39
39
|
<div class="qr-wrap"><img id="qr" alt="Pairing code — scan it in Spaces" width="260" height="260"></div>
|
|
@@ -173,7 +173,7 @@ async def run_setup(d: Deps) -> dict:
|
|
|
173
173
|
d.say(f" {named(gone)} was removed from your Witbitz account, so this computer pairs again.")
|
|
174
174
|
mine = []
|
|
175
175
|
if not mine:
|
|
176
|
-
d.say(" On your phone, open Spaces → Settings →
|
|
176
|
+
d.say(" On your phone, open Spaces → Settings → Privacy & security → Add a device, and scan the code below.\n")
|
|
177
177
|
await d.pair(port)
|
|
178
178
|
everything = d.all_pairings()
|
|
179
179
|
mine = on(everything, port)
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: witbitz-code
|
|
3
|
-
Version: 1.6.
|
|
3
|
+
Version: 1.6.7
|
|
4
4
|
Summary: Reach OpenCode on this computer from the Witbitz Spaces Code section, through an end-to-end sealed relay.
|
|
5
5
|
Author: Witbitz
|
|
6
6
|
License-Expression: MIT
|
|
@@ -50,7 +50,7 @@ witbitz-code setup
|
|
|
50
50
|
```
|
|
51
51
|
|
|
52
52
|
`setup` walks five steps and skips whatever is already done: it installs OpenCode if it is missing (it asks first), pairs
|
|
53
|
-
this computer (scan the QR code in Spaces → Settings → Back up & recovery → Add a device), which gives the computer **its
|
|
53
|
+
this computer (scan the QR code in Spaces → Settings → Privacy & security → Back up & recovery → Add a device — scan its code), which gives the computer **its
|
|
54
54
|
own keys from your Spaces account**, offers to install the tools the agent uses for audio, video, PDFs and scans (ffmpeg,
|
|
55
55
|
Poppler, Tesseract, … — with winget, Homebrew or one `sudo apt-get` line; it shows what it will run and asks once), then
|
|
56
56
|
starts `serve` in the window. Then open Code in Spaces. Leave it running (Ctrl-C
|
|
@@ -26,3 +26,21 @@ def test_the_keys_and_payers_files_are_written_where_python_has_no_fchmod(monkey
|
|
|
26
26
|
def test_no_module_calls_fchmod():
|
|
27
27
|
hits = [f.name for f in SRC.glob("*.py") if "os.fchmod(" in f.read_text(encoding="utf-8")]
|
|
28
28
|
assert hits == [], f"os.fchmod does not exist on Windows Python before 3.13 — use pairings.write_private: {hits}"
|
|
29
|
+
|
|
30
|
+
|
|
31
|
+
def test_on_windows_private_folders_inherit_instead_of_an_owner_only_access_list(monkeypatch, tmp_path):
|
|
32
|
+
"""Python 3.12.4+ makes mkdir(mode=0o700) an access list of SYSTEM, Administrators and the OWNER; made from an
|
|
33
|
+
Administrator window the owner is Administrators and the person's own windows are locked out (the owner's second
|
|
34
|
+
desktop, 2026-10-09: "[WinError 5] Access is denied: …\\.witbitz\\code\\pairings.json")."""
|
|
35
|
+
import pathlib
|
|
36
|
+
from witbitz_code import pairings
|
|
37
|
+
seen = []
|
|
38
|
+
real = pathlib.Path.mkdir
|
|
39
|
+
monkeypatch.setattr(pathlib.Path, "mkdir", lambda self, *a, **k: seen.append(k.get("mode", a[0] if a else None)) or real(self, parents=True, exist_ok=True))
|
|
40
|
+
monkeypatch.setattr(pairings.sys, "platform", "win32")
|
|
41
|
+
pairings.write_private(tmp_path / "w" / "pairings.json", "{}")
|
|
42
|
+
assert seen and all(m is None for m in seen), "no mode on Windows: the folder inherits the profile's access"
|
|
43
|
+
seen.clear()
|
|
44
|
+
monkeypatch.setattr(pairings.sys, "platform", "linux")
|
|
45
|
+
pairings.write_private(tmp_path / "l" / "pairings.json", "{}")
|
|
46
|
+
assert seen == [0o700], "0700 everywhere else, as before"
|
|
@@ -53,7 +53,8 @@ def test_the_windows_login_start_runs_pythonw_in_the_background_when_it_is_there
|
|
|
53
53
|
assert launcher_only["args"] == "--no-browser" and launcher_only["target"].endswith("Witbitz Code.cmd'")
|
|
54
54
|
|
|
55
55
|
|
|
56
|
-
def test_wsl_runs_headless_held_by_a_hidden_wsl_exe():
|
|
56
|
+
def test_wsl_runs_headless_held_by_a_hidden_wsl_exe(monkeypatch):
|
|
57
|
+
monkeypatch.setenv("WSL_DISTRO_NAME", "UbuntuD") # the runner is plain Linux: the entry is named by the distro
|
|
57
58
|
from witbitz_code import wslstart as ws
|
|
58
59
|
seen = []
|
|
59
60
|
ok, why = ws.launch_background(ps=lambda script: seen.append(script) or (0, "ok"), which=lambda n: "/home/a/.local/bin/witbitz-code",
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
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.5 → witbitz_code-1.6.7}/src/witbitz_code/opencode_plugins/witbitz-media-server.mjs
RENAMED
|
File without changes
|
{witbitz_code-1.6.5 → witbitz_code-1.6.7}/src/witbitz_code/opencode_plugins/witbitz-notes.js
RENAMED
|
File without changes
|
{witbitz_code-1.6.5 → witbitz_code-1.6.7}/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
|