witbitz-code 1.3.5__tar.gz → 1.3.6__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.
Files changed (83) hide show
  1. {witbitz_code-1.3.5/src/witbitz_code.egg-info → witbitz_code-1.3.6}/PKG-INFO +2 -1
  2. {witbitz_code-1.3.5 → witbitz_code-1.3.6}/pyproject.toml +2 -2
  3. {witbitz_code-1.3.5 → witbitz_code-1.3.6}/src/witbitz_code/__init__.py +1 -1
  4. {witbitz_code-1.3.5 → witbitz_code-1.3.6}/src/witbitz_code/cli.py +1 -1
  5. {witbitz_code-1.3.5 → witbitz_code-1.3.6}/src/witbitz_code/opencode_plugins/witbitz-media-server.mjs +19 -10
  6. {witbitz_code-1.3.5 → witbitz_code-1.3.6}/src/witbitz_code/wizard.py +7 -3
  7. {witbitz_code-1.3.5 → witbitz_code-1.3.6/src/witbitz_code.egg-info}/PKG-INFO +2 -1
  8. {witbitz_code-1.3.5 → witbitz_code-1.3.6}/src/witbitz_code.egg-info/requires.txt +1 -0
  9. {witbitz_code-1.3.5 → witbitz_code-1.3.6}/tests/test_toolsetup.py +58 -0
  10. {witbitz_code-1.3.5 → witbitz_code-1.3.6}/LICENSE +0 -0
  11. {witbitz_code-1.3.5 → witbitz_code-1.3.6}/README.md +0 -0
  12. {witbitz_code-1.3.5 → witbitz_code-1.3.6}/setup.cfg +0 -0
  13. {witbitz_code-1.3.5 → witbitz_code-1.3.6}/src/witbitz_code/__main__.py +0 -0
  14. {witbitz_code-1.3.5 → witbitz_code-1.3.6}/src/witbitz_code/_js.py +0 -0
  15. {witbitz_code-1.3.5 → witbitz_code-1.3.6}/src/witbitz_code/account.py +0 -0
  16. {witbitz_code-1.3.5 → witbitz_code-1.3.6}/src/witbitz_code/asks.py +0 -0
  17. {witbitz_code-1.3.5 → witbitz_code-1.3.6}/src/witbitz_code/attachments.py +0 -0
  18. {witbitz_code-1.3.5 → witbitz_code-1.3.6}/src/witbitz_code/auto.py +0 -0
  19. {witbitz_code-1.3.5 → witbitz_code-1.3.6}/src/witbitz_code/auto_runner.py +0 -0
  20. {witbitz_code-1.3.5 → witbitz_code-1.3.6}/src/witbitz_code/codekeys.py +0 -0
  21. {witbitz_code-1.3.5 → witbitz_code-1.3.6}/src/witbitz_code/codetools.py +0 -0
  22. {witbitz_code-1.3.5 → witbitz_code-1.3.6}/src/witbitz_code/compress.py +0 -0
  23. {witbitz_code-1.3.5 → witbitz_code-1.3.6}/src/witbitz_code/computers.py +0 -0
  24. {witbitz_code-1.3.5 → witbitz_code-1.3.6}/src/witbitz_code/confidential.py +0 -0
  25. {witbitz_code-1.3.5 → witbitz_code-1.3.6}/src/witbitz_code/confidential_models.json +0 -0
  26. {witbitz_code-1.3.5 → witbitz_code-1.3.6}/src/witbitz_code/connector.py +0 -0
  27. {witbitz_code-1.3.5 → witbitz_code-1.3.6}/src/witbitz_code/devicelink.py +0 -0
  28. {witbitz_code-1.3.5 → witbitz_code-1.3.6}/src/witbitz_code/folders.py +0 -0
  29. {witbitz_code-1.3.5 → witbitz_code-1.3.6}/src/witbitz_code/gateway.py +0 -0
  30. {witbitz_code-1.3.5 → witbitz_code-1.3.6}/src/witbitz_code/keys.py +0 -0
  31. {witbitz_code-1.3.5 → witbitz_code-1.3.6}/src/witbitz_code/link.py +0 -0
  32. {witbitz_code-1.3.5 → witbitz_code-1.3.6}/src/witbitz_code/models.py +0 -0
  33. {witbitz_code-1.3.5 → witbitz_code-1.3.6}/src/witbitz_code/net.py +0 -0
  34. {witbitz_code-1.3.5 → witbitz_code-1.3.6}/src/witbitz_code/offered_models.json +0 -0
  35. {witbitz_code-1.3.5 → witbitz_code-1.3.6}/src/witbitz_code/opencode_plugins/notes-init.md +0 -0
  36. {witbitz_code-1.3.5 → witbitz_code-1.3.6}/src/witbitz_code/opencode_plugins/witbitz-notes.js +0 -0
  37. {witbitz_code-1.3.5 → witbitz_code-1.3.6}/src/witbitz_code/opencode_plugins/witbitz-progress.js +0 -0
  38. {witbitz_code-1.3.5 → witbitz_code-1.3.6}/src/witbitz_code/outputs.py +0 -0
  39. {witbitz_code-1.3.5 → witbitz_code-1.3.6}/src/witbitz_code/pair.py +0 -0
  40. {witbitz_code-1.3.5 → witbitz_code-1.3.6}/src/witbitz_code/pairings.py +0 -0
  41. {witbitz_code-1.3.5 → witbitz_code-1.3.6}/src/witbitz_code/payers.py +0 -0
  42. {witbitz_code-1.3.5 → witbitz_code-1.3.6}/src/witbitz_code/plugins.py +0 -0
  43. {witbitz_code-1.3.5 → witbitz_code-1.3.6}/src/witbitz_code/policy.py +0 -0
  44. {witbitz_code-1.3.5 → witbitz_code-1.3.6}/src/witbitz_code/ports.py +0 -0
  45. {witbitz_code-1.3.5 → witbitz_code-1.3.6}/src/witbitz_code/qr.py +0 -0
  46. {witbitz_code-1.3.5 → witbitz_code-1.3.6}/src/witbitz_code/receipt.py +0 -0
  47. {witbitz_code-1.3.5 → witbitz_code-1.3.6}/src/witbitz_code/relay.py +0 -0
  48. {witbitz_code-1.3.5 → witbitz_code-1.3.6}/src/witbitz_code/seen.py +0 -0
  49. {witbitz_code-1.3.5 → witbitz_code-1.3.6}/src/witbitz_code/tinfoil_read.py +0 -0
  50. {witbitz_code-1.3.5 → witbitz_code-1.3.6}/src/witbitz_code/tools_catalog.json +0 -0
  51. {witbitz_code-1.3.5 → witbitz_code-1.3.6}/src/witbitz_code/tools_probe.py +0 -0
  52. {witbitz_code-1.3.5 → witbitz_code-1.3.6}/src/witbitz_code/toolsetup.py +0 -0
  53. {witbitz_code-1.3.5 → witbitz_code-1.3.6}/src/witbitz_code/uninstall.py +0 -0
  54. {witbitz_code-1.3.5 → witbitz_code-1.3.6}/src/witbitz_code/winagent.py +0 -0
  55. {witbitz_code-1.3.5 → witbitz_code-1.3.6}/src/witbitz_code/winconsole.py +0 -0
  56. {witbitz_code-1.3.5 → witbitz_code-1.3.6}/src/witbitz_code.egg-info/SOURCES.txt +0 -0
  57. {witbitz_code-1.3.5 → witbitz_code-1.3.6}/src/witbitz_code.egg-info/dependency_links.txt +0 -0
  58. {witbitz_code-1.3.5 → witbitz_code-1.3.6}/src/witbitz_code.egg-info/entry_points.txt +0 -0
  59. {witbitz_code-1.3.5 → witbitz_code-1.3.6}/src/witbitz_code.egg-info/top_level.txt +0 -0
  60. {witbitz_code-1.3.5 → witbitz_code-1.3.6}/tests/test_asks.py +0 -0
  61. {witbitz_code-1.3.5 → witbitz_code-1.3.6}/tests/test_attachments.py +0 -0
  62. {witbitz_code-1.3.5 → witbitz_code-1.3.6}/tests/test_auto.py +0 -0
  63. {witbitz_code-1.3.5 → witbitz_code-1.3.6}/tests/test_cli.py +0 -0
  64. {witbitz_code-1.3.5 → witbitz_code-1.3.6}/tests/test_codekeys.py +0 -0
  65. {witbitz_code-1.3.5 → witbitz_code-1.3.6}/tests/test_codetools.py +0 -0
  66. {witbitz_code-1.3.5 → witbitz_code-1.3.6}/tests/test_confidential.py +0 -0
  67. {witbitz_code-1.3.5 → witbitz_code-1.3.6}/tests/test_connector_e2e.py +0 -0
  68. {witbitz_code-1.3.5 → witbitz_code-1.3.6}/tests/test_devicelink.py +0 -0
  69. {witbitz_code-1.3.5 → witbitz_code-1.3.6}/tests/test_files_and_compress.py +0 -0
  70. {witbitz_code-1.3.5 → witbitz_code-1.3.6}/tests/test_folders.py +0 -0
  71. {witbitz_code-1.3.5 → witbitz_code-1.3.6}/tests/test_outputs.py +0 -0
  72. {witbitz_code-1.3.5 → witbitz_code-1.3.6}/tests/test_pair_flows.py +0 -0
  73. {witbitz_code-1.3.5 → witbitz_code-1.3.6}/tests/test_plugins.py +0 -0
  74. {witbitz_code-1.3.5 → witbitz_code-1.3.6}/tests/test_policy.py +0 -0
  75. {witbitz_code-1.3.5 → witbitz_code-1.3.6}/tests/test_ports.py +0 -0
  76. {witbitz_code-1.3.5 → witbitz_code-1.3.6}/tests/test_registry.py +0 -0
  77. {witbitz_code-1.3.5 → witbitz_code-1.3.6}/tests/test_relay_codec.py +0 -0
  78. {witbitz_code-1.3.5 → witbitz_code-1.3.6}/tests/test_setup.py +0 -0
  79. {witbitz_code-1.3.5 → witbitz_code-1.3.6}/tests/test_tools_probe.py +0 -0
  80. {witbitz_code-1.3.5 → witbitz_code-1.3.6}/tests/test_uninstall.py +0 -0
  81. {witbitz_code-1.3.5 → witbitz_code-1.3.6}/tests/test_winagent.py +0 -0
  82. {witbitz_code-1.3.5 → witbitz_code-1.3.6}/tests/test_winconsole.py +0 -0
  83. {witbitz_code-1.3.5 → witbitz_code-1.3.6}/tests/test_windows_files.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: witbitz-code
3
- Version: 1.3.5
3
+ Version: 1.3.6
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
@@ -21,6 +21,7 @@ Requires-Dist: segno>=1.5
21
21
  Requires-Dist: tinfoil<0.15,>=0.14
22
22
  Provides-Extra: test
23
23
  Requires-Dist: pytest>=8; extra == "test"
24
+ Requires-Dist: pyflakes>=3; extra == "test"
24
25
  Dynamic: license-file
25
26
 
26
27
  # witbitz-code
@@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"
4
4
 
5
5
  [project]
6
6
  name = "witbitz-code"
7
- version = "1.3.5"
7
+ version = "1.3.6"
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"
@@ -29,7 +29,7 @@ dependencies = [
29
29
  ]
30
30
 
31
31
  [project.optional-dependencies]
32
- test = ["pytest>=8"]
32
+ test = ["pytest>=8", "pyflakes>=3"]
33
33
 
34
34
  [project.urls]
35
35
  Documentation = "https://witbitz.chat/docs/opencode.md"
@@ -4,4 +4,4 @@ The Python implementation of tools/witbitz-code.mjs (docs/opencode-relay.md phas
4
4
  vectors and cross-implementation tests.
5
5
  """
6
6
 
7
- __version__ = "1.3.5"
7
+ __version__ = "1.3.6"
@@ -26,7 +26,7 @@ import httpx
26
26
  from . import __version__
27
27
  from . import pair as pair_cmd
28
28
  from . import pairings as pp
29
- from . import codekeys, codetools, confidential, keys, models, plugins, policy, uninstall as uninstall_mod, winagent, winconsole, wizard
29
+ from . import codekeys, codetools, confidential, keys, models, plugins, policy, ports, toolsetup, uninstall as uninstall_mod, winagent, winconsole, wizard
30
30
  from ._js import to_number, truthy
31
31
  from .connector import pairings_for_port, start_connector
32
32
  from .payers import SessionPayers
@@ -719,26 +719,35 @@ export function parseSearchAnswer (data) { // == agent/tinfoilWebSearch.mjs pars
719
719
  }
720
720
  return { text, citations }
721
721
  }
722
- async function webSearch (a, { fetchImpl = fetch, env = process.env } = {}) {
723
- const q = String((a && a.query) || '').replace(/\s+/g, ' ').trim().slice(0, 2000)
724
- if (!q) fail('query is required')
725
- const apiKey = env.TINFOIL_API_KEY
726
- if (!apiKey) fail('web_search needs a Tinfoil key: TINFOIL_API_KEY is not set for this OpenCode (on a laptop it comes from ~/.opencode-server.env; a cloud computer gets it from its Sentinel)')
727
- const url = env.WITBITZ_SEARCH_URL || `https://${SEARCH_HOST}${SEARCH_PATH}`
728
- const body = {
722
+ /** The one Tinfoil web-search request, for a laptop (sent with the key) and for a cloud computer's Sentinel (which builds it
723
+ * from the query alone, so the key it holds cannot be spent on any other call — infra/cloud-computer/sentinel). */
724
+ export function searchRequest (query, { piiCheck = true } = {}) {
725
+ return {
729
726
  model: SEARCH_MODEL,
730
727
  messages: [
731
728
  { role: 'system', content: 'You are a web research assistant. Search the web and report what you find, briefly and factually, with dates. State plainly when sources disagree or when something has not happened yet. Do not speculate beyond what the sources say.' },
732
- { role: 'user', content: q },
729
+ { role: 'user', content: query },
733
730
  ],
734
731
  max_tokens: SEARCH_MAX_TOKENS,
735
732
  reasoning_effort: SEARCH_REASONING_EFFORT,
736
733
  web_search_options: {},
737
- ...(a && a.pii_check === false ? {} : { pii_check_options: {} }), // a query carrying personal data is refused before it leaves the enclave for the index
734
+ ...(piiCheck === false ? {} : { pii_check_options: {} }), // a query carrying personal data is refused before it leaves the enclave for the index
738
735
  }
736
+ }
737
+
738
+ async function webSearch (a, { fetchImpl = fetch, env = process.env } = {}) {
739
+ const q = String((a && a.query) || '').replace(/\s+/g, ' ').trim().slice(0, 2000)
740
+ if (!q) fail('query is required')
741
+ const piiCheck = !(a && a.pii_check === false)
742
+ // A cloud computer holds no key: the query goes to its Credential Sentinel, which builds this same request with the key.
743
+ const viaSentinel = env.WITBITZ_SEARCH_SENTINEL
744
+ const apiKey = env.TINFOIL_API_KEY
745
+ if (!viaSentinel && !apiKey) fail('web_search needs a Tinfoil key: TINFOIL_API_KEY is not set for this OpenCode (on a laptop it comes from ~/.opencode-server.env; a cloud computer gets it from its Sentinel)')
746
+ const url = viaSentinel || env.WITBITZ_SEARCH_URL || `https://${SEARCH_HOST}${SEARCH_PATH}`
747
+ const body = viaSentinel ? { query: q, pii_check: piiCheck } : searchRequest(q, { piiCheck })
739
748
  let data
740
749
  try {
741
- const res = await fetchImpl(url, { method: 'POST', headers: { 'content-type': 'application/json', authorization: `Bearer ${apiKey}` }, body: JSON.stringify(body), signal: AbortSignal.timeout(SEARCH_TIMEOUT_MS) })
750
+ const res = await fetchImpl(url, { method: 'POST', headers: { 'content-type': 'application/json', ...(viaSentinel ? {} : { authorization: `Bearer ${apiKey}` }) }, body: JSON.stringify(body), signal: AbortSignal.timeout(SEARCH_TIMEOUT_MS) })
742
751
  if (!res.ok) fail(`the search endpoint answered ${res.status}`)
743
752
  data = await res.json()
744
753
  } catch (e) { fail(`could not search the web (${String((e && e.message) || e).slice(0, 160)})`) }
@@ -241,7 +241,7 @@ def _describe(step: dict) -> str:
241
241
 
242
242
 
243
243
  async def _tools_step(d: Deps) -> str:
244
- """→ 'here' | 'installed' | 'partly' | 'skipped' | 'unavailable'."""
244
+ """→ 'here' | 'installed' | 'partly' | 'skipped' | 'unavailable' | 'failed' (an install that breaks never ends setup)."""
245
245
  try:
246
246
  report = d.probe_tools()
247
247
  except Exception: # noqa: BLE001 — a probe that fails never stops setup
@@ -262,8 +262,12 @@ async def _tools_step(d: Deps) -> str:
262
262
  if not _yes(d.ask(" Install them now? [Y/n] ")):
263
263
  d.say(" Skipped — run witbitz-code setup again to install them, or use the card in Code.")
264
264
  return "skipped"
265
- d.run_tools(plan)
266
- d.refresh_path()
265
+ try:
266
+ d.run_tools(plan)
267
+ d.refresh_path()
268
+ except Exception as e: # noqa: BLE001 — the tools are a convenience: setup goes on to serve
269
+ d.say(f" Could not install them ({str(e)[:160]}) — setup goes on; \"Install with the agent\" in Code can try.")
270
+ return "failed"
267
271
  try:
268
272
  after = {t["id"] for t in toolsetup.missing_tools(d.probe_tools())}
269
273
  except Exception: # noqa: BLE001
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: witbitz-code
3
- Version: 1.3.5
3
+ Version: 1.3.6
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
@@ -21,6 +21,7 @@ Requires-Dist: segno>=1.5
21
21
  Requires-Dist: tinfoil<0.15,>=0.14
22
22
  Provides-Extra: test
23
23
  Requires-Dist: pytest>=8; extra == "test"
24
+ Requires-Dist: pyflakes>=3; extra == "test"
24
25
  Dynamic: license-file
25
26
 
26
27
  # witbitz-code
@@ -6,3 +6,4 @@ tinfoil<0.15,>=0.14
6
6
 
7
7
  [test]
8
8
  pytest>=8
9
+ pyflakes>=3
@@ -145,3 +145,61 @@ def test_no_package_manager_says_so_without_asking():
145
145
  t = Tools(["jq"], {"os": "darwin", "pm": ""})
146
146
  r, said = step(t, [])
147
147
  assert r == "unavailable" and t.plans == [] and any("no package manager found" in s for s in said)
148
+
149
+
150
+ def test_an_install_that_breaks_never_ends_setup():
151
+ # 1.3.5 on Windows: cli.py never imported toolsetup, so the first "y" died "FATAL — name 'toolsetup' is not defined"
152
+ t = Tools(["jq"], {"os": "win32", "pm": "winget"})
153
+
154
+ def broken(plan: list) -> dict:
155
+ raise NameError("name 'toolsetup' is not defined")
156
+
157
+ t.run = broken # type: ignore[method-assign]
158
+ r, said = step(t, [""])
159
+ assert r == "failed"
160
+ assert any("Could not install them (name 'toolsetup' is not defined) — setup goes on" in s for s in said)
161
+
162
+
163
+ def test_setup_hands_the_wizard_the_real_installer_and_path_refresh(monkeypatch):
164
+ # the wizard's own tests fake run_tools/refresh_path, so only this one runs the lambdas cli.py builds
165
+ from witbitz_code import cli
166
+
167
+ seen: dict = {}
168
+
169
+ async def capture(d):
170
+ seen["d"] = d
171
+ return {"served": 0}
172
+
173
+ monkeypatch.setattr(wizard, "run_setup", capture)
174
+ asyncio.run(cli.setup([]))
175
+ seen["d"].refresh_path()
176
+ assert seen["d"].run_tools([]) == {"done": [], "failed": [], "auto": [], "none": []}
177
+
178
+
179
+ def test_no_module_uses_a_name_it_never_defined():
180
+ # pyflakes: the two missing imports that reached a release (ports in 1.3.3's serve, toolsetup in 1.3.5's setup)
181
+ from pathlib import Path
182
+
183
+ import pyflakes.api
184
+ import pyflakes.messages
185
+
186
+ import witbitz_code
187
+
188
+ class Undefined:
189
+ def __init__(self) -> None:
190
+ self.found: list[str] = []
191
+
192
+ def unexpectedError(self, filename, msg): # noqa: N802 — pyflakes' reporter interface
193
+ self.found.append(f"{filename}: {msg}")
194
+
195
+ def syntaxError(self, filename, msg, lineno, offset, text): # noqa: N802
196
+ self.found.append(f"{filename}:{lineno}: {msg}")
197
+
198
+ def flake(self, message):
199
+ if isinstance(message, pyflakes.messages.UndefinedName):
200
+ self.found.append(str(message))
201
+
202
+ rep = Undefined()
203
+ for f in sorted(Path(witbitz_code.__file__).parent.glob("*.py")):
204
+ pyflakes.api.check(f.read_text(encoding="utf-8"), str(f), rep)
205
+ assert rep.found == []
File without changes
File without changes
File without changes