weio-cli 0.5.0__tar.gz → 0.6.0__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 (23) hide show
  1. {weio_cli-0.5.0/src/weio_cli.egg-info → weio_cli-0.6.0}/PKG-INFO +1 -1
  2. {weio_cli-0.5.0 → weio_cli-0.6.0}/pyproject.toml +1 -1
  3. {weio_cli-0.5.0 → weio_cli-0.6.0}/src/weio_cli/__init__.py +1 -1
  4. {weio_cli-0.5.0 → weio_cli-0.6.0}/src/weio_cli/agent.py +30 -1
  5. {weio_cli-0.5.0 → weio_cli-0.6.0}/src/weio_cli/tools.py +13 -0
  6. {weio_cli-0.5.0 → weio_cli-0.6.0/src/weio_cli.egg-info}/PKG-INFO +1 -1
  7. {weio_cli-0.5.0 → weio_cli-0.6.0}/tests/test_agent.py +15 -0
  8. {weio_cli-0.5.0 → weio_cli-0.6.0}/LICENSE +0 -0
  9. {weio_cli-0.5.0 → weio_cli-0.6.0}/README.md +0 -0
  10. {weio_cli-0.5.0 → weio_cli-0.6.0}/setup.cfg +0 -0
  11. {weio_cli-0.5.0 → weio_cli-0.6.0}/src/weio_cli/__main__.py +0 -0
  12. {weio_cli-0.5.0 → weio_cli-0.6.0}/src/weio_cli/browser_login.py +0 -0
  13. {weio_cli-0.5.0 → weio_cli-0.6.0}/src/weio_cli/cli.py +0 -0
  14. {weio_cli-0.5.0 → weio_cli-0.6.0}/src/weio_cli/client.py +0 -0
  15. {weio_cli-0.5.0 → weio_cli-0.6.0}/src/weio_cli/config.py +0 -0
  16. {weio_cli-0.5.0 → weio_cli-0.6.0}/src/weio_cli/settings.py +0 -0
  17. {weio_cli-0.5.0 → weio_cli-0.6.0}/src/weio_cli/tui.py +0 -0
  18. {weio_cli-0.5.0 → weio_cli-0.6.0}/src/weio_cli/updater.py +0 -0
  19. {weio_cli-0.5.0 → weio_cli-0.6.0}/src/weio_cli.egg-info/SOURCES.txt +0 -0
  20. {weio_cli-0.5.0 → weio_cli-0.6.0}/src/weio_cli.egg-info/dependency_links.txt +0 -0
  21. {weio_cli-0.5.0 → weio_cli-0.6.0}/src/weio_cli.egg-info/entry_points.txt +0 -0
  22. {weio_cli-0.5.0 → weio_cli-0.6.0}/src/weio_cli.egg-info/requires.txt +0 -0
  23. {weio_cli-0.5.0 → weio_cli-0.6.0}/src/weio_cli.egg-info/top_level.txt +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: weio-cli
3
- Version: 0.5.0
3
+ Version: 0.6.0
4
4
  Summary: Weio — an agentic coding assistant that routes inference through your Weio account.
5
5
  Author: We I/O Labs
6
6
  License: MIT
@@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"
4
4
 
5
5
  [project]
6
6
  name = "weio-cli"
7
- version = "0.5.0"
7
+ version = "0.6.0"
8
8
  description = "Weio — an agentic coding assistant that routes inference through your Weio account."
9
9
  readme = "README.md"
10
10
  requires-python = ">=3.9"
@@ -1,3 +1,3 @@
1
1
  """Weio CLI — an agentic coding assistant that routes inference through your Weio account."""
2
2
 
3
- __version__ = "0.5.0"
3
+ __version__ = "0.6.0"
@@ -9,6 +9,7 @@ UI is injected via callbacks so the TUI and one-shot runner share this engine.
9
9
  from __future__ import annotations
10
10
 
11
11
  import json
12
+ import platform
12
13
  import re
13
14
  from dataclasses import dataclass
14
15
  from pathlib import Path
@@ -23,13 +24,26 @@ _BARE_RE = re.compile(r'\{[^{}]*"tool"\s*:\s*"[a-z_]+".*?\}', re.DOTALL)
23
24
 
24
25
  READONLY_TOOLS = {"read_file", "list_dir", "search"}
25
26
  WRITE_TOOLS = {"write_file", "edit_file"}
26
- ALL_TOOLS = READONLY_TOOLS | WRITE_TOOLS | {"run_command", "finish"}
27
+ ALL_TOOLS = READONLY_TOOLS | WRITE_TOOLS | {"make_dir", "run_command", "finish"}
28
+
29
+
30
+ def _os_line() -> str:
31
+ sysname = platform.system() # 'Windows' | 'Darwin' | 'Linux'
32
+ if sysname == "Windows":
33
+ return ("Windows (cmd.exe). Use Windows path/command syntax. ALWAYS wrap any "
34
+ "path containing spaces in double quotes. Prefer the make_dir and "
35
+ "write_file tools over shell `mkdir`/redirection — they handle spaces safely.")
36
+ nice = "macOS" if sysname == "Darwin" else sysname
37
+ return (f"{nice} (POSIX shell). Quote any path containing spaces. Prefer make_dir "
38
+ "and write_file over shell mkdir/redirection.")
27
39
 
28
40
 
29
41
  def system_prompt(root: Path) -> str:
30
42
  return f"""You are Weio, an autonomous coding agent working in the user's project directory:
31
43
  {root}
32
44
 
45
+ Environment: {_os_line()}
46
+
33
47
  You complete the user's task by using TOOLS, one step at a time. On each turn, briefly
34
48
  state what you are about to do (one or two sentences), then emit one or more tool calls
35
49
  as fenced JSON blocks exactly like this:
@@ -42,6 +56,7 @@ TOOLS:
42
56
  - read_file {{"path": "..."}} read a file
43
57
  - list_dir {{"path": "."}} list a directory
44
58
  - search {{"query": "...", "path": "."}} search file contents
59
+ - make_dir {{"path": "..."}} create a directory (handles spaces safely)
45
60
  - write_file {{"path": "...", "content": "..."}} create/overwrite a file
46
61
  - edit_file {{"path": "...", "search": "...", "replace": "..."}} replace an exact snippet
47
62
  - run_command {{"command": "..."}} run a shell command in the project
@@ -118,6 +133,7 @@ class Agent:
118
133
  self.approval = settings.get("approval", "suggest")
119
134
  self.max_steps = int(settings.get("max_steps", 25))
120
135
  self.max_tokens = int(settings.get("max_tokens", 4096))
136
+ self._failed_cmds: dict[str, int] = {}
121
137
 
122
138
  # -- model call --
123
139
  def _complete(self) -> str:
@@ -143,6 +159,8 @@ class Agent:
143
159
  r = tools.list_dir(self.root, args.get("path", "."))
144
160
  elif tool == "search":
145
161
  r = tools.search_files(self.root, args.get("query", ""), args.get("path", "."))
162
+ elif tool == "make_dir":
163
+ r = tools.make_dir(self.root, args.get("path", ""))
146
164
  elif tool == "write_file":
147
165
  r = tools.write_file(self.root, args.get("path", ""), args.get("content", ""))
148
166
  r = self._maybe_commit(tool, args, r)
@@ -156,6 +174,17 @@ class Agent:
156
174
  r = ToolResult(False, "Command declined by user.", display=f"$ {cmd} (declined)")
157
175
  else:
158
176
  r = tools.run_command(self.root, cmd)
177
+ # Loop guard: if this exact command already failed, tell the model
178
+ # to change approach instead of repeating it.
179
+ if not r.ok:
180
+ n = self._failed_cmds.get(cmd, 0) + 1
181
+ self._failed_cmds[cmd] = n
182
+ if n >= 2:
183
+ r = ToolResult(False, r.output + (
184
+ "\n\nNOTE: this exact command has now failed "
185
+ f"{n} times. Do NOT run it again. Change approach — e.g. use the "
186
+ "make_dir/write_file tools, or fix quoting (wrap paths with spaces "
187
+ "in double quotes)."), display=r.display)
159
188
  else:
160
189
  r = ToolResult(False, f"Unknown tool: {tool}")
161
190
 
@@ -146,6 +146,19 @@ def commit_write(root: Path, path: str, new_text: str) -> bool:
146
146
  return True
147
147
 
148
148
 
149
+ def make_dir(root: Path, path: str) -> ToolResult:
150
+ """Create a directory (and parents) without the shell — avoids cross-platform
151
+ quoting pitfalls (e.g. spaces in Windows paths)."""
152
+ dp = _resolve(root, path)
153
+ if dp is None:
154
+ return ToolResult(False, f"Refused: '{path}' is outside the project.")
155
+ try:
156
+ dp.mkdir(parents=True, exist_ok=True)
157
+ except OSError as e:
158
+ return ToolResult(False, f"Cannot create {path}: {e}")
159
+ return ToolResult(True, f"created directory {path}", display=f"mkdir {path}")
160
+
161
+
149
162
  def run_command(root: Path, command: str, timeout: float = 120.0) -> ToolResult:
150
163
  try:
151
164
  proc = subprocess.run(
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: weio-cli
3
- Version: 0.5.0
3
+ Version: 0.6.0
4
4
  Summary: Weio — an agentic coding assistant that routes inference through your Weio account.
5
5
  Author: We I/O Labs
6
6
  License: MIT
@@ -60,6 +60,21 @@ def test_run_command(tmp_path):
60
60
  assert r.ok and "weio-ok" in r.output
61
61
 
62
62
 
63
+ def test_make_dir_with_spaces(tmp_path):
64
+ r = tools.make_dir(tmp_path, "My Project Folder")
65
+ assert r.ok and (tmp_path / "My Project Folder").is_dir()
66
+
67
+
68
+ def test_make_dir_nested(tmp_path):
69
+ r = tools.make_dir(tmp_path, "a/b/c")
70
+ assert r.ok and (tmp_path / "a" / "b" / "c").is_dir()
71
+
72
+
73
+ def test_make_dir_confined(tmp_path):
74
+ r = tools.make_dir(tmp_path, "../escape")
75
+ assert not r.ok and "outside" in r.output.lower()
76
+
77
+
63
78
  # ---- action parsing ----
64
79
  def test_parse_single_action():
65
80
  text = 'I will read it.\n```action\n{"tool": "read_file", "args": {"path": "a.py"}}\n```'
File without changes
File without changes
File without changes
File without changes
File without changes