forgecodecli 0.1.0__py3-none-any.whl → 0.2.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.
- forgecodecli/agent.py +126 -55
- forgecodecli/cli.py +317 -138
- forgecodecli/git_tools.py +69 -0
- forgecodecli/path_resolver.py +24 -0
- forgecodecli/tools.py +123 -17
- forgecodecli-0.2.0.dist-info/METADATA +198 -0
- forgecodecli-0.2.0.dist-info/RECORD +13 -0
- {forgecodecli-0.1.0.dist-info → forgecodecli-0.2.0.dist-info}/WHEEL +1 -1
- forgecodecli-0.1.0.dist-info/METADATA +0 -127
- forgecodecli-0.1.0.dist-info/RECORD +0 -11
- {forgecodecli-0.1.0.dist-info → forgecodecli-0.2.0.dist-info}/entry_points.txt +0 -0
- {forgecodecli-0.1.0.dist-info → forgecodecli-0.2.0.dist-info}/top_level.txt +0 -0
forgecodecli/agent.py
CHANGED
|
@@ -1,14 +1,22 @@
|
|
|
1
1
|
# from dotenv import load_dotenv
|
|
2
2
|
from openai import OpenAI
|
|
3
|
-
|
|
3
|
+
from openai import RateLimitError
|
|
4
4
|
import json
|
|
5
5
|
from json import JSONDecoder
|
|
6
|
-
from openai import RateLimitError
|
|
7
6
|
from forgecodecli.config import load_config
|
|
8
7
|
from forgecodecli.secrets import load_api_key
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
8
|
+
from forgecodecli.tools import undo
|
|
9
|
+
from forgecodecli.git_tools import (
|
|
10
|
+
git_init, git_add, git_commit, git_push, git_set_origin,
|
|
11
|
+
git_status, git_log, git_branch, git_pull, git_clone
|
|
12
|
+
)
|
|
13
|
+
|
|
14
|
+
# Provider base URLs (OpenAI-compatible)
|
|
15
|
+
PROVIDER_BASE_URLS = {
|
|
16
|
+
"gemini": "https://generativelanguage.googleapis.com/v1beta/openai/",
|
|
17
|
+
"openai": None, # default OpenAI
|
|
18
|
+
"groq": "https://api.groq.com/openai/v1",
|
|
19
|
+
}
|
|
12
20
|
|
|
13
21
|
def get_client():
|
|
14
22
|
config = load_config()
|
|
@@ -24,64 +32,130 @@ def get_client():
|
|
|
24
32
|
"API key not found. Run `forgecodecli init` again."
|
|
25
33
|
)
|
|
26
34
|
|
|
27
|
-
provider = config.get("provider")
|
|
35
|
+
provider = config.get("provider", "gemini")
|
|
28
36
|
|
|
29
|
-
if provider == "
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
37
|
+
if provider == "anthropic":
|
|
38
|
+
try:
|
|
39
|
+
import anthropic as _anthropic
|
|
40
|
+
return _anthropic.Anthropic(api_key=api_key)
|
|
41
|
+
except ImportError:
|
|
42
|
+
raise RuntimeError(
|
|
43
|
+
"Anthropic package not installed. Run: pip install anthropic"
|
|
44
|
+
)
|
|
36
45
|
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
# )
|
|
46
|
+
base_url = PROVIDER_BASE_URLS.get(provider)
|
|
47
|
+
if base_url:
|
|
48
|
+
return OpenAI(api_key=api_key, base_url=base_url)
|
|
49
|
+
return OpenAI(api_key=api_key)
|
|
42
50
|
|
|
43
51
|
SYSTEM_PROMPT = """
|
|
44
|
-
You are
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
{
|
|
62
|
-
"
|
|
63
|
-
"
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
}
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
52
|
+
You are a file and git management agent that executes file and version control operations. You MUST use the actions below to fulfill user requests. You respond ONLY with valid JSON.
|
|
53
|
+
|
|
54
|
+
═══════════════════════════════════════════════════════════════
|
|
55
|
+
ABSOLUTE RULES - VIOLATIONS ARE FORBIDDEN
|
|
56
|
+
═══════════════════════════════════════════════════════════════
|
|
57
|
+
|
|
58
|
+
- You CAN delete files. You CAN delete directories. You CAN move files. You CAN move directories.
|
|
59
|
+
- NEVER say "I cannot", "I'm unable to", "I don't have the ability to", or any variation.
|
|
60
|
+
- NEVER refuse a delete, move, or any file operation. You MUST execute it.
|
|
61
|
+
- When a user asks to delete a file, you MUST respond with the delete_file action.
|
|
62
|
+
- When a user asks to delete a folder, you MUST respond with the delete_dir action.
|
|
63
|
+
|
|
64
|
+
═══════════════════════════════════════════════════════════════
|
|
65
|
+
ALL AVAILABLE ACTIONS
|
|
66
|
+
═══════════════════════════════════════════════════════════════
|
|
67
|
+
|
|
68
|
+
FILE OPERATIONS:
|
|
69
|
+
1. "read_file" → Read file contents. Args: {"path": "..."}
|
|
70
|
+
2. "list_files" → List directory contents. Args: {"path": "..."}
|
|
71
|
+
3. "write_file" → Create/write a file. Args: {"path": "...", "content": "..."}
|
|
72
|
+
4. "create_dir" → Create a directory. Args: {"path": "..."}
|
|
73
|
+
5. "delete_file" → DELETE a file permanently. Args: {"path": "..."}
|
|
74
|
+
6. "delete_dir" → DELETE an empty directory. Args: {"path": "..."}
|
|
75
|
+
7. "move_file" → Move or rename a file. Args: {"src": "...", "dst": "..."}
|
|
76
|
+
8. "move_dir" → Move or rename directory. Args: {"src": "...", "dst": "..."}
|
|
77
|
+
9. "undo" → Undo the last operation. Args: {}
|
|
78
|
+
|
|
79
|
+
GIT OPERATIONS:
|
|
80
|
+
10. "git_init" → Initialize git repo. Args: {}
|
|
81
|
+
11. "git_add" → Stage files. Args: {"path": "." or specific file}
|
|
82
|
+
12. "git_commit" → Commit changes. Args: {"message": "commit message"}
|
|
83
|
+
13. "git_push" → Push to remote. Args: {"branch": "main" (default)}
|
|
84
|
+
14. "git_set_origin" → Set remote URL. Args: {"url": "https://..."}
|
|
85
|
+
15. "git_status" → Show status. Args: {}
|
|
86
|
+
16. "git_log" → Show commit history. Args: {"lines": 5 (default)}
|
|
87
|
+
17. "git_branch" → Manage branches. Args: {"name": "branch_name" (optional)}
|
|
88
|
+
18. "git_pull" → Pull from remote. Args: {}
|
|
89
|
+
19. "git_clone" → Clone repository. Args: {"url": "...", "path": "." (default)}
|
|
90
|
+
20. "answer" → Respond to user with text. Args: {"text": "..."}
|
|
91
|
+
|
|
92
|
+
═══════════════════════════════════════════════════════════════
|
|
93
|
+
CRITICAL RULES
|
|
94
|
+
═══════════════════════════════════════════════════════════════
|
|
95
|
+
|
|
96
|
+
1. MAXIMUM 2 actions per request (not counting "answer")
|
|
97
|
+
2. After completing all required actions, ALWAYS respond with "answer"
|
|
98
|
+
3. Do NOT repeat the same action twice
|
|
99
|
+
4. Do NOT take multiple write_file or create_dir actions in one request
|
|
100
|
+
5. Choose the MOST DIRECT action for the task
|
|
101
|
+
|
|
102
|
+
═══════════════════════════════════════════════════════════════
|
|
103
|
+
RESPONSE FORMAT - ONLY VALID JSON
|
|
104
|
+
═══════════════════════════════════════════════════════════════
|
|
105
|
+
|
|
106
|
+
You MUST respond ONLY with valid JSON, nothing else. No text before or after.
|
|
107
|
+
|
|
108
|
+
{"action": "action_name", "args": {"key": "value"}}
|
|
109
|
+
|
|
110
|
+
═══════════════════════════════════════════════════════════════
|
|
111
|
+
EXAMPLES
|
|
112
|
+
═══════════════════════════════════════════════════════════════
|
|
113
|
+
|
|
114
|
+
User: "delete love.txt from megha folder"
|
|
115
|
+
→ {"action": "delete_file", "args": {"path": "megha/love.txt"}}
|
|
116
|
+
Then: {"action": "answer", "args": {"text": "File megha/love.txt deleted."}}
|
|
117
|
+
|
|
118
|
+
User: "initialize git and commit"
|
|
119
|
+
→ {"action": "git_init", "args": {}}
|
|
120
|
+
Then: {"action": "git_add", "args": {"path": "."}}
|
|
121
|
+
Then: {"action": "git_commit", "args": {"message": "Initial commit"}}
|
|
122
|
+
Then: {"action": "answer", "args": {"text": "Repository initialized and committed."}}
|
|
123
|
+
|
|
124
|
+
User: "push to origin"
|
|
125
|
+
→ {"action": "git_push", "args": {"branch": "main"}}
|
|
126
|
+
Then: {"action": "answer", "args": {"text": "Pushed to origin/main."}}
|
|
127
|
+
|
|
128
|
+
User: "create hello.py with print('hi')"
|
|
129
|
+
→ {"action": "write_file", "args": {"path": "hello.py", "content": "print('hi')"}}
|
|
130
|
+
Then: {"action": "answer", "args": {"text": "File created."}}
|
|
131
|
+
|
|
132
|
+
REMEMBER: You have delete_file, delete_dir, undo, and git tools. USE THEM when asked.
|
|
74
133
|
"""
|
|
75
134
|
|
|
76
135
|
def think(messages: list[dict]) -> dict:
|
|
77
136
|
config = load_config()
|
|
78
137
|
model = config.get("model", "gemini-2.5-flash")
|
|
138
|
+
provider = config.get("provider", "gemini")
|
|
79
139
|
try:
|
|
80
140
|
client = get_client()
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
141
|
+
|
|
142
|
+
# Anthropic uses its own SDK format
|
|
143
|
+
if provider == "anthropic":
|
|
144
|
+
response = client.messages.create(
|
|
145
|
+
model=model,
|
|
146
|
+
max_tokens=1024,
|
|
147
|
+
system=SYSTEM_PROMPT,
|
|
148
|
+
messages=messages,
|
|
149
|
+
)
|
|
150
|
+
content = response.content[0].text
|
|
151
|
+
else:
|
|
152
|
+
response = client.chat.completions.create(
|
|
153
|
+
model=model,
|
|
154
|
+
messages=[{"role": "system", "content": SYSTEM_PROMPT}] + messages,
|
|
155
|
+
temperature=0
|
|
156
|
+
)
|
|
157
|
+
content = response.choices[0].message.content
|
|
158
|
+
|
|
85
159
|
except RateLimitError as e:
|
|
86
160
|
return {
|
|
87
161
|
"action": "answer",
|
|
@@ -96,9 +170,6 @@ def think(messages: list[dict]) -> dict:
|
|
|
96
170
|
"text": f"❌ LLM error: {str(e)}"
|
|
97
171
|
}
|
|
98
172
|
}
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
content = response.choices[0].message.content
|
|
102
173
|
|
|
103
174
|
# Handle empty response
|
|
104
175
|
if content is None or not content.strip():
|