nexcoder 0.1.0__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.
nex/tools/search.py ADDED
@@ -0,0 +1,156 @@
1
+ """File search tool — ripgrep wrapper with Python fallback."""
2
+
3
+ from __future__ import annotations
4
+
5
+ import asyncio
6
+ import re
7
+ from pathlib import Path
8
+
9
+ from nex.tools import ToolResult
10
+
11
+
12
+ async def search_files(
13
+ pattern: str,
14
+ path: str,
15
+ project_dir: Path,
16
+ max_results: int = 50,
17
+ ) -> ToolResult:
18
+ """Search files using ripgrep or fallback to Python grep.
19
+
20
+ Args:
21
+ pattern: Regex pattern to search for.
22
+ path: Directory to search (relative to project root).
23
+ project_dir: Project root directory.
24
+ max_results: Maximum number of matches to return.
25
+
26
+ Returns:
27
+ ToolResult with matches in file:line:content format.
28
+ """
29
+ search_dir = (project_dir / path).resolve()
30
+ project_root = project_dir.resolve()
31
+ if not str(search_dir).startswith(str(project_root)):
32
+ return ToolResult(success=False, output="", error="Path traversal blocked")
33
+
34
+ if not search_dir.is_dir():
35
+ return ToolResult(success=False, output="", error=f"Directory not found: {path}")
36
+
37
+ # Try ripgrep first
38
+ result = await _search_ripgrep(pattern, search_dir, max_results)
39
+ if result is not None:
40
+ return result
41
+
42
+ # Fallback to Python search
43
+ return _search_python(pattern, search_dir, project_root, max_results)
44
+
45
+
46
+ async def _search_ripgrep(pattern: str, search_dir: Path, max_results: int) -> ToolResult | None:
47
+ """Attempt search using ripgrep (rg).
48
+
49
+ Returns None if rg is not available.
50
+ """
51
+ try:
52
+ process = await asyncio.create_subprocess_exec(
53
+ "rg",
54
+ "--no-heading",
55
+ "--line-number",
56
+ "--context",
57
+ "2",
58
+ "--max-count",
59
+ str(max_results),
60
+ "--color",
61
+ "never",
62
+ pattern,
63
+ str(search_dir),
64
+ stdout=asyncio.subprocess.PIPE,
65
+ stderr=asyncio.subprocess.PIPE,
66
+ )
67
+
68
+ stdout, stderr = await asyncio.wait_for(process.communicate(), timeout=15)
69
+
70
+ if process.returncode == 2:
71
+ # rg error (bad pattern, etc.)
72
+ return ToolResult(
73
+ success=False,
74
+ output="",
75
+ error=f"Search error: {stderr.decode('utf-8', errors='replace')}",
76
+ )
77
+
78
+ output = stdout.decode("utf-8", errors="replace")
79
+ if not output.strip():
80
+ return ToolResult(success=True, output="No matches found.")
81
+
82
+ return ToolResult(success=True, output=output)
83
+
84
+ except FileNotFoundError:
85
+ return None # rg not installed, fall back
86
+ except TimeoutError:
87
+ return ToolResult(success=False, output="", error="Search timed out")
88
+
89
+
90
+ def _search_python(
91
+ pattern: str, search_dir: Path, project_root: Path, max_results: int
92
+ ) -> ToolResult:
93
+ """Fallback search using Python regex."""
94
+ try:
95
+ compiled = re.compile(pattern)
96
+ except re.error as exc:
97
+ return ToolResult(success=False, output="", error=f"Invalid regex: {exc}")
98
+
99
+ matches: list[str] = []
100
+ source_extensions = {
101
+ ".py",
102
+ ".js",
103
+ ".ts",
104
+ ".tsx",
105
+ ".jsx",
106
+ ".go",
107
+ ".rs",
108
+ ".java",
109
+ ".rb",
110
+ ".php",
111
+ ".c",
112
+ ".cpp",
113
+ ".h",
114
+ ".swift",
115
+ ".kt",
116
+ ".md",
117
+ ".txt",
118
+ ".toml",
119
+ ".yaml",
120
+ ".yml",
121
+ ".json",
122
+ }
123
+
124
+ for file_path in search_dir.rglob("*"):
125
+ if len(matches) >= max_results:
126
+ break
127
+ if not file_path.is_file():
128
+ continue
129
+ if file_path.suffix not in source_extensions:
130
+ continue
131
+
132
+ try:
133
+ content = file_path.read_text(encoding="utf-8", errors="replace")
134
+ lines = content.splitlines()
135
+ rel_path = file_path.relative_to(project_root)
136
+
137
+ for i, line in enumerate(lines, start=1):
138
+ if compiled.search(line):
139
+ # Include 2 lines of context
140
+ start = max(0, i - 3)
141
+ end = min(len(lines), i + 2)
142
+ context_lines = []
143
+ for j in range(start, end):
144
+ prefix = ">" if j == i - 1 else " "
145
+ context_lines.append(f"{prefix} {j + 1:4d} | {lines[j]}")
146
+ matches.append(f"{rel_path}:{i}\n" + "\n".join(context_lines))
147
+
148
+ if len(matches) >= max_results:
149
+ break
150
+ except (OSError, UnicodeDecodeError):
151
+ continue
152
+
153
+ if not matches:
154
+ return ToolResult(success=True, output="No matches found.")
155
+
156
+ return ToolResult(success=True, output="\n\n".join(matches))
nex/tools/shell.py ADDED
@@ -0,0 +1,72 @@
1
+ """Shell command execution tool."""
2
+
3
+ from __future__ import annotations
4
+
5
+ import asyncio
6
+ from pathlib import Path
7
+
8
+ from nex.tools import ToolResult
9
+
10
+ _MAX_OUTPUT_LEN = 10_000
11
+
12
+
13
+ async def run_command(
14
+ command: str,
15
+ project_dir: Path,
16
+ timeout: int = 30,
17
+ ) -> ToolResult:
18
+ """Execute a shell command with timeout.
19
+
20
+ Safety checks are NOT performed here — they happen in the safety layer
21
+ before this function is called.
22
+
23
+ Args:
24
+ command: The shell command to execute.
25
+ project_dir: Working directory for the command.
26
+ timeout: Maximum seconds to wait (default 30).
27
+
28
+ Returns:
29
+ ToolResult with stdout+stderr or error message.
30
+ """
31
+ try:
32
+ process = await asyncio.create_subprocess_shell(
33
+ command,
34
+ stdout=asyncio.subprocess.PIPE,
35
+ stderr=asyncio.subprocess.PIPE,
36
+ cwd=str(project_dir),
37
+ )
38
+
39
+ try:
40
+ stdout, stderr = await asyncio.wait_for(process.communicate(), timeout=timeout)
41
+ except TimeoutError:
42
+ process.kill()
43
+ await process.communicate()
44
+ return ToolResult(
45
+ success=False,
46
+ output="",
47
+ error=f"Command timed out after {timeout}s: {command}",
48
+ )
49
+
50
+ stdout_str = stdout.decode("utf-8", errors="replace")
51
+ stderr_str = stderr.decode("utf-8", errors="replace")
52
+
53
+ combined = ""
54
+ if stdout_str:
55
+ combined += stdout_str
56
+ if stderr_str:
57
+ if combined:
58
+ combined += "\n--- stderr ---\n"
59
+ combined += stderr_str
60
+
61
+ if len(combined) > _MAX_OUTPUT_LEN:
62
+ total = len(combined)
63
+ combined = combined[:_MAX_OUTPUT_LEN] + f"\n... (truncated, total {total} chars)"
64
+
65
+ return ToolResult(
66
+ success=process.returncode == 0,
67
+ output=combined,
68
+ error=f"Exit code {process.returncode}" if process.returncode != 0 else None,
69
+ )
70
+
71
+ except OSError as exc:
72
+ return ToolResult(success=False, output="", error=f"Failed to run command: {exc}")
@@ -0,0 +1,170 @@
1
+ Metadata-Version: 2.4
2
+ Name: nexcoder
3
+ Version: 0.1.0
4
+ Summary: The coding agent that remembers — AI coding assistant with persistent memory and error learning.
5
+ Project-URL: Homepage, https://github.com/nex-ai/nex-ai
6
+ Project-URL: Repository, https://github.com/nex-ai/nex-ai
7
+ Author: Nex AI Contributors
8
+ License-Expression: MIT
9
+ License-File: LICENSE
10
+ Keywords: ai,anthropic,claude,cli,coding-agent
11
+ Classifier: Development Status :: 3 - Alpha
12
+ Classifier: Environment :: Console
13
+ Classifier: Intended Audience :: Developers
14
+ Classifier: License :: OSI Approved :: MIT License
15
+ Classifier: Programming Language :: Python :: 3.12
16
+ Classifier: Topic :: Software Development :: Code Generators
17
+ Requires-Python: >=3.12
18
+ Requires-Dist: anthropic>=0.40.0
19
+ Requires-Dist: gitpython>=3.1.0
20
+ Requires-Dist: httpx>=0.28.0
21
+ Requires-Dist: rich>=13.9.0
22
+ Requires-Dist: tree-sitter-javascript>=0.23.0
23
+ Requires-Dist: tree-sitter-python>=0.23.0
24
+ Requires-Dist: tree-sitter-typescript>=0.23.0
25
+ Requires-Dist: tree-sitter>=0.24.0
26
+ Requires-Dist: typer[all]>=0.15.0
27
+ Provides-Extra: dev
28
+ Requires-Dist: build>=1.0.0; extra == 'dev'
29
+ Requires-Dist: mypy>=1.13.0; extra == 'dev'
30
+ Requires-Dist: pytest-asyncio>=0.24.0; extra == 'dev'
31
+ Requires-Dist: pytest>=8.3.0; extra == 'dev'
32
+ Requires-Dist: ruff>=0.8.0; extra == 'dev'
33
+ Description-Content-Type: text/markdown
34
+
35
+ # Nex AI — The Coding Agent That Remembers
36
+
37
+ A CLI-based AI coding agent that wraps the Anthropic Claude API with persistent project memory, error pattern learning, and codebase indexing.
38
+
39
+ ## Features
40
+
41
+ - **Persistent Memory** — Remembers your project context, conventions, and architecture across sessions
42
+ - **Error Learning** — Logs past mistakes and queries them before generating code to avoid repeating errors
43
+ - **Codebase Indexing** — Uses tree-sitter to parse your codebase and select relevant context for each task
44
+ - **Interactive Chat** — Multi-turn conversation mode with full tool access and persistent history
45
+ - **Smart Context Selection** — Two-phase token budgeting with file-level TF-IDF ranking and signature-only fallback
46
+ - **Safety Layer** — Detects destructive operations (rm -rf, DROP TABLE, force push) and requires confirmation
47
+ - **Git Integration** — Creates isolated branches per task, shows diffs, and offers to commit
48
+
49
+ ## Installation
50
+
51
+ ```bash
52
+ pip install nexcoder
53
+ ```
54
+
55
+ ## Quick Start
56
+
57
+ ```bash
58
+ # Initialize Nex in your project
59
+ nex init
60
+
61
+ # Set up your API key
62
+ nex auth
63
+
64
+ # Build the codebase index
65
+ nex index
66
+
67
+ # Run a one-shot task
68
+ nex "add a health check endpoint"
69
+
70
+ # Or start an interactive session
71
+ nex chat
72
+
73
+ # Check project status
74
+ nex status
75
+ ```
76
+
77
+ ## Commands
78
+
79
+ | Command | Description |
80
+ |---------|-------------|
81
+ | `nex "task"` | Run a coding task |
82
+ | `nex init` | Initialize .nex/ directory |
83
+ | `nex index` | Build codebase index (.nex/index.json) |
84
+ | `nex chat` | Start interactive chat session |
85
+ | `nex status` | Show project stats |
86
+ | `nex auth` | Configure API key |
87
+ | `nex memory show` | View project memory |
88
+ | `nex memory edit` | Edit project memory |
89
+ | `nex rollback` | Undo last agent change |
90
+ | `nex --dry-run "task"` | Preview without executing |
91
+
92
+ ## How It Works
93
+
94
+ 1. **Context Assembly** — Loads project memory, error patterns, and relevant code into the prompt. Files are ranked by TF-IDF relevance, with top files included in full and remaining files as signature summaries.
95
+ 2. **Agent Loop** — Iterates: call Claude API → execute tools → feed results back, up to 25 iterations.
96
+ 3. **Interactive Chat** — `nex chat` maintains a persistent conversation with the agent, accumulating context across turns. Useful for exploratory work, debugging, or multi-step tasks.
97
+ 4. **Codebase Index** — `nex index` parses your source files with tree-sitter, extracting function/class signatures for fast relevance search.
98
+ 5. **Git Commit** — Shows diff and offers to commit on an isolated branch.
99
+
100
+ ## Tools
101
+
102
+ The agent has access to 5 tools:
103
+
104
+ | Tool | Description |
105
+ |------|-------------|
106
+ | `read_file` | Read file contents with line numbers |
107
+ | `write_file` | Write content, creating directories if needed |
108
+ | `run_command` | Execute shell commands (safety-checked) |
109
+ | `search_files` | Regex search across the codebase |
110
+ | `list_directory` | Recursive directory listing |
111
+
112
+ ## Development
113
+
114
+ ```bash
115
+ # Clone and install in editable mode
116
+ git clone https://github.com/nex-ai/nex-ai.git
117
+ cd nex-ai
118
+ pip install -e ".[dev]"
119
+
120
+ # Run tests
121
+ pytest
122
+
123
+ # Lint and format
124
+ ruff check src/ tests/
125
+ ruff format src/ tests/
126
+
127
+ # Type check
128
+ mypy src/nex/
129
+ ```
130
+
131
+ ## Architecture
132
+
133
+ ```
134
+ src/nex/
135
+ ├── cli.py # Typer CLI entry point
136
+ ├── agent.py # Core agent loop + ChatSession
137
+ ├── api_client.py # Anthropic API wrapper
138
+ ├── planner.py # Task decomposition (Haiku)
139
+ ├── reviewer.py # Independent code review
140
+ ├── context.py # Context assembly + token budgeting
141
+ ├── safety.py # Destructive operation detection
142
+ ├── config.py # Configuration management
143
+ ├── memory/ # Persistent memory system
144
+ │ ├── project.py # .nex/memory.md
145
+ │ ├── errors.py # .nex/errors.db (SQLite)
146
+ │ └── decisions.py # .nex/decisions.md
147
+ ├── indexer/ # Codebase indexing
148
+ │ ├── scanner.py # File discovery
149
+ │ ├── parser.py # tree-sitter AST parsing
150
+ │ └── index.py # Index builder + TF-IDF search
151
+ └── tools/ # Agent tools (5 total)
152
+ ├── file_ops.py # read_file, write_file
153
+ ├── shell.py # run_command
154
+ ├── search.py # search_files
155
+ └── git_ops.py # Git operations
156
+ ```
157
+
158
+ ## Environment Variables
159
+
160
+ | Variable | Description | Default |
161
+ |----------|-------------|---------|
162
+ | `ANTHROPIC_API_KEY` | Anthropic API key (required) | — |
163
+ | `NEX_MODEL` | Override default model | `claude-sonnet-4-20250514` |
164
+ | `NEX_MAX_ITERATIONS` | Max tool calls per task | `25` |
165
+ | `NEX_DRY_RUN` | Default dry-run mode | `false` |
166
+ | `NEX_LOG_LEVEL` | Logging verbosity | `INFO` |
167
+
168
+ ## License
169
+
170
+ MIT
@@ -0,0 +1,30 @@
1
+ nex/__init__.py,sha256=L_sbkyem5kYqGkPuCZcd-O2wBI3_SUfhf6YOUZZKKys,130
2
+ nex/agent.py,sha256=1GOUOYYNW5pHgG2C11yxmTRUaDkgFKsIg1YD8BCHK4Y,21757
3
+ nex/api_client.py,sha256=L3uZLq2CcBLzphccl_6Hls8bWbjnqMs30OhQP-LWxC8,6193
4
+ nex/cli.py,sha256=hrOSsE89e15kveFk7CakVqLDhey16t2rwi6rIqGq7UI,15562
5
+ nex/config.py,sha256=U-HH513SnP-Qjwm9r7J-X-cc5mXIvwUcxw8mhpPii6I,5636
6
+ nex/context.py,sha256=3n8tNWlLTkUIR8GuQKmNCNEBnMCdrBRq8eD6rVAoOs4,8273
7
+ nex/exceptions.py,sha256=i8qczPp-mkH94RtNHwfyzjWUXFYuF_EmOwCydfBZXG0,1043
8
+ nex/planner.py,sha256=xFmHvP00BQxfwwPtAoMwE8BiJXzluHFNJuL36736roM,3594
9
+ nex/py.typed,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
10
+ nex/reviewer.py,sha256=kz9xuuNsMMD_05FNLQSyiM0cODxWLTSIsKJKo9Sl37Q,3348
11
+ nex/safety.py,sha256=keaGodCsHOfVkqHsKKMGYm1XNPsWtfQ8L-h5CZwWVjU,7588
12
+ nex/test_runner.py,sha256=ZLcCGlL0LvboNLY8nClZPkgkw7oK8LiZ4nUaZaJV3WI,6140
13
+ nex/indexer/__init__.py,sha256=AG2KmuT7MWvguV_vkfXM_wuRGCe5B0w-5x4SybQaogs,388
14
+ nex/indexer/index.py,sha256=R92Cq7DGfgXkms_vm-34pJ0j34alwFOOWZHGwef3iok,10570
15
+ nex/indexer/parser.py,sha256=Wo3mEnhztfJKt-tb8-FiI4T8AUqG5JvECP2IwmYMjYs,13877
16
+ nex/indexer/scanner.py,sha256=lIYT_MG3VD0GhxqRCFEYSSG2-hh_MXcXGPsHCSU42Oc,5499
17
+ nex/memory/__init__.py,sha256=p4dQWSY614MC0DhVgtqediIkCO9Lf5adv5F0YF4IzNI,396
18
+ nex/memory/decisions.py,sha256=XvnEb8qwG6lnF-1IFDJ7D6oLc5tN8FvShGx6VO6KE2Y,3905
19
+ nex/memory/errors.py,sha256=DCpE71giIPFD3_90dJILtwNORUj3cMrPFBySKQOiOC8,8174
20
+ nex/memory/project.py,sha256=2OZ2el2T3g-TXcfwqCzWKfasdEWCkLT7aQjx5H5VFhQ,4746
21
+ nex/tools/__init__.py,sha256=WK4gsBGgaWbGz2j8PuBvQtQNTpeWGp5FG_wZLPoqHKU,3443
22
+ nex/tools/file_ops.py,sha256=_QMbWsljexkKkzUqz9llOU1uS2rC4GeMg6nq7C4GD6s,2970
23
+ nex/tools/git_ops.py,sha256=K8yH37Y6vIJUjK-DOZ6afrno_KPOK8xK1VJxfcAlW9w,5406
24
+ nex/tools/search.py,sha256=jSy6uiPzU2VNEv6YJ0e8ZvoYUy8m0YQYqpip9LzVlO0,4648
25
+ nex/tools/shell.py,sha256=3QrzK5C5mq0HX9HS8EssH94d7EEAheJrNEtEc-d6dq0,2113
26
+ nexcoder-0.1.0.dist-info/METADATA,sha256=5D8MaS5V9uwqMNLt92-618yoNgTKdIN6x88mRldPjbg,6021
27
+ nexcoder-0.1.0.dist-info/WHEEL,sha256=QccIxa26bgl1E6uMy58deGWi-0aeIkkangHcxk2kWfw,87
28
+ nexcoder-0.1.0.dist-info/entry_points.txt,sha256=xR6UmNT0yEmlXt7E-Xcv8zZPywI7TmZw-c8WIOl6iVQ,36
29
+ nexcoder-0.1.0.dist-info/licenses/LICENSE,sha256=aOwfjAeGkp6oZxVcTmr_0ae5RoJ2FiJ9HVWAsg-o7tU,1076
30
+ nexcoder-0.1.0.dist-info/RECORD,,
@@ -0,0 +1,4 @@
1
+ Wheel-Version: 1.0
2
+ Generator: hatchling 1.29.0
3
+ Root-Is-Purelib: true
4
+ Tag: py3-none-any
@@ -0,0 +1,2 @@
1
+ [console_scripts]
2
+ nex = nex.cli:app
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2025 Nex AI Contributors
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.