mycode-cli 0.5.2__py3-none-any.whl → 0.5.4__py3-none-any.whl

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.
mycode_cli/main.py CHANGED
@@ -196,7 +196,7 @@ def run(
196
196
  def web(
197
197
  hostname: Annotated[str, typer.Option(help="Hostname to listen on")] = "127.0.0.1",
198
198
  port: Annotated[int | None, typer.Option(help="Port to listen on")] = None,
199
- dev: Annotated[bool, typer.Option(help="API-only backend for web UI dev workflows")] = False,
199
+ dev: Annotated[bool, typer.Option(help="API-only backend with hot reload for web UI dev workflows")] = False,
200
200
  ) -> None:
201
201
  """Start the web server."""
202
202
 
@@ -206,9 +206,17 @@ def web(
206
206
 
207
207
  import uvicorn
208
208
 
209
- from mycode_cli.server.app import create_app
209
+ app_factory = "mycode_cli.server.app:create_web_app"
210
+ if dev:
211
+ app_factory = "mycode_cli.server.app:create_api_app"
210
212
 
211
- uvicorn.run(create_app(serve_web=not dev), host=hostname, port=resolved_port)
213
+ uvicorn.run(
214
+ app_factory,
215
+ host=hostname,
216
+ port=resolved_port,
217
+ reload=dev,
218
+ factory=True,
219
+ )
212
220
 
213
221
 
214
222
  @session_app.command("list")
mycode_cli/server/app.py CHANGED
@@ -49,4 +49,14 @@ def create_app(*, serve_web: bool = True) -> FastAPI:
49
49
  return application
50
50
 
51
51
 
52
+ def create_web_app() -> FastAPI:
53
+ """Create the app with packaged web assets."""
54
+ return create_app(serve_web=True)
55
+
56
+
57
+ def create_api_app() -> FastAPI:
58
+ """Create the API-only app."""
59
+ return create_app(serve_web=False)
60
+
61
+
52
62
  app = create_app()
mycode_cli/tui/chat.py CHANGED
@@ -14,8 +14,10 @@ from uuid import uuid4
14
14
 
15
15
  from prompt_toolkit import PromptSession
16
16
  from prompt_toolkit.application import Application, get_app
17
+ from prompt_toolkit.buffer import Buffer
17
18
  from prompt_toolkit.completion import CompleteEvent, Completer, Completion
18
19
  from prompt_toolkit.document import Document
20
+ from prompt_toolkit.filters import has_completions
19
21
  from prompt_toolkit.formatted_text import ANSI, StyleAndTextTuples
20
22
  from prompt_toolkit.history import FileHistory
21
23
  from prompt_toolkit.key_binding import KeyBindings
@@ -63,7 +65,7 @@ _COMMAND_HELP = (
63
65
  )
64
66
  _SLASH_COMMANDS = tuple(command for command, _ in _COMMAND_HELP)
65
67
  # Only treat `@path` as a reference when it starts a standalone token.
66
- _AT_PATH_RE = re.compile(r"(?<!\S)@(?:(?P<quote>['\"])(?P<quoted>[^'\"]*)|(?P<plain>[^\s'\"]*))$")
68
+ _AT_PATH_RE = re.compile(r"""(?<!\S)@(?:'(?P<single>[^']*)'?$|"(?P<double>[^"]*)"?$|(?P<plain>[^\s'"]*))$""")
67
69
 
68
70
 
69
71
  # Style for the focused row in the inline selector.
@@ -133,8 +135,13 @@ class _PromptCompleter(Completer):
133
135
  if self._cwd:
134
136
  match = _AT_PATH_RE.search(text_before_cursor)
135
137
  if match:
136
- quote = str(match.group("quote") or "")
137
- query = str(match.group("quoted") or match.group("plain") or "")
138
+ if (query := match.group("single")) is not None:
139
+ quote = "'"
140
+ elif (query := match.group("double")) is not None:
141
+ quote = '"'
142
+ else:
143
+ quote = ""
144
+ query = str(match.group("plain") or "")
138
145
  # Complete only real paths under the current working directory.
139
146
  if query == "~":
140
147
  base_prefix = "~/"
@@ -155,11 +162,12 @@ class _PromptCompleter(Completer):
155
162
  if partial and not entry.name.startswith(partial):
156
163
  continue
157
164
  candidate = f"{base_prefix}{entry.name}{'/' if entry.is_dir() else ''}"
158
- replacement = "@" + shlex.quote(candidate)
159
165
  if quote:
160
- replacement = f"@{quote}{candidate}"
161
- if not entry.is_dir():
162
- replacement += quote
166
+ replacement = f"@{quote}{candidate}{quote}"
167
+ elif any(ch.isspace() for ch in candidate):
168
+ replacement = "@" + shlex.quote(candidate)
169
+ else:
170
+ replacement = "@" + candidate
163
171
  yield Completion(
164
172
  replacement,
165
173
  start_position=-len(match.group(0)),
@@ -193,6 +201,13 @@ def _rewrite_pasted_file_paths(text: str) -> str | None:
193
201
  return " ".join(f"@{shlex.quote(str(path))}" for path in paths)
194
202
 
195
203
 
204
+ async def _restart_completion(buffer: Buffer) -> None:
205
+ """Restart completion on the next loop tick after accepting a directory."""
206
+
207
+ await asyncio.sleep(0)
208
+ buffer.start_completion(select_first=True)
209
+
210
+
196
211
  def _build_chat_key_bindings() -> KeyBindings:
197
212
  """Build key bindings for the main chat prompt."""
198
213
  kb = KeyBindings()
@@ -203,10 +218,21 @@ def _build_chat_key_bindings() -> KeyBindings:
203
218
  kb.add("c-l")(_clear)
204
219
 
205
220
  # In multiline mode the default Enter inserts a newline; override it to submit.
221
+ @kb.add("enter", filter=has_completions, eager=True)
222
+ def _accept_selected_completion(event: KeyPressEvent) -> None:
223
+ buffer = event.current_buffer
224
+ state = buffer.complete_state
225
+ if state is None or not state.completions:
226
+ return
227
+ completion = state.current_completion or state.completions[0]
228
+ buffer.apply_completion(completion)
229
+ if completion.display_meta_text == "dir":
230
+ get_app().create_background_task(_restart_completion(buffer))
231
+
206
232
  def _submit(event: KeyPressEvent) -> None:
207
233
  event.current_buffer.validate_and_handle()
208
234
 
209
- kb.add("enter", eager=True)(_submit)
235
+ kb.add("enter", filter=~has_completions, eager=True)(_submit)
210
236
 
211
237
  # Esc+Enter (Meta+Enter) inserts a newline for multiline input.
212
238
  def _insert_newline(event: KeyPressEvent) -> None:
mycode_cli/tui/render.py CHANGED
@@ -550,7 +550,7 @@ class ReplyRenderer:
550
550
 
551
551
  def _stop_tool_live(self) -> None:
552
552
  if self._tool_live is not None:
553
- self._tool_live.stop()
553
+ self._clear_live(self._tool_live)
554
554
  self._tool_live = None
555
555
 
556
556
  @staticmethod
@@ -669,12 +669,7 @@ class ReplyRenderer:
669
669
  self._thinking_collapsed = True
670
670
 
671
671
  if self._live is not None:
672
- # Rich's stop() refreshes the last renderable once more before
673
- # restoring the cursor. Blank it first so a final spinner frame
674
- # can't get stranded in terminals that occasionally miss the clear.
675
- self._live.transient = True
676
- self._live.update(Text(""))
677
- self._live.stop()
672
+ self._clear_live(self._live)
678
673
  self._live = None
679
674
 
680
675
  duration = ""
@@ -714,7 +709,10 @@ class ReplyRenderer:
714
709
  """Stop live rendering and clear transient buffers for the current phase."""
715
710
 
716
711
  if self._live is not None:
717
- self._live.stop()
712
+ if self._text:
713
+ self._live.stop()
714
+ else:
715
+ self._clear_live(self._live)
718
716
  self._live = None
719
717
  self._stop_tool_live()
720
718
  self._reasoning.clear()
@@ -723,6 +721,12 @@ class ReplyRenderer:
723
721
  self._thinking_collapsed = False
724
722
  self._thinking_start_time = None
725
723
 
724
+ @staticmethod
725
+ def _clear_live(live: Live) -> None:
726
+ live.transient = True
727
+ live.update(Text(""))
728
+ live.stop()
729
+
726
730
  def _build_live_renderable(self) -> Spinner | _LeftMarkdown:
727
731
  """Build the Rich renderable used while a reply is streaming."""
728
732
 
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: mycode-cli
3
- Version: 0.5.2
3
+ Version: 0.5.4
4
4
  Summary: Minimal coding agent CLI and web UI built on mycode-sdk.
5
5
  Project-URL: Homepage, https://github.com/legibet/mycode
6
6
  Project-URL: Repository, https://github.com/legibet/mycode
@@ -22,7 +22,7 @@ Classifier: Programming Language :: Python :: 3.13
22
22
  Classifier: Topic :: Software Development
23
23
  Requires-Python: >=3.12
24
24
  Requires-Dist: fastapi>=0.115.0
25
- Requires-Dist: mycode-sdk==0.5.2
25
+ Requires-Dist: mycode-sdk==0.5.4
26
26
  Requires-Dist: prompt-toolkit>=3.0.52
27
27
  Requires-Dist: pyyaml>=6.0.0
28
28
  Requires-Dist: rich>=14.2.0
@@ -40,7 +40,6 @@ A minimal coding agent. Inspired by [pi](https://github.com/badlogic/pi-mono).
40
40
  - 4 built-in tools only (`read`, `write`, `edit`, `bash`), expanded via skills.
41
41
  - Mobile-friendly web UI.
42
42
  - Native image and pdf input support.
43
- - Lightweight Python SDK for building custom agents.
44
43
 
45
44
  ## Quick Start
46
45
 
@@ -185,25 +184,19 @@ uv build --package mycode-cli
185
184
 
186
185
  ## mycode-sdk
187
186
 
188
- Agent core as a lightweight Python SDK for building agents. Install via: `uv add mycode-sdk`
187
+ Agent core of mycode as a lightweight Python SDK for building custom agents. Install via: `uv add mycode-sdk`
189
188
 
190
189
  ```python
191
- import asyncio
192
- from mycode import Agent, bash_tool, read_tool
193
-
194
- async def main() -> None:
195
- agent = Agent(
196
- model="claude-sonnet-4-6",
197
- api_key="...",
198
- cwd=".",
199
- system="You are a concise coding assistant.",
200
- tools=[read_tool, bash_tool],
201
- )
202
- async for event in agent.achat("Read pyproject.toml and tell me the project name."):
203
- if event.type == "text":
204
- print(event.data["delta"], end="")
205
-
206
- asyncio.run(main())
190
+ from mycode import Agent, read_tool
191
+
192
+ agent = Agent(
193
+ model="claude-sonnet-4-6",
194
+ api_key="...",
195
+ tools=[read_tool],
196
+ )
197
+
198
+ result = agent.run("Read pyproject.toml and tell me the project name.")
199
+ print(result.text)
207
200
  ```
208
201
 
209
202
  See [mycode/README.md](mycode/README.md) for details.
@@ -1,10 +1,10 @@
1
1
  mycode_cli/__init__.py,sha256=qsnZt6kWVPR_j5x_544x9vs1nyKuqC_tq1J1La7spYE,78
2
2
  mycode_cli/config.py,sha256=Zd7TmgZYN91EL7O5N3XuvO4GM3XPQ0L7BQP4MJ_4YZo,17299
3
- mycode_cli/main.py,sha256=zkQ56CGpnNMv91miqsM6EFpztE_zQ9LnZ10r94lf8HI,7325
3
+ mycode_cli/main.py,sha256=5X9pSaKOEfDyo43aDut05_piSecJDh7Clv4brGv2a8I,7477
4
4
  mycode_cli/runtime.py,sha256=Th8QF9Fm62pAPLdTxS6El4XtHME46q5wSJznIU576n0,8657
5
5
  mycode_cli/system_prompt.py,sha256=M8xTJ24bjU7JqlWf29u4nBegVokBncAND6Re5y4QXoo,10549
6
6
  mycode_cli/server/__init__.py,sha256=Bt8J0HJdkPdE2VQl7zjnEjofriUHksen5tHQ8agw7RM,32
7
- mycode_cli/server/app.py,sha256=9QvJC6e1-QwrLaRYrYNKxYp6Iylv_ZreoEWphH4rPTk,1448
7
+ mycode_cli/server/app.py,sha256=N1zUM1iQbIYcpwneHbFAdgo-ucQkoOw5mXbzItnz9GY,1681
8
8
  mycode_cli/server/deps.py,sha256=BcNTWoZ1wMMll-6un6wGaRDI_83wR6VYgugcjw2O9GA,733
9
9
  mycode_cli/server/run_manager.py,sha256=VcSk0g9-OwwYIoafBwofq3Qt-L7LSzpPMX-cB4ho-HY,6243
10
10
  mycode_cli/server/schemas.py,sha256=v9qdOgDE5zKVNpPL5K9sVPQd--uQXMTXTwSthF4ahEg,1711
@@ -378,11 +378,11 @@ mycode_cli/server/static/assets/yaml-Buea-lGh.js,sha256=p6i0_BT6e6M4WY9hGFo6E-Sd
378
378
  mycode_cli/server/static/assets/zenscript-DVFEvuxE.js,sha256=WKgS641xEh7jvW5RO4ogVVvsRPlo98N4vX7L_eBYZqc,3912
379
379
  mycode_cli/server/static/assets/zig-VOosw3JB.js,sha256=fDaSkledibeB6Rt2pN8b-6YEKHx6XixCaSWTXvCMCEE,5340
380
380
  mycode_cli/tui/__init__.py,sha256=5sPlJS3c8HdNEBwcF0bUuj8f8brAD-_6MNmF4Q_1Edg,69
381
- mycode_cli/tui/chat.py,sha256=iW0xC5SVyTOiVnTbm3joyAlMrv_ZL6JrMTTWbU7d4aU,25171
382
- mycode_cli/tui/render.py,sha256=mp59CieMVUGYpdLXE-uwPoUvgcTyfJUHAjQq_x1JKd0,26633
381
+ mycode_cli/tui/chat.py,sha256=Y8Jvh2zPiP33APLD2FF6A2CYGpu3IDEzXPvkOpIzxWw,26255
382
+ mycode_cli/tui/render.py,sha256=FgrJ_xYcgiUIcNjjHQz3bWTL3tiRCBn4dNowOenYipE,26578
383
383
  mycode_cli/tui/theme.py,sha256=HS3C1kSoIONrSaiSx22AWRLk1n6-T-Tn5iohCMewAg4,3455
384
- mycode_cli-0.5.2.dist-info/METADATA,sha256=yUyMeOM1Ia4PPHcLuRD4TuU1cpVppVxdkK2ZEVo6mSo,6416
385
- mycode_cli-0.5.2.dist-info/WHEEL,sha256=QccIxa26bgl1E6uMy58deGWi-0aeIkkangHcxk2kWfw,87
386
- mycode_cli-0.5.2.dist-info/entry_points.txt,sha256=1R7YN8dxJUy5AMC4q2J5Ly9xuEom3ZbTaa8xXwxuZyk,48
387
- mycode_cli-0.5.2.dist-info/licenses/LICENSE,sha256=DSE6T-B9WhrWypr6H7y2kTZzoF1tef5xf7veUyImQsM,1064
388
- mycode_cli-0.5.2.dist-info/RECORD,,
384
+ mycode_cli-0.5.4.dist-info/METADATA,sha256=iaUK9SDcHu8E2P60djVRPmyh6E1LU3EvwwT1uMR4ZwI,6128
385
+ mycode_cli-0.5.4.dist-info/WHEEL,sha256=QccIxa26bgl1E6uMy58deGWi-0aeIkkangHcxk2kWfw,87
386
+ mycode_cli-0.5.4.dist-info/entry_points.txt,sha256=1R7YN8dxJUy5AMC4q2J5Ly9xuEom3ZbTaa8xXwxuZyk,48
387
+ mycode_cli-0.5.4.dist-info/licenses/LICENSE,sha256=DSE6T-B9WhrWypr6H7y2kTZzoF1tef5xf7veUyImQsM,1064
388
+ mycode_cli-0.5.4.dist-info/RECORD,,