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.
Files changed (101) hide show
  1. {loom_code-0.1.1 → loom_code-0.1.2}/PKG-INFO +5 -4
  2. {loom_code-0.1.1 → loom_code-0.1.2}/README.md +4 -3
  3. {loom_code-0.1.1 → loom_code-0.1.2}/loom_code/__init__.py +1 -1
  4. {loom_code-0.1.1 → loom_code-0.1.2}/loom_code/approval.py +66 -12
  5. {loom_code-0.1.1 → loom_code-0.1.2}/loom_code.egg-info/PKG-INFO +5 -4
  6. {loom_code-0.1.1 → loom_code-0.1.2}/pyproject.toml +2 -2
  7. {loom_code-0.1.1 → loom_code-0.1.2}/tests/test_approval.py +75 -0
  8. {loom_code-0.1.1 → loom_code-0.1.2}/LICENSE +0 -0
  9. {loom_code-0.1.1 → loom_code-0.1.2}/loom_code/_post_commit.py +0 -0
  10. {loom_code-0.1.1 → loom_code-0.1.2}/loom_code/agent.py +0 -0
  11. {loom_code-0.1.1 → loom_code-0.1.2}/loom_code/browse/__init__.py +0 -0
  12. {loom_code-0.1.1 → loom_code-0.1.2}/loom_code/browse/act.py +0 -0
  13. {loom_code-0.1.1 → loom_code-0.1.2}/loom_code/browse/observe.py +0 -0
  14. {loom_code-0.1.1 → loom_code-0.1.2}/loom_code/browse/session.py +0 -0
  15. {loom_code-0.1.1 → loom_code-0.1.2}/loom_code/browse/verify.py +0 -0
  16. {loom_code-0.1.1 → loom_code-0.1.2}/loom_code/checkpoint.py +0 -0
  17. {loom_code-0.1.1 → loom_code-0.1.2}/loom_code/cli.py +0 -0
  18. {loom_code-0.1.1 → loom_code-0.1.2}/loom_code/code_index.py +0 -0
  19. {loom_code-0.1.1 → loom_code-0.1.2}/loom_code/compact.py +0 -0
  20. {loom_code-0.1.1 → loom_code-0.1.2}/loom_code/consent.py +0 -0
  21. {loom_code-0.1.1 → loom_code-0.1.2}/loom_code/credentials.py +0 -0
  22. {loom_code-0.1.1 → loom_code-0.1.2}/loom_code/edit_tool.py +0 -0
  23. {loom_code-0.1.1 → loom_code-0.1.2}/loom_code/extensions.py +0 -0
  24. {loom_code-0.1.1 → loom_code-0.1.2}/loom_code/file_history.py +0 -0
  25. {loom_code-0.1.1 → loom_code-0.1.2}/loom_code/file_tools.py +0 -0
  26. {loom_code-0.1.1 → loom_code-0.1.2}/loom_code/git_hook.py +0 -0
  27. {loom_code-0.1.1 → loom_code-0.1.2}/loom_code/grep_tool.py +0 -0
  28. {loom_code-0.1.1 → loom_code-0.1.2}/loom_code/hooks.py +0 -0
  29. {loom_code-0.1.1 → loom_code-0.1.2}/loom_code/loominit/__init__.py +0 -0
  30. {loom_code-0.1.1 → loom_code-0.1.2}/loom_code/loominit/_ast_walk.py +0 -0
  31. {loom_code-0.1.1 → loom_code-0.1.2}/loom_code/loominit/_files.py +0 -0
  32. {loom_code-0.1.1 → loom_code-0.1.2}/loom_code/loominit/_graph.py +0 -0
  33. {loom_code-0.1.1 → loom_code-0.1.2}/loom_code/loominit/_resolve.py +0 -0
  34. {loom_code-0.1.1 → loom_code-0.1.2}/loom_code/loominit/_tests_map.py +0 -0
  35. {loom_code-0.1.1 → loom_code-0.1.2}/loom_code/loominit/extractor.py +0 -0
  36. {loom_code-0.1.1 → loom_code-0.1.2}/loom_code/loominit/repomap.py +0 -0
  37. {loom_code-0.1.1 → loom_code-0.1.2}/loom_code/loominit/schema.py +0 -0
  38. {loom_code-0.1.1 → loom_code-0.1.2}/loom_code/lsp_tools.py +0 -0
  39. {loom_code-0.1.1 → loom_code-0.1.2}/loom_code/mcp_host.py +0 -0
  40. {loom_code-0.1.1 → loom_code-0.1.2}/loom_code/operator.py +0 -0
  41. {loom_code-0.1.1 → loom_code-0.1.2}/loom_code/paste.py +0 -0
  42. {loom_code-0.1.1 → loom_code-0.1.2}/loom_code/paths.py +0 -0
  43. {loom_code-0.1.1 → loom_code-0.1.2}/loom_code/permissions.py +0 -0
  44. {loom_code-0.1.1 → loom_code-0.1.2}/loom_code/project.py +0 -0
  45. {loom_code-0.1.1 → loom_code-0.1.2}/loom_code/prompts.py +0 -0
  46. {loom_code-0.1.1 → loom_code-0.1.2}/loom_code/render.py +0 -0
  47. {loom_code-0.1.1 → loom_code-0.1.2}/loom_code/repl.py +0 -0
  48. {loom_code-0.1.1 → loom_code-0.1.2}/loom_code/rules.py +0 -0
  49. {loom_code-0.1.1 → loom_code-0.1.2}/loom_code/sandboxed_bash.py +0 -0
  50. {loom_code-0.1.1 → loom_code-0.1.2}/loom_code/scribe.py +0 -0
  51. {loom_code-0.1.1 → loom_code-0.1.2}/loom_code/skills/__init__.py +0 -0
  52. {loom_code-0.1.1 → loom_code-0.1.2}/loom_code/skills/graphify/SKILL.md +0 -0
  53. {loom_code-0.1.1 → loom_code-0.1.2}/loom_code/skills/graphify/tools.py +0 -0
  54. {loom_code-0.1.1 → loom_code-0.1.2}/loom_code/trust.py +0 -0
  55. {loom_code-0.1.1 → loom_code-0.1.2}/loom_code/turn.py +0 -0
  56. {loom_code-0.1.1 → loom_code-0.1.2}/loom_code/web_fetch.py +0 -0
  57. {loom_code-0.1.1 → loom_code-0.1.2}/loom_code/workers.py +0 -0
  58. {loom_code-0.1.1 → loom_code-0.1.2}/loom_code/worktree.py +0 -0
  59. {loom_code-0.1.1 → loom_code-0.1.2}/loom_code.egg-info/SOURCES.txt +0 -0
  60. {loom_code-0.1.1 → loom_code-0.1.2}/loom_code.egg-info/dependency_links.txt +0 -0
  61. {loom_code-0.1.1 → loom_code-0.1.2}/loom_code.egg-info/entry_points.txt +0 -0
  62. {loom_code-0.1.1 → loom_code-0.1.2}/loom_code.egg-info/requires.txt +0 -0
  63. {loom_code-0.1.1 → loom_code-0.1.2}/loom_code.egg-info/top_level.txt +0 -0
  64. {loom_code-0.1.1 → loom_code-0.1.2}/setup.cfg +0 -0
  65. {loom_code-0.1.1 → loom_code-0.1.2}/tests/test_agent.py +0 -0
  66. {loom_code-0.1.1 → loom_code-0.1.2}/tests/test_antipoison_gate.py +0 -0
  67. {loom_code-0.1.1 → loom_code-0.1.2}/tests/test_approval_danger.py +0 -0
  68. {loom_code-0.1.1 → loom_code-0.1.2}/tests/test_approval_integration.py +0 -0
  69. {loom_code-0.1.1 → loom_code-0.1.2}/tests/test_checkpoint.py +0 -0
  70. {loom_code-0.1.1 → loom_code-0.1.2}/tests/test_code_index.py +0 -0
  71. {loom_code-0.1.1 → loom_code-0.1.2}/tests/test_compact.py +0 -0
  72. {loom_code-0.1.1 → loom_code-0.1.2}/tests/test_credentials.py +0 -0
  73. {loom_code-0.1.1 → loom_code-0.1.2}/tests/test_edit_tool.py +0 -0
  74. {loom_code-0.1.1 → loom_code-0.1.2}/tests/test_extensions.py +0 -0
  75. {loom_code-0.1.1 → loom_code-0.1.2}/tests/test_file_boundary.py +0 -0
  76. {loom_code-0.1.1 → loom_code-0.1.2}/tests/test_file_history.py +0 -0
  77. {loom_code-0.1.1 → loom_code-0.1.2}/tests/test_git_hook.py +0 -0
  78. {loom_code-0.1.1 → loom_code-0.1.2}/tests/test_graphify_file_discovery.py +0 -0
  79. {loom_code-0.1.1 → loom_code-0.1.2}/tests/test_graphify_query_tiers.py +0 -0
  80. {loom_code-0.1.1 → loom_code-0.1.2}/tests/test_graphify_wiring.py +0 -0
  81. {loom_code-0.1.1 → loom_code-0.1.2}/tests/test_grep_tool.py +0 -0
  82. {loom_code-0.1.1 → loom_code-0.1.2}/tests/test_learned_notes.py +0 -0
  83. {loom_code-0.1.1 → loom_code-0.1.2}/tests/test_loom_hooks.py +0 -0
  84. {loom_code-0.1.1 → loom_code-0.1.2}/tests/test_lsp_tools.py +0 -0
  85. {loom_code-0.1.1 → loom_code-0.1.2}/tests/test_mcp.py +0 -0
  86. {loom_code-0.1.1 → loom_code-0.1.2}/tests/test_paste.py +0 -0
  87. {loom_code-0.1.1 → loom_code-0.1.2}/tests/test_permissions.py +0 -0
  88. {loom_code-0.1.1 → loom_code-0.1.2}/tests/test_pricing.py +0 -0
  89. {loom_code-0.1.1 → loom_code-0.1.2}/tests/test_project.py +0 -0
  90. {loom_code-0.1.1 → loom_code-0.1.2}/tests/test_prompts.py +0 -0
  91. {loom_code-0.1.1 → loom_code-0.1.2}/tests/test_render.py +0 -0
  92. {loom_code-0.1.1 → loom_code-0.1.2}/tests/test_repl_guards.py +0 -0
  93. {loom_code-0.1.1 → loom_code-0.1.2}/tests/test_resume_migration.py +0 -0
  94. {loom_code-0.1.1 → loom_code-0.1.2}/tests/test_resume_preview.py +0 -0
  95. {loom_code-0.1.1 → loom_code-0.1.2}/tests/test_routing.py +0 -0
  96. {loom_code-0.1.1 → loom_code-0.1.2}/tests/test_rules.py +0 -0
  97. {loom_code-0.1.1 → loom_code-0.1.2}/tests/test_sandboxed_bash.py +0 -0
  98. {loom_code-0.1.1 → loom_code-0.1.2}/tests/test_stream_liveness.py +0 -0
  99. {loom_code-0.1.1 → loom_code-0.1.2}/tests/test_turn_economy.py +0 -0
  100. {loom_code-0.1.1 → loom_code-0.1.2}/tests/test_web_fetch.py +0 -0
  101. {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.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 git+https://github.com/Anurich/loomflow-cli
109
+ pipx install loom-code
110
110
  ```
111
111
 
112
- (`pip install` works too; `pipx` keeps CLI tools in their own venvs.
113
- No pipx? `brew install pipx` or `python -m pip install --user pipx`.)
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 git+https://github.com/Anurich/loomflow-cli
55
+ pipx install loom-code
56
56
  ```
57
57
 
58
- (`pip install` works too; `pipx` keeps CLI tools in their own venvs.
59
- No pipx? `brew install pipx` or `python -m pip install --user pipx`.)
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
 
@@ -19,4 +19,4 @@ belongs in the framework, not here. loom-code is the dogfood test
19
19
  that keeps loomflow honest.
20
20
  """
21
21
 
22
- __version__ = "0.1.1"
22
+ __version__ = "0.1.2"
@@ -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
- # Enter raw mode ONCE for the whole selector session — no
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
- import termios
194
- import tty
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
- old = termios.tcgetattr(fd)
199
- except Exception:
200
- old = None
201
- if old is not None:
202
- tty.setraw(fd)
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 = _read_key_raw(fd) if old is not None else _read_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
- if old is not None:
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.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 git+https://github.com/Anurich/loomflow-cli
109
+ pipx install loom-code
110
110
  ```
111
111
 
112
- (`pip install` works too; `pipx` keeps CLI tools in their own venvs.
113
- No pipx? `brew install pipx` or `python -m pip install --user pipx`.)
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.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.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