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 CHANGED
@@ -1,14 +1,22 @@
1
1
  # from dotenv import load_dotenv
2
2
  from openai import OpenAI
3
- # import os
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
- # Load env
11
- # load_dotenv()
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 == "gemini":
30
- return OpenAI(
31
- api_key=api_key,
32
- base_url="https://generativelanguage.googleapis.com/v1beta/openai/"
33
- )
34
-
35
- raise RuntimeError(f"Unsupported provider: {provider}")
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
- # client = OpenAI(
39
- # api_key=os.getenv("GEMINI_API_KEY"),
40
- # base_url="https://generativelanguage.googleapis.com/v1beta/openai/"
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 an agent that decides what action to take.
45
-
46
- CRITICAL RULES:
47
- 1. Each user request requires AT MOST TWO action that makes a change (write_file, create_dir, read_file, or list_files)
48
- 2. After you have completed all required actions
49
- (up to the allowed limit), respond with "answer"
50
- 3. Do NOT take multiple write_file or create_dir actions
51
- 4. Do NOT repeat actions
52
-
53
- Actions available:
54
- - "read_file": read a file
55
- - "list_files": list directory contents
56
- - "write_file": write/create a file
57
- - "create_dir": create a directory
58
- - "answer": respond to the user (use this after task is complete)
59
-
60
- RESPONSE FORMAT - You MUST respond ONLY with valid JSON, nothing else:
61
- {
62
- "action": "answer",
63
- "args": {
64
- "text": "Your message here"
65
- }
66
- }
67
-
68
- Examples:
69
- - User: "create file.py with print('hello')" write_file appears → immediately return answer
70
- - User: "read file.py" → read_file → content appears → immediately return answer
71
- - User: "what's in src?" → list_files → files appear → immediately return answer
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
- response = client.chat.completions.create(
82
- model=model,
83
- messages=[{"role": "system", "content": SYSTEM_PROMPT}] + messages
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():