loom-code 0.1.1__tar.gz → 0.1.2__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.
- {loom_code-0.1.1 → loom_code-0.1.2}/PKG-INFO +5 -4
- {loom_code-0.1.1 → loom_code-0.1.2}/README.md +4 -3
- {loom_code-0.1.1 → loom_code-0.1.2}/loom_code/__init__.py +1 -1
- {loom_code-0.1.1 → loom_code-0.1.2}/loom_code/approval.py +66 -12
- {loom_code-0.1.1 → loom_code-0.1.2}/loom_code.egg-info/PKG-INFO +5 -4
- {loom_code-0.1.1 → loom_code-0.1.2}/pyproject.toml +2 -2
- {loom_code-0.1.1 → loom_code-0.1.2}/tests/test_approval.py +75 -0
- {loom_code-0.1.1 → loom_code-0.1.2}/LICENSE +0 -0
- {loom_code-0.1.1 → loom_code-0.1.2}/loom_code/_post_commit.py +0 -0
- {loom_code-0.1.1 → loom_code-0.1.2}/loom_code/agent.py +0 -0
- {loom_code-0.1.1 → loom_code-0.1.2}/loom_code/browse/__init__.py +0 -0
- {loom_code-0.1.1 → loom_code-0.1.2}/loom_code/browse/act.py +0 -0
- {loom_code-0.1.1 → loom_code-0.1.2}/loom_code/browse/observe.py +0 -0
- {loom_code-0.1.1 → loom_code-0.1.2}/loom_code/browse/session.py +0 -0
- {loom_code-0.1.1 → loom_code-0.1.2}/loom_code/browse/verify.py +0 -0
- {loom_code-0.1.1 → loom_code-0.1.2}/loom_code/checkpoint.py +0 -0
- {loom_code-0.1.1 → loom_code-0.1.2}/loom_code/cli.py +0 -0
- {loom_code-0.1.1 → loom_code-0.1.2}/loom_code/code_index.py +0 -0
- {loom_code-0.1.1 → loom_code-0.1.2}/loom_code/compact.py +0 -0
- {loom_code-0.1.1 → loom_code-0.1.2}/loom_code/consent.py +0 -0
- {loom_code-0.1.1 → loom_code-0.1.2}/loom_code/credentials.py +0 -0
- {loom_code-0.1.1 → loom_code-0.1.2}/loom_code/edit_tool.py +0 -0
- {loom_code-0.1.1 → loom_code-0.1.2}/loom_code/extensions.py +0 -0
- {loom_code-0.1.1 → loom_code-0.1.2}/loom_code/file_history.py +0 -0
- {loom_code-0.1.1 → loom_code-0.1.2}/loom_code/file_tools.py +0 -0
- {loom_code-0.1.1 → loom_code-0.1.2}/loom_code/git_hook.py +0 -0
- {loom_code-0.1.1 → loom_code-0.1.2}/loom_code/grep_tool.py +0 -0
- {loom_code-0.1.1 → loom_code-0.1.2}/loom_code/hooks.py +0 -0
- {loom_code-0.1.1 → loom_code-0.1.2}/loom_code/loominit/__init__.py +0 -0
- {loom_code-0.1.1 → loom_code-0.1.2}/loom_code/loominit/_ast_walk.py +0 -0
- {loom_code-0.1.1 → loom_code-0.1.2}/loom_code/loominit/_files.py +0 -0
- {loom_code-0.1.1 → loom_code-0.1.2}/loom_code/loominit/_graph.py +0 -0
- {loom_code-0.1.1 → loom_code-0.1.2}/loom_code/loominit/_resolve.py +0 -0
- {loom_code-0.1.1 → loom_code-0.1.2}/loom_code/loominit/_tests_map.py +0 -0
- {loom_code-0.1.1 → loom_code-0.1.2}/loom_code/loominit/extractor.py +0 -0
- {loom_code-0.1.1 → loom_code-0.1.2}/loom_code/loominit/repomap.py +0 -0
- {loom_code-0.1.1 → loom_code-0.1.2}/loom_code/loominit/schema.py +0 -0
- {loom_code-0.1.1 → loom_code-0.1.2}/loom_code/lsp_tools.py +0 -0
- {loom_code-0.1.1 → loom_code-0.1.2}/loom_code/mcp_host.py +0 -0
- {loom_code-0.1.1 → loom_code-0.1.2}/loom_code/operator.py +0 -0
- {loom_code-0.1.1 → loom_code-0.1.2}/loom_code/paste.py +0 -0
- {loom_code-0.1.1 → loom_code-0.1.2}/loom_code/paths.py +0 -0
- {loom_code-0.1.1 → loom_code-0.1.2}/loom_code/permissions.py +0 -0
- {loom_code-0.1.1 → loom_code-0.1.2}/loom_code/project.py +0 -0
- {loom_code-0.1.1 → loom_code-0.1.2}/loom_code/prompts.py +0 -0
- {loom_code-0.1.1 → loom_code-0.1.2}/loom_code/render.py +0 -0
- {loom_code-0.1.1 → loom_code-0.1.2}/loom_code/repl.py +0 -0
- {loom_code-0.1.1 → loom_code-0.1.2}/loom_code/rules.py +0 -0
- {loom_code-0.1.1 → loom_code-0.1.2}/loom_code/sandboxed_bash.py +0 -0
- {loom_code-0.1.1 → loom_code-0.1.2}/loom_code/scribe.py +0 -0
- {loom_code-0.1.1 → loom_code-0.1.2}/loom_code/skills/__init__.py +0 -0
- {loom_code-0.1.1 → loom_code-0.1.2}/loom_code/skills/graphify/SKILL.md +0 -0
- {loom_code-0.1.1 → loom_code-0.1.2}/loom_code/skills/graphify/tools.py +0 -0
- {loom_code-0.1.1 → loom_code-0.1.2}/loom_code/trust.py +0 -0
- {loom_code-0.1.1 → loom_code-0.1.2}/loom_code/turn.py +0 -0
- {loom_code-0.1.1 → loom_code-0.1.2}/loom_code/web_fetch.py +0 -0
- {loom_code-0.1.1 → loom_code-0.1.2}/loom_code/workers.py +0 -0
- {loom_code-0.1.1 → loom_code-0.1.2}/loom_code/worktree.py +0 -0
- {loom_code-0.1.1 → loom_code-0.1.2}/loom_code.egg-info/SOURCES.txt +0 -0
- {loom_code-0.1.1 → loom_code-0.1.2}/loom_code.egg-info/dependency_links.txt +0 -0
- {loom_code-0.1.1 → loom_code-0.1.2}/loom_code.egg-info/entry_points.txt +0 -0
- {loom_code-0.1.1 → loom_code-0.1.2}/loom_code.egg-info/requires.txt +0 -0
- {loom_code-0.1.1 → loom_code-0.1.2}/loom_code.egg-info/top_level.txt +0 -0
- {loom_code-0.1.1 → loom_code-0.1.2}/setup.cfg +0 -0
- {loom_code-0.1.1 → loom_code-0.1.2}/tests/test_agent.py +0 -0
- {loom_code-0.1.1 → loom_code-0.1.2}/tests/test_antipoison_gate.py +0 -0
- {loom_code-0.1.1 → loom_code-0.1.2}/tests/test_approval_danger.py +0 -0
- {loom_code-0.1.1 → loom_code-0.1.2}/tests/test_approval_integration.py +0 -0
- {loom_code-0.1.1 → loom_code-0.1.2}/tests/test_checkpoint.py +0 -0
- {loom_code-0.1.1 → loom_code-0.1.2}/tests/test_code_index.py +0 -0
- {loom_code-0.1.1 → loom_code-0.1.2}/tests/test_compact.py +0 -0
- {loom_code-0.1.1 → loom_code-0.1.2}/tests/test_credentials.py +0 -0
- {loom_code-0.1.1 → loom_code-0.1.2}/tests/test_edit_tool.py +0 -0
- {loom_code-0.1.1 → loom_code-0.1.2}/tests/test_extensions.py +0 -0
- {loom_code-0.1.1 → loom_code-0.1.2}/tests/test_file_boundary.py +0 -0
- {loom_code-0.1.1 → loom_code-0.1.2}/tests/test_file_history.py +0 -0
- {loom_code-0.1.1 → loom_code-0.1.2}/tests/test_git_hook.py +0 -0
- {loom_code-0.1.1 → loom_code-0.1.2}/tests/test_graphify_file_discovery.py +0 -0
- {loom_code-0.1.1 → loom_code-0.1.2}/tests/test_graphify_query_tiers.py +0 -0
- {loom_code-0.1.1 → loom_code-0.1.2}/tests/test_graphify_wiring.py +0 -0
- {loom_code-0.1.1 → loom_code-0.1.2}/tests/test_grep_tool.py +0 -0
- {loom_code-0.1.1 → loom_code-0.1.2}/tests/test_learned_notes.py +0 -0
- {loom_code-0.1.1 → loom_code-0.1.2}/tests/test_loom_hooks.py +0 -0
- {loom_code-0.1.1 → loom_code-0.1.2}/tests/test_lsp_tools.py +0 -0
- {loom_code-0.1.1 → loom_code-0.1.2}/tests/test_mcp.py +0 -0
- {loom_code-0.1.1 → loom_code-0.1.2}/tests/test_paste.py +0 -0
- {loom_code-0.1.1 → loom_code-0.1.2}/tests/test_permissions.py +0 -0
- {loom_code-0.1.1 → loom_code-0.1.2}/tests/test_pricing.py +0 -0
- {loom_code-0.1.1 → loom_code-0.1.2}/tests/test_project.py +0 -0
- {loom_code-0.1.1 → loom_code-0.1.2}/tests/test_prompts.py +0 -0
- {loom_code-0.1.1 → loom_code-0.1.2}/tests/test_render.py +0 -0
- {loom_code-0.1.1 → loom_code-0.1.2}/tests/test_repl_guards.py +0 -0
- {loom_code-0.1.1 → loom_code-0.1.2}/tests/test_resume_migration.py +0 -0
- {loom_code-0.1.1 → loom_code-0.1.2}/tests/test_resume_preview.py +0 -0
- {loom_code-0.1.1 → loom_code-0.1.2}/tests/test_routing.py +0 -0
- {loom_code-0.1.1 → loom_code-0.1.2}/tests/test_rules.py +0 -0
- {loom_code-0.1.1 → loom_code-0.1.2}/tests/test_sandboxed_bash.py +0 -0
- {loom_code-0.1.1 → loom_code-0.1.2}/tests/test_stream_liveness.py +0 -0
- {loom_code-0.1.1 → loom_code-0.1.2}/tests/test_turn_economy.py +0 -0
- {loom_code-0.1.1 → loom_code-0.1.2}/tests/test_web_fetch.py +0 -0
- {loom_code-0.1.1 → loom_code-0.1.2}/tests/test_workers.py +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: loom-code
|
|
3
|
-
Version: 0.1.
|
|
3
|
+
Version: 0.1.2
|
|
4
4
|
Summary: loom-code — a loomflow-native terminal coding agent
|
|
5
5
|
Author: Anupam Nautiyal
|
|
6
6
|
License: MIT License
|
|
@@ -106,11 +106,12 @@ load-bearing — the agent loop, tools, planning, memory — is loomflow.
|
|
|
106
106
|
## Install
|
|
107
107
|
|
|
108
108
|
```bash
|
|
109
|
-
pipx install
|
|
109
|
+
pipx install loom-code
|
|
110
110
|
```
|
|
111
111
|
|
|
112
|
-
(`pip install` works too; `pipx` keeps CLI tools in their
|
|
113
|
-
No pipx? `brew install pipx` or
|
|
112
|
+
(`pip install loom-code` works too; `pipx` keeps CLI tools in their
|
|
113
|
+
own venvs. No pipx? `brew install pipx` or
|
|
114
|
+
`python -m pip install --user pipx`.)
|
|
114
115
|
|
|
115
116
|
Requires Python 3.11+. To update: `pipx upgrade loom-code`.
|
|
116
117
|
|
|
@@ -52,11 +52,12 @@ load-bearing — the agent loop, tools, planning, memory — is loomflow.
|
|
|
52
52
|
## Install
|
|
53
53
|
|
|
54
54
|
```bash
|
|
55
|
-
pipx install
|
|
55
|
+
pipx install loom-code
|
|
56
56
|
```
|
|
57
57
|
|
|
58
|
-
(`pip install` works too; `pipx` keeps CLI tools in their
|
|
59
|
-
No pipx? `brew install pipx` or
|
|
58
|
+
(`pip install loom-code` works too; `pipx` keeps CLI tools in their
|
|
59
|
+
own venvs. No pipx? `brew install pipx` or
|
|
60
|
+
`python -m pip install --user pipx`.)
|
|
60
61
|
|
|
61
62
|
Requires Python 3.11+. To update: `pipx upgrade loom-code`.
|
|
62
63
|
|
|
@@ -112,6 +112,30 @@ def _read_key_raw(fd: int) -> str:
|
|
|
112
112
|
return ch.lower()
|
|
113
113
|
|
|
114
114
|
|
|
115
|
+
def _read_key_msvcrt() -> str:
|
|
116
|
+
"""Windows equivalent of :func:`_read_key_raw` — one LOGICAL key
|
|
117
|
+
via ``msvcrt.getwch()`` (already raw + no echo, so no mode
|
|
118
|
+
setup/teardown is needed).
|
|
119
|
+
|
|
120
|
+
Arrow keys arrive as a TWO-event sequence: a ``'\\xe0'`` (or
|
|
121
|
+
``'\\x00'`` for some layouts) prefix, then ``'H'`` (up) /
|
|
122
|
+
``'P'`` (down). Ctrl-C surfaces as ``'\\x03'`` and maps to the
|
|
123
|
+
SAFE 'esc', mirroring the POSIX reader."""
|
|
124
|
+
import msvcrt
|
|
125
|
+
|
|
126
|
+
ch = msvcrt.getwch()
|
|
127
|
+
if ch in ("\r", "\n"):
|
|
128
|
+
return "enter"
|
|
129
|
+
if ch == "\x03": # Ctrl-C
|
|
130
|
+
return "esc"
|
|
131
|
+
if ch == "\x1b":
|
|
132
|
+
return "esc"
|
|
133
|
+
if ch in ("\xe0", "\x00"): # extended-key prefix
|
|
134
|
+
final = msvcrt.getwch()
|
|
135
|
+
return {"H": "up", "P": "down"}.get(final, "esc")
|
|
136
|
+
return ch.lower()
|
|
137
|
+
|
|
138
|
+
|
|
115
139
|
def _read_key() -> str:
|
|
116
140
|
"""Single-key read that manages its own raw-mode window. Prefer
|
|
117
141
|
:func:`_read_key_raw` inside a selector that enters raw mode ONCE
|
|
@@ -187,23 +211,54 @@ def _select_option(options: list[tuple[str, str]], default: int = 0) -> str:
|
|
|
187
211
|
out.write("\r\n") # explicit CR+LF for raw mode
|
|
188
212
|
out.flush()
|
|
189
213
|
|
|
190
|
-
#
|
|
214
|
+
# Pick the platform's key reader + raw-mode strategy.
|
|
215
|
+
#
|
|
216
|
+
# POSIX: enter raw mode ONCE for the whole selector session — no
|
|
191
217
|
# per-keypress termios churn, and no cooked-mode gap between keys
|
|
192
218
|
# where type-ahead would echo raw escape bytes onto the prompt.
|
|
193
|
-
|
|
194
|
-
import
|
|
219
|
+
#
|
|
220
|
+
# Windows: there is NO termios (the bare import crashed /set_model
|
|
221
|
+
# for pipx users with ModuleNotFoundError). msvcrt.getwch() is
|
|
222
|
+
# already raw + unbuffered, so no mode management is needed at
|
|
223
|
+
# all; ``os.system("")`` nudges legacy conhost into processing the
|
|
224
|
+
# ANSI redraw sequences (Windows Terminal has VT on by default).
|
|
225
|
+
def _restore() -> None:
|
|
226
|
+
return None
|
|
227
|
+
|
|
228
|
+
def _reader() -> str:
|
|
229
|
+
return _read_key()
|
|
195
230
|
|
|
196
|
-
fd = sys.stdin.fileno()
|
|
197
231
|
try:
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
232
|
+
import termios
|
|
233
|
+
import tty
|
|
234
|
+
|
|
235
|
+
fd = sys.stdin.fileno()
|
|
236
|
+
try:
|
|
237
|
+
old = termios.tcgetattr(fd)
|
|
238
|
+
except Exception:
|
|
239
|
+
old = None
|
|
240
|
+
if old is not None:
|
|
241
|
+
tty.setraw(fd)
|
|
242
|
+
|
|
243
|
+
def _reader() -> str:
|
|
244
|
+
return _read_key_raw(fd)
|
|
245
|
+
|
|
246
|
+
def _restore() -> None:
|
|
247
|
+
termios.tcsetattr(fd, termios.TCSADRAIN, old)
|
|
248
|
+
except ImportError:
|
|
249
|
+
try:
|
|
250
|
+
import msvcrt # noqa: F401 — probe: Windows console?
|
|
251
|
+
import os
|
|
252
|
+
|
|
253
|
+
os.system("") # enable VT processing on legacy conhost
|
|
254
|
+
_reader = _read_key_msvcrt
|
|
255
|
+
except ImportError:
|
|
256
|
+
pass # exotic platform → keep the _read_key fallback
|
|
257
|
+
|
|
203
258
|
try:
|
|
204
259
|
_draw(first=True)
|
|
205
260
|
while True:
|
|
206
|
-
key =
|
|
261
|
+
key = _reader()
|
|
207
262
|
if key == "up":
|
|
208
263
|
idx = (idx - 1) % n
|
|
209
264
|
elif key == "down":
|
|
@@ -221,8 +276,7 @@ def _select_option(options: list[tuple[str, str]], default: int = 0) -> str:
|
|
|
221
276
|
continue # unknown key: ignore, keep waiting
|
|
222
277
|
_draw(first=False)
|
|
223
278
|
finally:
|
|
224
|
-
|
|
225
|
-
termios.tcsetattr(fd, termios.TCSADRAIN, old)
|
|
279
|
+
_restore()
|
|
226
280
|
|
|
227
281
|
|
|
228
282
|
def _read_single_key() -> str:
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: loom-code
|
|
3
|
-
Version: 0.1.
|
|
3
|
+
Version: 0.1.2
|
|
4
4
|
Summary: loom-code — a loomflow-native terminal coding agent
|
|
5
5
|
Author: Anupam Nautiyal
|
|
6
6
|
License: MIT License
|
|
@@ -106,11 +106,12 @@ load-bearing — the agent loop, tools, planning, memory — is loomflow.
|
|
|
106
106
|
## Install
|
|
107
107
|
|
|
108
108
|
```bash
|
|
109
|
-
pipx install
|
|
109
|
+
pipx install loom-code
|
|
110
110
|
```
|
|
111
111
|
|
|
112
|
-
(`pip install` works too; `pipx` keeps CLI tools in their
|
|
113
|
-
No pipx? `brew install pipx` or
|
|
112
|
+
(`pip install loom-code` works too; `pipx` keeps CLI tools in their
|
|
113
|
+
own venvs. No pipx? `brew install pipx` or
|
|
114
|
+
`python -m pip install --user pipx`.)
|
|
114
115
|
|
|
115
116
|
Requires Python 3.11+. To update: `pipx upgrade loom-code`.
|
|
116
117
|
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
[project]
|
|
2
2
|
name = "loom-code"
|
|
3
|
-
version = "0.1.
|
|
3
|
+
version = "0.1.2"
|
|
4
4
|
description = "loom-code — a loomflow-native terminal coding agent"
|
|
5
5
|
readme = "README.md"
|
|
6
6
|
license = { file = "LICENSE" }
|
|
@@ -129,7 +129,7 @@ select = ["E", "F", "I", "B", "UP", "ASYNC"]
|
|
|
129
129
|
# publishing — no tokens). Daily flow stays plain git; ``make release
|
|
130
130
|
# BUMP=patch|minor|major`` is the explicit "this is a release" flag.
|
|
131
131
|
[tool.bumpversion]
|
|
132
|
-
current_version = "0.1.
|
|
132
|
+
current_version = "0.1.2"
|
|
133
133
|
search = "{current_version}"
|
|
134
134
|
replace = "{new_version}"
|
|
135
135
|
commit = true
|
|
@@ -70,3 +70,78 @@ async def test_approval_gate_allow_all_works_for_every_tool() -> None:
|
|
|
70
70
|
for tool in ("bash", "edit", "write"):
|
|
71
71
|
call = ToolCall(tool=tool, args={})
|
|
72
72
|
assert await gate.handler(call) is True
|
|
73
|
+
|
|
74
|
+
|
|
75
|
+
# ---- Windows key reader (msvcrt) -------------------------------------
|
|
76
|
+
# termios doesn't exist on Windows; the selector dispatches to
|
|
77
|
+
# _read_key_msvcrt there. We can't run real Windows in CI, but the
|
|
78
|
+
# reader's decode logic is pure — drive it with a fake msvcrt module.
|
|
79
|
+
|
|
80
|
+
|
|
81
|
+
def _fake_msvcrt(keys: list[str]):
|
|
82
|
+
"""A stand-in msvcrt whose getwch() pops from ``keys``."""
|
|
83
|
+
import types
|
|
84
|
+
|
|
85
|
+
mod = types.ModuleType("msvcrt")
|
|
86
|
+
seq = iter(keys)
|
|
87
|
+
mod.getwch = lambda: next(seq)
|
|
88
|
+
return mod
|
|
89
|
+
|
|
90
|
+
|
|
91
|
+
def test_msvcrt_reader_decodes_logical_keys(monkeypatch) -> None:
|
|
92
|
+
import sys as _sys
|
|
93
|
+
|
|
94
|
+
from loom_code.approval import _read_key_msvcrt
|
|
95
|
+
|
|
96
|
+
cases = [
|
|
97
|
+
(["\r"], "enter"),
|
|
98
|
+
(["\n"], "enter"),
|
|
99
|
+
(["\x03"], "esc"), # Ctrl-C → SAFE cancel
|
|
100
|
+
(["\x1b"], "esc"),
|
|
101
|
+
(["\xe0", "H"], "up"),
|
|
102
|
+
(["\xe0", "P"], "down"),
|
|
103
|
+
(["\x00", "H"], "up"), # alternate extended prefix
|
|
104
|
+
(["\xe0", "K"], "esc"), # unknown extended key → safe
|
|
105
|
+
(["3"], "3"),
|
|
106
|
+
(["Y"], "y"),
|
|
107
|
+
]
|
|
108
|
+
for keys, expected in cases:
|
|
109
|
+
monkeypatch.setitem(
|
|
110
|
+
_sys.modules, "msvcrt", _fake_msvcrt(keys)
|
|
111
|
+
)
|
|
112
|
+
assert _read_key_msvcrt() == expected, (keys, expected)
|
|
113
|
+
|
|
114
|
+
|
|
115
|
+
def test_select_option_survives_missing_termios(monkeypatch) -> None:
|
|
116
|
+
"""Regression: on Windows there is no termios — the selector's
|
|
117
|
+
bare ``import termios`` crashed /set_model with
|
|
118
|
+
ModuleNotFoundError for every pipx-on-Windows user. With the
|
|
119
|
+
dispatch fix, a missing termios falls through to the msvcrt
|
|
120
|
+
reader (faked here): ↓ then Enter must select option 2 and
|
|
121
|
+
never raise."""
|
|
122
|
+
import builtins
|
|
123
|
+
import sys as _sys
|
|
124
|
+
|
|
125
|
+
from loom_code import approval
|
|
126
|
+
|
|
127
|
+
real_import = builtins.__import__
|
|
128
|
+
|
|
129
|
+
def _no_termios(name, *args, **kwargs):
|
|
130
|
+
if name in ("termios", "tty"):
|
|
131
|
+
raise ImportError(f"No module named '{name}'")
|
|
132
|
+
return real_import(name, *args, **kwargs)
|
|
133
|
+
|
|
134
|
+
monkeypatch.setattr(builtins, "__import__", _no_termios)
|
|
135
|
+
monkeypatch.setitem(
|
|
136
|
+
_sys.modules, "msvcrt", _fake_msvcrt(["\xe0", "P", "\r"])
|
|
137
|
+
)
|
|
138
|
+
monkeypatch.delitem(_sys.modules, "termios", raising=False)
|
|
139
|
+
monkeypatch.delitem(_sys.modules, "tty", raising=False)
|
|
140
|
+
# stdin must look like a TTY to reach the interactive path
|
|
141
|
+
monkeypatch.setattr(
|
|
142
|
+
approval.sys.stdin, "isatty", lambda: True, raising=False
|
|
143
|
+
)
|
|
144
|
+
result = approval._select_option(
|
|
145
|
+
[("a", "first"), ("b", "second"), ("c", "third")]
|
|
146
|
+
)
|
|
147
|
+
assert result == "b" # ↓ moved 0→1, Enter picked it
|
|
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
|
|
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
|