memocode 0.2.0__tar.gz → 0.2.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.
- memocode-0.2.2/PKG-INFO +180 -0
- {memocode-0.2.0 → memocode-0.2.2}/README.md +6 -5
- {memocode-0.2.0 → memocode-0.2.2}/control/brain.py +3 -2
- memocode-0.2.2/memocode.egg-info/PKG-INFO +180 -0
- {memocode-0.2.0 → memocode-0.2.2}/pyproject.toml +3 -1
- {memocode-0.2.0 → memocode-0.2.2}/run.py +31 -34
- {memocode-0.2.0 → memocode-0.2.2}/safety/safety.py +139 -20
- memocode-0.2.0/PKG-INFO +0 -9
- memocode-0.2.0/memocode.egg-info/PKG-INFO +0 -9
- {memocode-0.2.0 → memocode-0.2.2}/control/__init__.py +0 -0
- {memocode-0.2.0 → memocode-0.2.2}/control/audit.py +0 -0
- {memocode-0.2.0 → memocode-0.2.2}/control/chatmem/__init__.py +0 -0
- {memocode-0.2.0 → memocode-0.2.2}/control/chatmem/cli.py +0 -0
- {memocode-0.2.0 → memocode-0.2.2}/control/chatmem/compressor.py +0 -0
- {memocode-0.2.0 → memocode-0.2.2}/control/chatmem/config.py +0 -0
- {memocode-0.2.0 → memocode-0.2.2}/control/chatmem/context_manager.py +0 -0
- {memocode-0.2.0 → memocode-0.2.2}/control/chatmem/mcp_server.py +0 -0
- {memocode-0.2.0 → memocode-0.2.2}/control/chatmem/memory/__init__.py +0 -0
- {memocode-0.2.0 → memocode-0.2.2}/control/chatmem/memory/consolidation.py +0 -0
- {memocode-0.2.0 → memocode-0.2.2}/control/chatmem/memory/core_memory.py +0 -0
- {memocode-0.2.0 → memocode-0.2.2}/control/chatmem/memory/forgetting.py +0 -0
- {memocode-0.2.0 → memocode-0.2.2}/control/chatmem/memory/recent_memory.py +0 -0
- {memocode-0.2.0 → memocode-0.2.2}/control/chatmem/server.py +0 -0
- {memocode-0.2.0 → memocode-0.2.2}/control/chatmem/token_counter.py +0 -0
- {memocode-0.2.0 → memocode-0.2.2}/control/fmt.py +0 -0
- {memocode-0.2.0 → memocode-0.2.2}/control/llm.py +0 -0
- {memocode-0.2.0 → memocode-0.2.2}/control/planner.py +0 -0
- {memocode-0.2.0 → memocode-0.2.2}/control/project_manager.py +0 -0
- {memocode-0.2.0 → memocode-0.2.2}/memocode.egg-info/SOURCES.txt +0 -0
- {memocode-0.2.0 → memocode-0.2.2}/memocode.egg-info/dependency_links.txt +0 -0
- {memocode-0.2.0 → memocode-0.2.2}/memocode.egg-info/entry_points.txt +0 -0
- {memocode-0.2.0 → memocode-0.2.2}/memocode.egg-info/requires.txt +0 -0
- {memocode-0.2.0 → memocode-0.2.2}/memocode.egg-info/top_level.txt +0 -0
- {memocode-0.2.0 → memocode-0.2.2}/safety/__init__.py +0 -0
- {memocode-0.2.0 → memocode-0.2.2}/safety/backup.py +0 -0
- {memocode-0.2.0 → memocode-0.2.2}/safety/policy.py +0 -0
- {memocode-0.2.0 → memocode-0.2.2}/setup.cfg +0 -0
- {memocode-0.2.0 → memocode-0.2.2}/tools/__init__.py +0 -0
- {memocode-0.2.0 → memocode-0.2.2}/tools/file.py +0 -0
- {memocode-0.2.0 → memocode-0.2.2}/tools/loader.py +0 -0
- {memocode-0.2.0 → memocode-0.2.2}/tools/registry.py +0 -0
- {memocode-0.2.0 → memocode-0.2.2}/tools/shell.py +0 -0
- {memocode-0.2.0 → memocode-0.2.2}/tools/web.py +0 -0
memocode-0.2.2/PKG-INFO
ADDED
|
@@ -0,0 +1,180 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: memocode
|
|
3
|
+
Version: 0.2.2
|
|
4
|
+
Summary: Personal AI coding agent with memory, tool execution, and safety controls
|
|
5
|
+
Author: AssassinCHN
|
|
6
|
+
Requires-Python: >=3.11
|
|
7
|
+
Description-Content-Type: text/markdown
|
|
8
|
+
Requires-Dist: openai>=1.0
|
|
9
|
+
Requires-Dist: rich>=13.0
|
|
10
|
+
Requires-Dist: prompt_toolkit>=3.0
|
|
11
|
+
Requires-Dist: certifi>=2024.0
|
|
12
|
+
|
|
13
|
+
# Mcode
|
|
14
|
+
|
|
15
|
+
Personal AI coding agent CLI with memory, tool execution, and safety controls.
|
|
16
|
+
|
|
17
|
+
**Author:** AssassinCHN
|
|
18
|
+
|
|
19
|
+
## Features
|
|
20
|
+
|
|
21
|
+
- **Agentic tool-use loop** — Native function calling (OpenAI / Anthropic compatible), stuck detection, auto mode for fully autonomous task execution
|
|
22
|
+
- **Memory system** — Persistent conversation memory with consolidation, forgetting curve, and cross-session injection
|
|
23
|
+
- **Project management** — Multiple projects with separate memory DBs and working directories
|
|
24
|
+
- **Built-in tools** — Shell execution, file read/write/edit, glob, grep, web fetch
|
|
25
|
+
- **Safety layer** — Human mode (zone checks + confirmation prompts) / Auto mode (whitelist + work_dir hard boundaries) / Bypass mode (session-only, explicit confirmation required)
|
|
26
|
+
- **Extensible** — Drop Python files into `~/.mcode/tools/` for custom tools
|
|
27
|
+
|
|
28
|
+
## Requirements
|
|
29
|
+
|
|
30
|
+
- Python 3.11+
|
|
31
|
+
- API key for a supported model (MiniMax, Kimi, or any OpenAI-compatible endpoint)
|
|
32
|
+
|
|
33
|
+
## Installation
|
|
34
|
+
|
|
35
|
+
```bash
|
|
36
|
+
pip install memocode
|
|
37
|
+
```
|
|
38
|
+
|
|
39
|
+
Configure your model in `~/.mcode/agent.json` (created on first run):
|
|
40
|
+
|
|
41
|
+
```json
|
|
42
|
+
{
|
|
43
|
+
"active_model": "minimax",
|
|
44
|
+
"models": {
|
|
45
|
+
"minimax": {
|
|
46
|
+
"provider": "openai",
|
|
47
|
+
"model": "MiniMax-M2.7",
|
|
48
|
+
"base_url": "https://api.minimaxi.com/v1",
|
|
49
|
+
"api_key_env": "MINIMAX_API_KEY",
|
|
50
|
+
"context_window": 1000000,
|
|
51
|
+
"extra_body": {"reasoning_split": true}
|
|
52
|
+
}
|
|
53
|
+
}
|
|
54
|
+
}
|
|
55
|
+
```
|
|
56
|
+
|
|
57
|
+
```bash
|
|
58
|
+
export MINIMAX_API_KEY=your_key_here
|
|
59
|
+
mcode
|
|
60
|
+
```
|
|
61
|
+
|
|
62
|
+
## Usage
|
|
63
|
+
|
|
64
|
+
```bash
|
|
65
|
+
mcode # Start (auto-resumes last project)
|
|
66
|
+
mcode --project myapp # Start with a specific project
|
|
67
|
+
mcode --verbose # Show full tracebacks on errors
|
|
68
|
+
```
|
|
69
|
+
|
|
70
|
+
## Slash Commands
|
|
71
|
+
|
|
72
|
+
| Command | Description |
|
|
73
|
+
|---------|-------------|
|
|
74
|
+
| `/help` | Show all commands |
|
|
75
|
+
| `/status` | Show current settings |
|
|
76
|
+
| `/quit` / `/exit` | Exit |
|
|
77
|
+
| `/end` | End session (save to memory) |
|
|
78
|
+
| | |
|
|
79
|
+
| `/btw <question>` | Quick one-shot question — no memory, no tools |
|
|
80
|
+
| `/template [example]` | Show auto-mode task prompt template |
|
|
81
|
+
| | |
|
|
82
|
+
| `/auto [on\|off]` | Toggle auto mode (no prompts, hard boundaries) |
|
|
83
|
+
| `/auto whitelist [list\|add\|remove\|reset]` | Manage auto mode command whitelist |
|
|
84
|
+
| `/safety [on\|off]` | Toggle safety bypass (DANGEROUS, session-only) |
|
|
85
|
+
| | |
|
|
86
|
+
| `/project list` | List all projects |
|
|
87
|
+
| `/project workdir [path]` | Set working directory |
|
|
88
|
+
| `/project rename [<old>] <new>` | Rename a project (defaults to current) |
|
|
89
|
+
| `/project delete <name>` | Delete a project (glob patterns supported) |
|
|
90
|
+
| | |
|
|
91
|
+
| `/model [name]` | Show or switch LLM model |
|
|
92
|
+
| `/tools` | List all loaded tools |
|
|
93
|
+
| `/history [N]` | Show audit log |
|
|
94
|
+
| `/undo` | Undo the last run — restore code and/or rewind conversation |
|
|
95
|
+
| `/rollback` | Restore a specific backed-up file (file only, for audit) |
|
|
96
|
+
| `/rewind [N]` | Rewind conversation to turn N (conversation only) |
|
|
97
|
+
| `/profile` | Show/edit core memory (user profile) |
|
|
98
|
+
| | |
|
|
99
|
+
| `!<command>` | Run shell command directly (safety-checked) |
|
|
100
|
+
| `@<path>` | Attach file contents to your message |
|
|
101
|
+
|
|
102
|
+
## Auto Mode
|
|
103
|
+
|
|
104
|
+
Auto mode runs the agent fully autonomously — no confirmation prompts, hard safety boundaries (whitelist-only shell commands, writes restricted to `work_dir`).
|
|
105
|
+
|
|
106
|
+
Enable with `/auto on`, then use `/template` for a recommended task prompt structure:
|
|
107
|
+
|
|
108
|
+
```
|
|
109
|
+
Task: <one-line goal>
|
|
110
|
+
Context: work_dir, language/framework, entry point
|
|
111
|
+
Acceptance criteria: 1) ... 2) ... 3) ...
|
|
112
|
+
Constraints: do NOT modify <files>
|
|
113
|
+
Verify by running: <test command>
|
|
114
|
+
```
|
|
115
|
+
|
|
116
|
+
## Safety Modes
|
|
117
|
+
|
|
118
|
+
| Mode | Behavior |
|
|
119
|
+
|------|----------|
|
|
120
|
+
| Human (default) | Prompts for risky operations; backs up files before destructive ops |
|
|
121
|
+
| Auto (`/auto on`) | No prompts; blocks non-whitelisted commands and writes outside `work_dir` |
|
|
122
|
+
| Bypass (`/safety off`) | Skips all checks; session-only; requires typing `yes` to enable |
|
|
123
|
+
|
|
124
|
+
## Built-in Tools
|
|
125
|
+
|
|
126
|
+
| Tool | Description |
|
|
127
|
+
|------|-------------|
|
|
128
|
+
| `shell_exec` | Run shell commands (streaming output) |
|
|
129
|
+
| `file_read` | Read file with pagination |
|
|
130
|
+
| `file_write` | Write / append to file |
|
|
131
|
+
| `file_edit` | Targeted string replacement in file |
|
|
132
|
+
| `glob` | Find files by pattern (`**/*.py`) |
|
|
133
|
+
| `grep` | Search file content by regex |
|
|
134
|
+
| `web_fetch` | Fetch a URL, returns readable text (HTML stripped) |
|
|
135
|
+
|
|
136
|
+
## Custom Tools
|
|
137
|
+
|
|
138
|
+
Drop a Python file in `~/.mcode/tools/`:
|
|
139
|
+
|
|
140
|
+
```python
|
|
141
|
+
from tools.registry import Tool, ToolSchema
|
|
142
|
+
|
|
143
|
+
def _my_tool(param: str) -> str:
|
|
144
|
+
return f"result: {param}"
|
|
145
|
+
|
|
146
|
+
MY_TOOL = Tool(
|
|
147
|
+
schema=ToolSchema(
|
|
148
|
+
name="my_tool",
|
|
149
|
+
description="Description shown to the model",
|
|
150
|
+
parameters={
|
|
151
|
+
"type": "object",
|
|
152
|
+
"properties": {"param": {"type": "string"}},
|
|
153
|
+
"required": ["param"],
|
|
154
|
+
},
|
|
155
|
+
),
|
|
156
|
+
fn=_my_tool,
|
|
157
|
+
)
|
|
158
|
+
```
|
|
159
|
+
|
|
160
|
+
## Project Structure
|
|
161
|
+
|
|
162
|
+
```
|
|
163
|
+
mcode/
|
|
164
|
+
├── run.py # CLI entry point
|
|
165
|
+
├── control/
|
|
166
|
+
│ ├── brain.py # Agent loop, tool dispatch, safety
|
|
167
|
+
│ ├── llm.py # LLM adapter (OpenAI / Anthropic)
|
|
168
|
+
│ ├── project_manager.py # Project registry
|
|
169
|
+
│ ├── audit.py # Audit log
|
|
170
|
+
│ └── chatmem/ # Memory system
|
|
171
|
+
├── tools/
|
|
172
|
+
│ ├── file.py # file_read/write/edit, glob, grep
|
|
173
|
+
│ ├── shell.py # shell_exec
|
|
174
|
+
│ ├── web.py # web_fetch
|
|
175
|
+
│ └── registry.py # Tool registry + loader
|
|
176
|
+
└── safety/
|
|
177
|
+
├── safety.py # Zone checks, auto mode rules
|
|
178
|
+
├── backup.py # File backup
|
|
179
|
+
└── policy.py # Persistent always-allow policies
|
|
180
|
+
```
|
|
@@ -2,6 +2,8 @@
|
|
|
2
2
|
|
|
3
3
|
Personal AI coding agent CLI with memory, tool execution, and safety controls.
|
|
4
4
|
|
|
5
|
+
**Author:** AssassinCHN
|
|
6
|
+
|
|
5
7
|
## Features
|
|
6
8
|
|
|
7
9
|
- **Agentic tool-use loop** — Native function calling (OpenAI / Anthropic compatible), stuck detection, auto mode for fully autonomous task execution
|
|
@@ -19,9 +21,7 @@ Personal AI coding agent CLI with memory, tool execution, and safety controls.
|
|
|
19
21
|
## Installation
|
|
20
22
|
|
|
21
23
|
```bash
|
|
22
|
-
|
|
23
|
-
cd mcode
|
|
24
|
-
pip install -e .
|
|
24
|
+
pip install memocode
|
|
25
25
|
```
|
|
26
26
|
|
|
27
27
|
Configure your model in `~/.mcode/agent.json` (created on first run):
|
|
@@ -79,8 +79,9 @@ mcode --verbose # Show full tracebacks on errors
|
|
|
79
79
|
| `/model [name]` | Show or switch LLM model |
|
|
80
80
|
| `/tools` | List all loaded tools |
|
|
81
81
|
| `/history [N]` | Show audit log |
|
|
82
|
-
| `/
|
|
83
|
-
| `/
|
|
82
|
+
| `/undo` | Undo the last run — restore code and/or rewind conversation |
|
|
83
|
+
| `/rollback` | Restore a specific backed-up file (file only, for audit) |
|
|
84
|
+
| `/rewind [N]` | Rewind conversation to turn N (conversation only) |
|
|
84
85
|
| `/profile` | Show/edit core memory (user profile) |
|
|
85
86
|
| | |
|
|
86
87
|
| `!<command>` | Run shell command directly (safety-checked) |
|
|
@@ -402,8 +402,9 @@ class Brain:
|
|
|
402
402
|
"If the user asks you to write or create a file, call file_write. "
|
|
403
403
|
"If the user asks you to edit a file, call file_edit. "
|
|
404
404
|
"If the user asks you to fetch a URL or HTTP endpoint, call web_fetch — never fabricate the response. "
|
|
405
|
-
"Before editing a file, always read it first with file_read. "
|
|
406
|
-
"For targeted changes use file_edit; for new files use file_write."
|
|
405
|
+
"Before editing a file, always read it first with file_read, then proceed to make the edit without stopping. "
|
|
406
|
+
"For targeted changes use file_edit; for new files use file_write. "
|
|
407
|
+
"If a task requires multiple tool calls, keep calling tools until the task is fully done — do not report completion until all actions have been executed via tools."
|
|
407
408
|
)
|
|
408
409
|
user_msg = [
|
|
409
410
|
{"role": "system", "content": _system},
|
|
@@ -0,0 +1,180 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: memocode
|
|
3
|
+
Version: 0.2.2
|
|
4
|
+
Summary: Personal AI coding agent with memory, tool execution, and safety controls
|
|
5
|
+
Author: AssassinCHN
|
|
6
|
+
Requires-Python: >=3.11
|
|
7
|
+
Description-Content-Type: text/markdown
|
|
8
|
+
Requires-Dist: openai>=1.0
|
|
9
|
+
Requires-Dist: rich>=13.0
|
|
10
|
+
Requires-Dist: prompt_toolkit>=3.0
|
|
11
|
+
Requires-Dist: certifi>=2024.0
|
|
12
|
+
|
|
13
|
+
# Mcode
|
|
14
|
+
|
|
15
|
+
Personal AI coding agent CLI with memory, tool execution, and safety controls.
|
|
16
|
+
|
|
17
|
+
**Author:** AssassinCHN
|
|
18
|
+
|
|
19
|
+
## Features
|
|
20
|
+
|
|
21
|
+
- **Agentic tool-use loop** — Native function calling (OpenAI / Anthropic compatible), stuck detection, auto mode for fully autonomous task execution
|
|
22
|
+
- **Memory system** — Persistent conversation memory with consolidation, forgetting curve, and cross-session injection
|
|
23
|
+
- **Project management** — Multiple projects with separate memory DBs and working directories
|
|
24
|
+
- **Built-in tools** — Shell execution, file read/write/edit, glob, grep, web fetch
|
|
25
|
+
- **Safety layer** — Human mode (zone checks + confirmation prompts) / Auto mode (whitelist + work_dir hard boundaries) / Bypass mode (session-only, explicit confirmation required)
|
|
26
|
+
- **Extensible** — Drop Python files into `~/.mcode/tools/` for custom tools
|
|
27
|
+
|
|
28
|
+
## Requirements
|
|
29
|
+
|
|
30
|
+
- Python 3.11+
|
|
31
|
+
- API key for a supported model (MiniMax, Kimi, or any OpenAI-compatible endpoint)
|
|
32
|
+
|
|
33
|
+
## Installation
|
|
34
|
+
|
|
35
|
+
```bash
|
|
36
|
+
pip install memocode
|
|
37
|
+
```
|
|
38
|
+
|
|
39
|
+
Configure your model in `~/.mcode/agent.json` (created on first run):
|
|
40
|
+
|
|
41
|
+
```json
|
|
42
|
+
{
|
|
43
|
+
"active_model": "minimax",
|
|
44
|
+
"models": {
|
|
45
|
+
"minimax": {
|
|
46
|
+
"provider": "openai",
|
|
47
|
+
"model": "MiniMax-M2.7",
|
|
48
|
+
"base_url": "https://api.minimaxi.com/v1",
|
|
49
|
+
"api_key_env": "MINIMAX_API_KEY",
|
|
50
|
+
"context_window": 1000000,
|
|
51
|
+
"extra_body": {"reasoning_split": true}
|
|
52
|
+
}
|
|
53
|
+
}
|
|
54
|
+
}
|
|
55
|
+
```
|
|
56
|
+
|
|
57
|
+
```bash
|
|
58
|
+
export MINIMAX_API_KEY=your_key_here
|
|
59
|
+
mcode
|
|
60
|
+
```
|
|
61
|
+
|
|
62
|
+
## Usage
|
|
63
|
+
|
|
64
|
+
```bash
|
|
65
|
+
mcode # Start (auto-resumes last project)
|
|
66
|
+
mcode --project myapp # Start with a specific project
|
|
67
|
+
mcode --verbose # Show full tracebacks on errors
|
|
68
|
+
```
|
|
69
|
+
|
|
70
|
+
## Slash Commands
|
|
71
|
+
|
|
72
|
+
| Command | Description |
|
|
73
|
+
|---------|-------------|
|
|
74
|
+
| `/help` | Show all commands |
|
|
75
|
+
| `/status` | Show current settings |
|
|
76
|
+
| `/quit` / `/exit` | Exit |
|
|
77
|
+
| `/end` | End session (save to memory) |
|
|
78
|
+
| | |
|
|
79
|
+
| `/btw <question>` | Quick one-shot question — no memory, no tools |
|
|
80
|
+
| `/template [example]` | Show auto-mode task prompt template |
|
|
81
|
+
| | |
|
|
82
|
+
| `/auto [on\|off]` | Toggle auto mode (no prompts, hard boundaries) |
|
|
83
|
+
| `/auto whitelist [list\|add\|remove\|reset]` | Manage auto mode command whitelist |
|
|
84
|
+
| `/safety [on\|off]` | Toggle safety bypass (DANGEROUS, session-only) |
|
|
85
|
+
| | |
|
|
86
|
+
| `/project list` | List all projects |
|
|
87
|
+
| `/project workdir [path]` | Set working directory |
|
|
88
|
+
| `/project rename [<old>] <new>` | Rename a project (defaults to current) |
|
|
89
|
+
| `/project delete <name>` | Delete a project (glob patterns supported) |
|
|
90
|
+
| | |
|
|
91
|
+
| `/model [name]` | Show or switch LLM model |
|
|
92
|
+
| `/tools` | List all loaded tools |
|
|
93
|
+
| `/history [N]` | Show audit log |
|
|
94
|
+
| `/undo` | Undo the last run — restore code and/or rewind conversation |
|
|
95
|
+
| `/rollback` | Restore a specific backed-up file (file only, for audit) |
|
|
96
|
+
| `/rewind [N]` | Rewind conversation to turn N (conversation only) |
|
|
97
|
+
| `/profile` | Show/edit core memory (user profile) |
|
|
98
|
+
| | |
|
|
99
|
+
| `!<command>` | Run shell command directly (safety-checked) |
|
|
100
|
+
| `@<path>` | Attach file contents to your message |
|
|
101
|
+
|
|
102
|
+
## Auto Mode
|
|
103
|
+
|
|
104
|
+
Auto mode runs the agent fully autonomously — no confirmation prompts, hard safety boundaries (whitelist-only shell commands, writes restricted to `work_dir`).
|
|
105
|
+
|
|
106
|
+
Enable with `/auto on`, then use `/template` for a recommended task prompt structure:
|
|
107
|
+
|
|
108
|
+
```
|
|
109
|
+
Task: <one-line goal>
|
|
110
|
+
Context: work_dir, language/framework, entry point
|
|
111
|
+
Acceptance criteria: 1) ... 2) ... 3) ...
|
|
112
|
+
Constraints: do NOT modify <files>
|
|
113
|
+
Verify by running: <test command>
|
|
114
|
+
```
|
|
115
|
+
|
|
116
|
+
## Safety Modes
|
|
117
|
+
|
|
118
|
+
| Mode | Behavior |
|
|
119
|
+
|------|----------|
|
|
120
|
+
| Human (default) | Prompts for risky operations; backs up files before destructive ops |
|
|
121
|
+
| Auto (`/auto on`) | No prompts; blocks non-whitelisted commands and writes outside `work_dir` |
|
|
122
|
+
| Bypass (`/safety off`) | Skips all checks; session-only; requires typing `yes` to enable |
|
|
123
|
+
|
|
124
|
+
## Built-in Tools
|
|
125
|
+
|
|
126
|
+
| Tool | Description |
|
|
127
|
+
|------|-------------|
|
|
128
|
+
| `shell_exec` | Run shell commands (streaming output) |
|
|
129
|
+
| `file_read` | Read file with pagination |
|
|
130
|
+
| `file_write` | Write / append to file |
|
|
131
|
+
| `file_edit` | Targeted string replacement in file |
|
|
132
|
+
| `glob` | Find files by pattern (`**/*.py`) |
|
|
133
|
+
| `grep` | Search file content by regex |
|
|
134
|
+
| `web_fetch` | Fetch a URL, returns readable text (HTML stripped) |
|
|
135
|
+
|
|
136
|
+
## Custom Tools
|
|
137
|
+
|
|
138
|
+
Drop a Python file in `~/.mcode/tools/`:
|
|
139
|
+
|
|
140
|
+
```python
|
|
141
|
+
from tools.registry import Tool, ToolSchema
|
|
142
|
+
|
|
143
|
+
def _my_tool(param: str) -> str:
|
|
144
|
+
return f"result: {param}"
|
|
145
|
+
|
|
146
|
+
MY_TOOL = Tool(
|
|
147
|
+
schema=ToolSchema(
|
|
148
|
+
name="my_tool",
|
|
149
|
+
description="Description shown to the model",
|
|
150
|
+
parameters={
|
|
151
|
+
"type": "object",
|
|
152
|
+
"properties": {"param": {"type": "string"}},
|
|
153
|
+
"required": ["param"],
|
|
154
|
+
},
|
|
155
|
+
),
|
|
156
|
+
fn=_my_tool,
|
|
157
|
+
)
|
|
158
|
+
```
|
|
159
|
+
|
|
160
|
+
## Project Structure
|
|
161
|
+
|
|
162
|
+
```
|
|
163
|
+
mcode/
|
|
164
|
+
├── run.py # CLI entry point
|
|
165
|
+
├── control/
|
|
166
|
+
│ ├── brain.py # Agent loop, tool dispatch, safety
|
|
167
|
+
│ ├── llm.py # LLM adapter (OpenAI / Anthropic)
|
|
168
|
+
│ ├── project_manager.py # Project registry
|
|
169
|
+
│ ├── audit.py # Audit log
|
|
170
|
+
│ └── chatmem/ # Memory system
|
|
171
|
+
├── tools/
|
|
172
|
+
│ ├── file.py # file_read/write/edit, glob, grep
|
|
173
|
+
│ ├── shell.py # shell_exec
|
|
174
|
+
│ ├── web.py # web_fetch
|
|
175
|
+
│ └── registry.py # Tool registry + loader
|
|
176
|
+
└── safety/
|
|
177
|
+
├── safety.py # Zone checks, auto mode rules
|
|
178
|
+
├── backup.py # File backup
|
|
179
|
+
└── policy.py # Persistent always-allow policies
|
|
180
|
+
```
|
|
@@ -4,8 +4,10 @@ build-backend = "setuptools.build_meta"
|
|
|
4
4
|
|
|
5
5
|
[project]
|
|
6
6
|
name = "memocode"
|
|
7
|
-
version = "0.2.
|
|
7
|
+
version = "0.2.2"
|
|
8
8
|
description = "Personal AI coding agent with memory, tool execution, and safety controls"
|
|
9
|
+
readme = "README.md"
|
|
10
|
+
authors = [{name = "AssassinCHN"}]
|
|
9
11
|
requires-python = ">=3.11"
|
|
10
12
|
dependencies = [
|
|
11
13
|
"openai>=1.0",
|
|
@@ -26,7 +26,7 @@ Slash commands:
|
|
|
26
26
|
/end End session (save to memory, keep verbatim tail)
|
|
27
27
|
/history [N] Show last N audit log entries (default 15)
|
|
28
28
|
/undo Undo a run: restore code and/or rewind conversation
|
|
29
|
-
/rollback Restore a backed-up file (file only)
|
|
29
|
+
/rollback Restore a backed-up file (file only, for audit)
|
|
30
30
|
/rewind [N] Rewind conversation to turn N (conversation only)
|
|
31
31
|
|
|
32
32
|
/project list List all projects
|
|
@@ -232,31 +232,6 @@ def cmd_undo(brain: Brain):
|
|
|
232
232
|
print("Undo complete.")
|
|
233
233
|
|
|
234
234
|
|
|
235
|
-
def cmd_rollback(brain: Brain):
|
|
236
|
-
runs = brain.audit.runs_with_backups(project=brain.projects.current_project)
|
|
237
|
-
if not runs:
|
|
238
|
-
print("No backups available.")
|
|
239
|
-
return
|
|
240
|
-
|
|
241
|
-
import time
|
|
242
|
-
print("\nRuns with available backups:")
|
|
243
|
-
for i, run in enumerate(runs[:10]):
|
|
244
|
-
ts = time.strftime("%Y-%m-%d %H:%M:%S", time.localtime(run["ts"]))
|
|
245
|
-
status = "✓" if run["success"] else "✗"
|
|
246
|
-
print(f" [{i}] {ts} {status} {run['goal'][:60]}")
|
|
247
|
-
for orig, bp in run["available_backups"].items():
|
|
248
|
-
print(f" {orig} ← {bp}")
|
|
249
|
-
|
|
250
|
-
choice = _ask("\nEnter number to restore (or Enter to cancel): ")
|
|
251
|
-
if not choice.isdigit() or int(choice) >= len(runs[:10]):
|
|
252
|
-
print("Cancelled.")
|
|
253
|
-
return
|
|
254
|
-
|
|
255
|
-
from safety.backup import restore_backup
|
|
256
|
-
for orig, bp in runs[int(choice)]["available_backups"].items():
|
|
257
|
-
print(f"Restored: {orig}" if restore_backup(bp) else f"Failed: {bp}")
|
|
258
|
-
|
|
259
|
-
|
|
260
235
|
def cmd_rewind(brain: Brain, arg: str):
|
|
261
236
|
turns = brain.memory.list_turns()
|
|
262
237
|
if not turns:
|
|
@@ -264,14 +239,11 @@ def cmd_rewind(brain: Brain, arg: str):
|
|
|
264
239
|
return
|
|
265
240
|
|
|
266
241
|
if not arg:
|
|
267
|
-
# List turns for user to pick
|
|
268
242
|
print("\nCurrent session turns:")
|
|
269
243
|
for t in turns:
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
if asst_preview:
|
|
274
|
-
print(f" Agent: {asst_preview[:60]}")
|
|
244
|
+
print(f" [{t['index']}] You: {t['user'].replace(chr(10), ' ')[:60]}")
|
|
245
|
+
if t["assistant"]:
|
|
246
|
+
print(f" Agent: {t['assistant'].replace(chr(10), ' ')[:60]}")
|
|
275
247
|
choice = _ask("\nRewind to turn number (or Enter to cancel): ")
|
|
276
248
|
if not choice.isdigit():
|
|
277
249
|
print("Cancelled.")
|
|
@@ -285,12 +257,37 @@ def cmd_rewind(brain: Brain, arg: str):
|
|
|
285
257
|
idx = int(arg)
|
|
286
258
|
try:
|
|
287
259
|
brain.memory.rewind_to(idx)
|
|
288
|
-
|
|
289
|
-
print(f"Rewound to turn [{idx}]. Session now has {len(turns)} turn(s).")
|
|
260
|
+
print(f"Rewound to turn [{idx}]. Session now has {len(brain.memory.list_turns())} turn(s).")
|
|
290
261
|
except IndexError as e:
|
|
291
262
|
print(f"Error: {e}")
|
|
292
263
|
|
|
293
264
|
|
|
265
|
+
def cmd_rollback(brain: Brain):
|
|
266
|
+
runs = brain.audit.runs_with_backups(project=brain.projects.current_project)
|
|
267
|
+
if not runs:
|
|
268
|
+
print("No backups available.")
|
|
269
|
+
return
|
|
270
|
+
|
|
271
|
+
import time
|
|
272
|
+
print("\nRuns with available backups:")
|
|
273
|
+
for i, run in enumerate(runs[:10]):
|
|
274
|
+
ts = time.strftime("%Y-%m-%d %H:%M:%S", time.localtime(run["ts"]))
|
|
275
|
+
status = "✓" if run["success"] else "✗"
|
|
276
|
+
print(f" [{i}] {ts} {status} {run['goal'][:60]}")
|
|
277
|
+
for orig, bp in run["available_backups"].items():
|
|
278
|
+
print(f" {orig} ← {bp}")
|
|
279
|
+
|
|
280
|
+
choice = _ask("\nEnter number to restore (or Enter to cancel): ")
|
|
281
|
+
if not choice.isdigit() or int(choice) >= len(runs[:10]):
|
|
282
|
+
print("Cancelled.")
|
|
283
|
+
return
|
|
284
|
+
|
|
285
|
+
from safety.backup import restore_backup
|
|
286
|
+
for orig, bp in runs[int(choice)]["available_backups"].items():
|
|
287
|
+
print(f"Restored: {orig}" if restore_backup(bp) else f"Failed: {bp}")
|
|
288
|
+
|
|
289
|
+
|
|
290
|
+
|
|
294
291
|
def cmd_history(brain: Brain, n: int = 15):
|
|
295
292
|
import time
|
|
296
293
|
project = brain.projects.current_project
|
|
@@ -82,7 +82,7 @@ def _extract_paths(command: str, work_dir: str | None = None) -> list[str]:
|
|
|
82
82
|
# relative path (./foo, ../foo) — resolve against work_dir or cwd
|
|
83
83
|
base = work_dir or os.getcwd()
|
|
84
84
|
ep = os.path.normpath(os.path.join(base, p))
|
|
85
|
-
if not ep.startswith(("/proc/", "/sys/")):
|
|
85
|
+
if not ep.startswith(("/proc/", "/sys/")) and ep not in ("/dev/null", "/dev/stdout", "/dev/stderr"):
|
|
86
86
|
result.append(ep)
|
|
87
87
|
return result
|
|
88
88
|
|
|
@@ -266,31 +266,150 @@ def check(task, work_dir: str | None = None,
|
|
|
266
266
|
# Command injection patterns
|
|
267
267
|
_INJECTION_RE = re.compile(r'\$\(|`')
|
|
268
268
|
|
|
269
|
-
#
|
|
270
|
-
|
|
269
|
+
# Leading KEY=value env-var assignments (e.g. PYTHONPATH=... FOO=bar cmd ...)
|
|
270
|
+
_ENV_PREFIX_RE = re.compile(r'^(\w+=\S*\s*)+')
|
|
271
271
|
|
|
272
272
|
DEFAULT_AUTO_WHITELIST: list[str] = [
|
|
273
|
-
#
|
|
274
|
-
"
|
|
275
|
-
|
|
276
|
-
#
|
|
277
|
-
"
|
|
278
|
-
|
|
273
|
+
# ── Navigation ───────────────────────────────────────────────────────────
|
|
274
|
+
"cd", "pwd", "pushd", "popd", "dirs",
|
|
275
|
+
|
|
276
|
+
# ── File inspection ──────────────────────────────────────────────────────
|
|
277
|
+
"ls", "ll", "la", "find", "locate", "which", "type", "file",
|
|
278
|
+
"stat", "du", "df", "realpath", "basename", "dirname", "readlink",
|
|
279
|
+
"lsblk", "lscpu", "lshw", "lsof",
|
|
280
|
+
|
|
281
|
+
# ── File read ────────────────────────────────────────────────────────────
|
|
282
|
+
"cat", "head", "tail", "less", "more", "bat",
|
|
283
|
+
"wc", "od", "xxd", "hexdump", "strings",
|
|
284
|
+
|
|
285
|
+
# ── File create / copy / move ─────────────────────────────────────────────
|
|
286
|
+
"mkdir", "touch", "cp", "mv", "ln", "mktemp", "tee", "split", "install",
|
|
287
|
+
|
|
288
|
+
# ── Archive ──────────────────────────────────────────────────────────────
|
|
289
|
+
"tar", "zip", "unzip", "gzip", "gunzip", "bzip2", "bunzip2",
|
|
290
|
+
"xz", "unxz", "zcat", "7z", "7za",
|
|
291
|
+
|
|
292
|
+
# ── Text processing ──────────────────────────────────────────────────────
|
|
293
|
+
"grep", "rg", "ag", "awk", "sed", "cut", "sort", "uniq",
|
|
294
|
+
"diff", "patch", "tr", "xargs",
|
|
295
|
+
"paste", "join", "expand", "unexpand", "nl", "fold", "column",
|
|
296
|
+
"echo", "printf", "iconv",
|
|
297
|
+
|
|
298
|
+
# ── Encoding / hashing ───────────────────────────────────────────────────
|
|
299
|
+
"base64", "md5sum", "sha1sum", "sha256sum", "sha512sum", "shasum", "cksum",
|
|
300
|
+
"openssl",
|
|
301
|
+
|
|
302
|
+
# ── Search ───────────────────────────────────────────────────────────────
|
|
303
|
+
"fd", "fzf",
|
|
304
|
+
|
|
305
|
+
# ── Environment / system info ─────────────────────────────────────────────
|
|
306
|
+
"env", "printenv", "date", "uname", "hostname", "uptime",
|
|
307
|
+
"whoami", "id", "groups",
|
|
308
|
+
"ps", "pgrep", "pidof", "top", "htop", "free", "vmstat", "iostat",
|
|
309
|
+
"ulimit", "time", "bc",
|
|
310
|
+
|
|
311
|
+
# ── Process control ───────────────────────────────────────────────────────
|
|
312
|
+
"sleep", "wait", "timeout", "watch", "nohup",
|
|
313
|
+
"true", "false", "test", "seq", "kill", "pkill",
|
|
314
|
+
|
|
315
|
+
# ── Network (read / fetch) ─────────────────────────────────────────────────
|
|
316
|
+
"curl", "wget", "http",
|
|
317
|
+
"ping", "traceroute", "nslookup", "dig", "host",
|
|
318
|
+
"netstat", "ss", "ifconfig", "ip",
|
|
319
|
+
"ssh", "scp", "rsync",
|
|
320
|
+
|
|
321
|
+
# ── Permissions ──────────────────────────────────────────────────────────
|
|
322
|
+
"chmod", "chown", "chgrp", "umask",
|
|
323
|
+
|
|
324
|
+
# ── Git ──────────────────────────────────────────────────────────────────
|
|
279
325
|
"git",
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
"pip
|
|
284
|
-
"
|
|
285
|
-
"
|
|
286
|
-
|
|
287
|
-
"
|
|
326
|
+
|
|
327
|
+
# ── Python ───────────────────────────────────────────────────────────────
|
|
328
|
+
"python", "python3", "python3.10", "python3.11", "python3.12", "python3.13",
|
|
329
|
+
"pip", "pip3", "pipenv", "poetry", "uv",
|
|
330
|
+
"pytest", "mypy", "pyright", "ruff", "flake8", "pylint",
|
|
331
|
+
"black", "isort", "autopep8",
|
|
332
|
+
"ipython", "jupyter",
|
|
333
|
+
"pyenv",
|
|
334
|
+
|
|
335
|
+
# ── Node / JS ────────────────────────────────────────────────────────────
|
|
336
|
+
"node", "npm", "npx", "yarn", "pnpm", "bun", "deno",
|
|
337
|
+
"jest", "mocha", "vitest", "eslint", "prettier", "tsc",
|
|
338
|
+
"nvm",
|
|
339
|
+
|
|
340
|
+
# ── Compiled languages ────────────────────────────────────────────────────
|
|
341
|
+
"gcc", "g++", "clang", "clang++", "cc", "c++",
|
|
342
|
+
"make", "cmake", "ninja", "meson", "bear",
|
|
343
|
+
"cargo", "rustc", "rustfmt",
|
|
344
|
+
"go", "gofmt",
|
|
345
|
+
"java", "javac", "jar", "mvn", "gradle",
|
|
346
|
+
"dotnet",
|
|
347
|
+
"swift", "swiftc",
|
|
348
|
+
"ruby", "gem", "bundle", "rspec",
|
|
349
|
+
"perl", "php", "composer",
|
|
350
|
+
"lua", "Rscript",
|
|
351
|
+
|
|
352
|
+
# ── Shell scripting ───────────────────────────────────────────────────────
|
|
353
|
+
"bash", "sh", "zsh", "shellcheck",
|
|
354
|
+
"source",
|
|
355
|
+
|
|
356
|
+
# ── Data / serialisation ──────────────────────────────────────────────────
|
|
357
|
+
"jq", "yq",
|
|
358
|
+
|
|
359
|
+
# ── Containers / orchestration ────────────────────────────────────────────
|
|
360
|
+
"docker", "docker-compose", "docker compose", "podman",
|
|
361
|
+
"kubectl", "helm", "minikube", "kind",
|
|
362
|
+
|
|
363
|
+
# ── Cloud CLIs ────────────────────────────────────────────────────────────
|
|
364
|
+
"aws", "gcloud", "az",
|
|
365
|
+
|
|
366
|
+
# ── Database clients ──────────────────────────────────────────────────────
|
|
367
|
+
"mysql", "mysqldump", "psql", "pg_dump", "sqlite3",
|
|
368
|
+
"redis-cli",
|
|
369
|
+
|
|
370
|
+
# ── Misc ─────────────────────────────────────────────────────────────────
|
|
371
|
+
"man", "tput", "stty", "locale",
|
|
372
|
+
"systemctl", "service",
|
|
288
373
|
]
|
|
289
374
|
|
|
290
375
|
|
|
376
|
+
def _split_shell_ops(command: str) -> list[str]:
|
|
377
|
+
"""Split on && || | ; but NOT inside single- or double-quoted strings."""
|
|
378
|
+
segments: list[str] = []
|
|
379
|
+
current: list[str] = []
|
|
380
|
+
in_single = False
|
|
381
|
+
in_double = False
|
|
382
|
+
i = 0
|
|
383
|
+
while i < len(command):
|
|
384
|
+
c = command[i]
|
|
385
|
+
if c == "'" and not in_double:
|
|
386
|
+
in_single = not in_single
|
|
387
|
+
current.append(c)
|
|
388
|
+
elif c == '"' and not in_single:
|
|
389
|
+
in_double = not in_double
|
|
390
|
+
current.append(c)
|
|
391
|
+
elif not in_single and not in_double:
|
|
392
|
+
two = command[i:i + 2]
|
|
393
|
+
if two in ("&&", "||"):
|
|
394
|
+
segments.append("".join(current))
|
|
395
|
+
current = []
|
|
396
|
+
i += 2
|
|
397
|
+
continue
|
|
398
|
+
elif c in ("|", ";"):
|
|
399
|
+
segments.append("".join(current))
|
|
400
|
+
current = []
|
|
401
|
+
else:
|
|
402
|
+
current.append(c)
|
|
403
|
+
else:
|
|
404
|
+
current.append(c)
|
|
405
|
+
i += 1
|
|
406
|
+
segments.append("".join(current))
|
|
407
|
+
return segments
|
|
408
|
+
|
|
409
|
+
|
|
291
410
|
def _matches_whitelist(segment: str, whitelist: list[str]) -> bool:
|
|
292
|
-
"""Return True if the command segment starts with a whitelist entry
|
|
293
|
-
seg = segment.lower().strip()
|
|
411
|
+
"""Return True if the command segment (after stripping env-var prefixes) starts with a whitelist entry."""
|
|
412
|
+
seg = _ENV_PREFIX_RE.sub("", segment).lower().strip()
|
|
294
413
|
for entry in whitelist:
|
|
295
414
|
e = entry.lower().strip()
|
|
296
415
|
if seg == e or seg.startswith(e + " "):
|
|
@@ -352,7 +471,7 @@ def check_auto(task, work_dir: str | None = None,
|
|
|
352
471
|
reason="Auto mode: command injection pattern blocked")
|
|
353
472
|
|
|
354
473
|
# Whitelist check — every pipe/chain segment must be in whitelist
|
|
355
|
-
for seg in
|
|
474
|
+
for seg in _split_shell_ops(command):
|
|
356
475
|
seg = seg.strip()
|
|
357
476
|
if not seg:
|
|
358
477
|
continue
|
memocode-0.2.0/PKG-INFO
DELETED
|
@@ -1,9 +0,0 @@
|
|
|
1
|
-
Metadata-Version: 2.4
|
|
2
|
-
Name: memocode
|
|
3
|
-
Version: 0.2.0
|
|
4
|
-
Summary: Personal AI coding agent with memory, tool execution, and safety controls
|
|
5
|
-
Requires-Python: >=3.11
|
|
6
|
-
Requires-Dist: openai>=1.0
|
|
7
|
-
Requires-Dist: rich>=13.0
|
|
8
|
-
Requires-Dist: prompt_toolkit>=3.0
|
|
9
|
-
Requires-Dist: certifi>=2024.0
|
|
@@ -1,9 +0,0 @@
|
|
|
1
|
-
Metadata-Version: 2.4
|
|
2
|
-
Name: memocode
|
|
3
|
-
Version: 0.2.0
|
|
4
|
-
Summary: Personal AI coding agent with memory, tool execution, and safety controls
|
|
5
|
-
Requires-Python: >=3.11
|
|
6
|
-
Requires-Dist: openai>=1.0
|
|
7
|
-
Requires-Dist: rich>=13.0
|
|
8
|
-
Requires-Dist: prompt_toolkit>=3.0
|
|
9
|
-
Requires-Dist: certifi>=2024.0
|
|
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
|