zephyr-zecode 0.4.5__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.
@@ -0,0 +1,113 @@
1
+ Metadata-Version: 2.4
2
+ Name: zephyr-zecode
3
+ Version: 0.4.5
4
+ Summary: PowerShell-native ZeCode terminal client for Zephyr
5
+ Author: Zephyr Project
6
+ License-Expression: LicenseRef-Proprietary
7
+ Keywords: zecode,zephyr,coding-agent,terminal
8
+ Classifier: Development Status :: 4 - Beta
9
+ Classifier: Environment :: Console
10
+ Classifier: Intended Audience :: Developers
11
+ Classifier: Operating System :: Microsoft :: Windows
12
+ Classifier: Programming Language :: Python :: 3 :: Only
13
+ Classifier: Topic :: Software Development
14
+ Requires-Python: >=3.11
15
+ Description-Content-Type: text/markdown
16
+ Requires-Dist: mcp<2,>=1.27
17
+ Requires-Dist: httpx<1,>=0.27
18
+ Requires-Dist: arabic-reshaper<4,>=3
19
+ Requires-Dist: python-bidi<1,>=0.6
20
+ Requires-Dist: rich<15,>=13.7
21
+ Provides-Extra: classic
22
+ Requires-Dist: textual<7,>=0.85; extra == "classic"
23
+
24
+ # ZeCode 0.4.5
25
+
26
+ ZeCode is the PowerShell-native terminal client for Zephyr. It provides one
27
+ chronological conversation with inline tool activity, verified file delivery,
28
+ on-demand terminal menus, and secure Zephyr Gateway v2 device pairing.
29
+
30
+ Version `0.4.5` uses native terminal input and Rich output, without importing
31
+ Textual or using CSS in the default launch path. It retains normal terminal
32
+ scrollback and requires Zephyr `1.40.8` for session-free home startup.
33
+
34
+ 0.4.5 fixes live group-event filtering, pending-session recovery and the PgUp/Esc screen wipe. Home does not create or attach to a session. Choose Resume explicitly or save automatic resume.
35
+
36
+ ## Terminal experience
37
+
38
+ - Top-left native terminal home with model, workspace, mode, and effort.
39
+ - One chronological transcript for assistant text and real tool activity.
40
+ - Stable `File`, `Shell`, `Tests`, `Web Search`, `MCP`, and `Delivery` rows.
41
+ - Left-aligned multiline input with temporary menus; full-screen history and source viewers preserve the primary screen.
42
+ - Slash command search, model/theme/mode pickers, and F2 settings.
43
+ - Failed runs release their client lock immediately and do not duplicate chat.
44
+
45
+ 0.4.3 adds batched Windows input, guarded unmarked paste, direct clipboard paste
46
+ with F4 on Windows, and collapsible long drafts (500 characters or six lines).
47
+ F6 toggles full text. Explicit paste waits for Enter. Unmarked input bursts use
48
+ F8 to submit; their Enter characters remain newlines. No backend or model changes.
49
+
50
+ 0.4.2 added an explicit startup choice (new/resume/select session), optional
51
+ automatic resume, incremental row drawing with a single flush per update,
52
+ and role-labelled, wrapped conversation history. PgUp reads the current
53
+ conversation while preserving the draft; arriving output waits until Esc/End.
54
+ PageDown stops at the final full page, including after window resizing.
55
+ `/tools` opens coding tools first; media and other integrations live in
56
+ More tools. Browsing these categories does not alter runtime permissions.
57
+
58
+ ## Install on Windows
59
+
60
+ Install Python 3.11 or newer, extract the complete release ZIP, then run in
61
+ PowerShell from that folder:
62
+
63
+ ```powershell
64
+ Set-ExecutionPolicy -Scope Process Bypass
65
+ .\UPDATE_ZECODE_WINDOWS.ps1
66
+ zecode --version
67
+ ```
68
+
69
+ Start ZeCode:
70
+
71
+ ```powershell
72
+ zecode login
73
+ zecode doctor
74
+ zecode
75
+ ```
76
+
77
+ The script verifies and installs the bundled wheel via pipx, in the same
78
+ PowerShell window. It prioritizes the pipx executable in the current and saved
79
+ user PATH. Internet is needed for pipx and any missing dependencies.
80
+
81
+ This ZIP is a local owner release, not confirmation of a PyPI publication.
82
+ The following command continues to update from the configured package index;
83
+ use the bundled script to install this exact release:
84
+
85
+ ```powershell
86
+ zecode --update
87
+ ```
88
+
89
+ The installed distribution is named `zephyr-zecode`; the terminal command is
90
+ always `zecode`.
91
+
92
+ ## Native controls
93
+
94
+ Enter sends ordinary drafts; F8 sends guarded unmarked pastes. F4 on Windows
95
+ reads the clipboard as one paste, without relying on the host's Ctrl+V binding.
96
+ F6 expands/collapses long pasted drafts. Arrow and deletion keys reveal the
97
+ text before editing. Short pastes remain inline. A compact summary is only a
98
+ display representation; the full draft is sent. Shift+Enter (Windows) or
99
+ Alt+Enter inserts a newline. Ctrl+V on
100
+ Windows and bracketed paste insert multiline text as a single draft. `/` opens
101
+ suggestions, F2 settings, Ctrl+P commands, and Ctrl+L sessions. Tab cycles mode;
102
+ Esc stops an active run. Ctrl+Q exits. `/keyboard` lists all native shortcuts.
103
+
104
+ `/files` searches paths and previews/downloads files; `/diff` previews patches
105
+ and confirms accept/reject of all pending changes. `/mcp`, `/permissions`,
106
+ `/agents` and `/tools` use existing server controls. `/allow`, `/always` and
107
+ `/deny` resolve pending approvals.
108
+
109
+ Syntax themes color previews; terminal font and background remain terminal
110
+ settings. Native shortcuts are fixed in this release; previous Textual key
111
+ remappings do not apply. Historical Textual source remains for compatibility
112
+ tests with the optional `classic` dependency, and is not the default UI.
113
+ Arabic rendering can be selected in Display preferences.
@@ -0,0 +1,90 @@
1
+ # ZeCode 0.4.5
2
+
3
+ ZeCode is the PowerShell-native terminal client for Zephyr. It provides one
4
+ chronological conversation with inline tool activity, verified file delivery,
5
+ on-demand terminal menus, and secure Zephyr Gateway v2 device pairing.
6
+
7
+ Version `0.4.5` uses native terminal input and Rich output, without importing
8
+ Textual or using CSS in the default launch path. It retains normal terminal
9
+ scrollback and requires Zephyr `1.40.8` for session-free home startup.
10
+
11
+ 0.4.5 fixes live group-event filtering, pending-session recovery and the PgUp/Esc screen wipe. Home does not create or attach to a session. Choose Resume explicitly or save automatic resume.
12
+
13
+ ## Terminal experience
14
+
15
+ - Top-left native terminal home with model, workspace, mode, and effort.
16
+ - One chronological transcript for assistant text and real tool activity.
17
+ - Stable `File`, `Shell`, `Tests`, `Web Search`, `MCP`, and `Delivery` rows.
18
+ - Left-aligned multiline input with temporary menus; full-screen history and source viewers preserve the primary screen.
19
+ - Slash command search, model/theme/mode pickers, and F2 settings.
20
+ - Failed runs release their client lock immediately and do not duplicate chat.
21
+
22
+ 0.4.3 adds batched Windows input, guarded unmarked paste, direct clipboard paste
23
+ with F4 on Windows, and collapsible long drafts (500 characters or six lines).
24
+ F6 toggles full text. Explicit paste waits for Enter. Unmarked input bursts use
25
+ F8 to submit; their Enter characters remain newlines. No backend or model changes.
26
+
27
+ 0.4.2 added an explicit startup choice (new/resume/select session), optional
28
+ automatic resume, incremental row drawing with a single flush per update,
29
+ and role-labelled, wrapped conversation history. PgUp reads the current
30
+ conversation while preserving the draft; arriving output waits until Esc/End.
31
+ PageDown stops at the final full page, including after window resizing.
32
+ `/tools` opens coding tools first; media and other integrations live in
33
+ More tools. Browsing these categories does not alter runtime permissions.
34
+
35
+ ## Install on Windows
36
+
37
+ Install Python 3.11 or newer, extract the complete release ZIP, then run in
38
+ PowerShell from that folder:
39
+
40
+ ```powershell
41
+ Set-ExecutionPolicy -Scope Process Bypass
42
+ .\UPDATE_ZECODE_WINDOWS.ps1
43
+ zecode --version
44
+ ```
45
+
46
+ Start ZeCode:
47
+
48
+ ```powershell
49
+ zecode login
50
+ zecode doctor
51
+ zecode
52
+ ```
53
+
54
+ The script verifies and installs the bundled wheel via pipx, in the same
55
+ PowerShell window. It prioritizes the pipx executable in the current and saved
56
+ user PATH. Internet is needed for pipx and any missing dependencies.
57
+
58
+ This ZIP is a local owner release, not confirmation of a PyPI publication.
59
+ The following command continues to update from the configured package index;
60
+ use the bundled script to install this exact release:
61
+
62
+ ```powershell
63
+ zecode --update
64
+ ```
65
+
66
+ The installed distribution is named `zephyr-zecode`; the terminal command is
67
+ always `zecode`.
68
+
69
+ ## Native controls
70
+
71
+ Enter sends ordinary drafts; F8 sends guarded unmarked pastes. F4 on Windows
72
+ reads the clipboard as one paste, without relying on the host's Ctrl+V binding.
73
+ F6 expands/collapses long pasted drafts. Arrow and deletion keys reveal the
74
+ text before editing. Short pastes remain inline. A compact summary is only a
75
+ display representation; the full draft is sent. Shift+Enter (Windows) or
76
+ Alt+Enter inserts a newline. Ctrl+V on
77
+ Windows and bracketed paste insert multiline text as a single draft. `/` opens
78
+ suggestions, F2 settings, Ctrl+P commands, and Ctrl+L sessions. Tab cycles mode;
79
+ Esc stops an active run. Ctrl+Q exits. `/keyboard` lists all native shortcuts.
80
+
81
+ `/files` searches paths and previews/downloads files; `/diff` previews patches
82
+ and confirms accept/reject of all pending changes. `/mcp`, `/permissions`,
83
+ `/agents` and `/tools` use existing server controls. `/allow`, `/always` and
84
+ `/deny` resolve pending approvals.
85
+
86
+ Syntax themes color previews; terminal font and background remain terminal
87
+ settings. Native shortcuts are fixed in this release; previous Textual key
88
+ remappings do not apply. Historical Textual source remains for compatibility
89
+ tests with the optional `classic` dependency, and is not the default UI.
90
+ Arabic rendering can be selected in Display preferences.
@@ -0,0 +1,42 @@
1
+ [build-system]
2
+ requires = ["setuptools>=77", "wheel"]
3
+ build-backend = "setuptools.build_meta"
4
+
5
+ [project]
6
+ name = "zephyr-zecode"
7
+ dynamic = ["version"]
8
+ description = "PowerShell-native ZeCode terminal client for Zephyr"
9
+ readme = "PYPI_README.md"
10
+ requires-python = ">=3.11"
11
+ license = "LicenseRef-Proprietary"
12
+ authors = [
13
+ {name = "Zephyr Project"},
14
+ ]
15
+ keywords = ["zecode", "zephyr", "coding-agent", "terminal"]
16
+ classifiers = [
17
+ "Development Status :: 4 - Beta",
18
+ "Environment :: Console",
19
+ "Intended Audience :: Developers",
20
+ "Operating System :: Microsoft :: Windows",
21
+ "Programming Language :: Python :: 3 :: Only",
22
+ "Topic :: Software Development",
23
+ ]
24
+ dependencies = [
25
+ "mcp>=1.27,<2",
26
+ "httpx>=0.27,<1",
27
+ "arabic-reshaper>=3,<4",
28
+ "python-bidi>=0.6,<1",
29
+ "rich>=13.7,<15",
30
+ ]
31
+
32
+ [project.optional-dependencies]
33
+ classic = ["textual>=0.85,<7"]
34
+
35
+ [project.scripts]
36
+ zecode = "zecode_terminal.__main__:main"
37
+
38
+ [tool.setuptools.dynamic]
39
+ version = {attr = "zecode_terminal.__version__"}
40
+
41
+ [tool.setuptools.packages.find]
42
+ include = ["zecode_terminal*"]
@@ -0,0 +1,4 @@
1
+ [egg_info]
2
+ tag_build =
3
+ tag_date = 0
4
+
@@ -0,0 +1,4 @@
1
+ """Owner-only ZeCode terminal client."""
2
+
3
+ __version__ = "0.4.5"
4
+ ZEPHYR_COMPAT_VERSION = "1.40.8"
@@ -0,0 +1,287 @@
1
+ from __future__ import annotations
2
+
3
+ import argparse
4
+ import asyncio
5
+ from getpass import getpass
6
+ from importlib import metadata
7
+ import shutil
8
+ import socket
9
+ import subprocess
10
+ import sys
11
+ import time
12
+
13
+ from .app import ZeCodeApp
14
+ from . import __version__
15
+ from .client import (
16
+ GatewayBridge,
17
+ MCPBridge,
18
+ begin_device_pairing,
19
+ gateway_health,
20
+ is_transient_disconnect,
21
+ poll_device_pairing,
22
+ )
23
+ from .secrets import (
24
+ clear_gateway_credentials,
25
+ clear_private_link,
26
+ load_gateway_credentials,
27
+ load_gateway_url,
28
+ load_private_link,
29
+ normalize_gateway_url,
30
+ save_gateway_credentials,
31
+ save_private_link,
32
+ )
33
+ from .updater import PYPI_DISTRIBUTION
34
+
35
+
36
+ def _parser() -> argparse.ArgumentParser:
37
+ parser = argparse.ArgumentParser(prog="zecode")
38
+ parser.add_argument(
39
+ "--version", action="version", version=f"ZeCode {__version__}")
40
+ parser.add_argument(
41
+ "command",
42
+ nargs="?",
43
+ choices=("login", "logout", "doctor", "update", "legacy-login"),
44
+ )
45
+ parser.add_argument("--workspace", default="", help="Zephyr workspace key")
46
+ parser.add_argument(
47
+ "--gateway", default="", help="HTTPS ZeCode Gateway v2 URL")
48
+ parser.add_argument(
49
+ "--device-name", default="", help="Name shown during device approval")
50
+ parser.add_argument(
51
+ "--update",
52
+ action="store_true",
53
+ dest="update_requested",
54
+ help="Update ZeCode through pipx in this terminal and exit",
55
+ )
56
+ return parser
57
+
58
+
59
+ async def _run_app(bridge, workspace: str) -> int:
60
+ async with bridge:
61
+ app = ZeCodeApp(bridge, workspace=workspace)
62
+ if not await app.bootstrap():
63
+ return 2
64
+ await app.run()
65
+ bridge.normal_close = app.ui.requested_exit
66
+ return 0
67
+
68
+
69
+ async def _start(workspace: str) -> int:
70
+ """Prefer Transport v2; retain the old bridge only for migration."""
71
+ try:
72
+ credentials = load_gateway_credentials()
73
+ except RuntimeError:
74
+ private_link = load_private_link()
75
+ for attempt in range(1, 4):
76
+ bridge = MCPBridge(private_link)
77
+ try:
78
+ return await _run_app(bridge, workspace)
79
+ except BaseException as exc:
80
+ if attempt >= 3 or not is_transient_disconnect(exc):
81
+ raise
82
+ print(
83
+ f"Legacy connection interrupted. Reconnecting ({attempt}/3)…",
84
+ file=sys.stderr,
85
+ )
86
+ await asyncio.sleep(float(attempt))
87
+ return 1
88
+
89
+ for attempt in range(1, 4):
90
+ bridge = GatewayBridge(credentials)
91
+ try:
92
+ return await _run_app(bridge, workspace)
93
+ except BaseException as exc:
94
+ credentials = dict(bridge.credentials)
95
+ if attempt >= 3 or not is_transient_disconnect(exc):
96
+ raise
97
+ print(
98
+ f"ZeCode connection interrupted. Reconnecting ({attempt}/3)…",
99
+ file=sys.stderr,
100
+ )
101
+ await asyncio.sleep(float(attempt))
102
+ return 1
103
+
104
+
105
+ def _gateway_for_login(explicit: str) -> str:
106
+ if str(explicit or "").strip():
107
+ return normalize_gateway_url(explicit)
108
+ stored = load_gateway_url()
109
+ if stored:
110
+ return stored
111
+ entered = input(
112
+ "ZeCode Gateway URL (for example https://host.example/zecode/v2): "
113
+ ).strip()
114
+ return normalize_gateway_url(entered)
115
+
116
+
117
+ async def _login(gateway: str, device_name: str) -> int:
118
+ endpoint = _gateway_for_login(gateway)
119
+ health = await gateway_health(endpoint)
120
+ if not health.get("ok"):
121
+ raise RuntimeError(
122
+ str(health.get("error") or "ZeCode Gateway is not ready"))
123
+ pairing = await begin_device_pairing(
124
+ endpoint,
125
+ device_name=(
126
+ str(device_name or "").strip()
127
+ or "ZeCode on " + socket.gethostname()
128
+ ),
129
+ )
130
+ code = str(pairing.get("user_code") or "")
131
+ command = str(pairing.get("verification_command") or "/zecode-pair " + code)
132
+ print("")
133
+ print("ZeCode device code: " + code)
134
+ print("In your Zephyr Discord server, run:")
135
+ print(" " + command)
136
+ print("Waiting for owner approval…")
137
+ interval = max(1, int(pairing.get("interval") or 2))
138
+ deadline = time.monotonic() + max(30, int(pairing.get("expires_in") or 600))
139
+ while time.monotonic() < deadline:
140
+ result = await poll_device_pairing(
141
+ endpoint, device_code=str(pairing.get("device_code") or ""))
142
+ if result.get("ok"):
143
+ save_gateway_credentials(result)
144
+ clear_private_link()
145
+ print("ZeCode is linked securely to this device.")
146
+ return 0
147
+ await asyncio.sleep(interval)
148
+ raise RuntimeError("The ZeCode device code expired. Run: zecode login")
149
+
150
+
151
+ async def _doctor(explicit_gateway: str) -> int:
152
+ endpoint = _gateway_for_login(explicit_gateway)
153
+ print("ZeCode doctor")
154
+ print(" gateway: " + endpoint)
155
+ health = await gateway_health(endpoint)
156
+ print(" HTTPS: " + ("PASS" if health.get("http_status") else "FAIL"))
157
+ print(" Gateway v2: " + ("PASS" if health.get("ok") else "FAIL"))
158
+ print(" Server version: " + str(health.get("version") or "unknown"))
159
+ try:
160
+ credentials = load_gateway_credentials()
161
+ except RuntimeError:
162
+ print(" Device token: NOT LINKED (run: zecode login)")
163
+ return 2
164
+ bridge = GatewayBridge(credentials)
165
+ async with bridge:
166
+ identity = await bridge.probe_identity()
167
+ device = identity.get("device") or {}
168
+ print(" Device token: PASS")
169
+ print(" Device: " + str(device.get("device_name") or "unknown"))
170
+ print(" Transport: HTTPS JSON v2")
171
+ return 0
172
+
173
+
174
+ async def _logout() -> int:
175
+ try:
176
+ credentials = load_gateway_credentials()
177
+ except RuntimeError:
178
+ credentials = {}
179
+ if credentials:
180
+ try:
181
+ bridge = GatewayBridge(credentials)
182
+ async with bridge:
183
+ await bridge.revoke()
184
+ except Exception:
185
+ # Local logout must still succeed when the server is offline.
186
+ pass
187
+ removed = clear_gateway_credentials()
188
+ legacy_removed = clear_private_link()
189
+ removed = removed or legacy_removed
190
+ print("ZeCode device access removed." if removed else "ZeCode was not linked.")
191
+ return 0
192
+
193
+
194
+ def _legacy_login() -> int:
195
+ private_link = getpass(
196
+ "Paste the complete owner private link from Discord /mcp-link: ").strip()
197
+ if not private_link.startswith("https://") or "/mcp/private/" not in private_link:
198
+ raise SystemExit("Invalid private link. Paste the complete HTTPS URL.")
199
+ save_private_link(private_link)
200
+ print("Legacy owner link saved. Run `zecode login` to move to Gateway v2.")
201
+ return 0
202
+
203
+
204
+ def _launch_update() -> int:
205
+ pipx = shutil.which("pipx")
206
+ if not pipx:
207
+ print("ZeCode update requires pipx.", file=sys.stderr)
208
+ print("Install it in PowerShell with:", file=sys.stderr)
209
+ print(" py -m pip install --user pipx", file=sys.stderr)
210
+ print(" py -m pipx ensurepath", file=sys.stderr)
211
+ return 2
212
+ try:
213
+ metadata.version(PYPI_DISTRIBUTION)
214
+ action = "upgrade"
215
+ label = "Updating"
216
+ except metadata.PackageNotFoundError:
217
+ action = "install"
218
+ label = "Migrating this source installation to"
219
+ print("ZeCode update")
220
+ print(f" {label} {PYPI_DISTRIBUTION} through pipx...")
221
+ completed = subprocess.run(
222
+ [pipx, action, PYPI_DISTRIBUTION],
223
+ check=False,
224
+ )
225
+ if completed.returncode:
226
+ print(
227
+ "ZeCode update failed. pipx kept the current installation unchanged.",
228
+ file=sys.stderr,
229
+ )
230
+ else:
231
+ print("ZeCode update completed in this terminal.")
232
+ return int(completed.returncode)
233
+
234
+
235
+ def _friendly_error(exc: BaseException) -> str:
236
+ current = exc
237
+ seen: set[int] = set()
238
+ while id(current) not in seen:
239
+ seen.add(id(current))
240
+ nested = getattr(current, "exceptions", None)
241
+ if not nested:
242
+ break
243
+ current = nested[0]
244
+ message = str(current or type(current).__name__).strip()
245
+ try:
246
+ credentials = load_gateway_credentials()
247
+ except RuntimeError:
248
+ credentials = {}
249
+ for value in (
250
+ credentials.get("access_token"), credentials.get("refresh_token"),
251
+ ):
252
+ if value:
253
+ message = message.replace(str(value), "<device-token>")
254
+ try:
255
+ private_link = load_private_link()
256
+ except (OSError, RuntimeError, ValueError):
257
+ private_link = ""
258
+ if private_link:
259
+ message = message.replace(private_link, "<private-link>")
260
+ return message[:800] or type(current).__name__
261
+
262
+
263
+ def main() -> int:
264
+ args = _parser().parse_args()
265
+ try:
266
+ if args.update_requested:
267
+ return _launch_update()
268
+ if args.command == "login":
269
+ return asyncio.run(_login(args.gateway, args.device_name))
270
+ if args.command == "logout":
271
+ return asyncio.run(_logout())
272
+ if args.command == "doctor":
273
+ return asyncio.run(_doctor(args.gateway))
274
+ if args.command == "legacy-login":
275
+ return _legacy_login()
276
+ if args.command == "update":
277
+ return _launch_update()
278
+ return asyncio.run(_start(args.workspace))
279
+ except KeyboardInterrupt:
280
+ return 130
281
+ except Exception as exc:
282
+ print("ZeCode could not start: " + _friendly_error(exc), file=sys.stderr)
283
+ return 1
284
+
285
+
286
+ if __name__ == "__main__":
287
+ raise SystemExit(main())