AGofficial commited on
Commit
b2863d5
·
verified ·
1 Parent(s): 332b75a

Upload 24 files

Browse files
.gitattributes CHANGED
@@ -33,3 +33,13 @@ saved_model/**/* filter=lfs diff=lfs merge=lfs -text
33
  *.zip filter=lfs diff=lfs merge=lfs -text
34
  *.zst filter=lfs diff=lfs merge=lfs -text
35
  *tfevents* filter=lfs diff=lfs merge=lfs -text
 
 
 
 
 
 
 
 
 
 
 
33
  *.zip filter=lfs diff=lfs merge=lfs -text
34
  *.zst filter=lfs diff=lfs merge=lfs -text
35
  *tfevents* filter=lfs diff=lfs merge=lfs -text
36
+ agicli/pfps/anime_pfp1.png filter=lfs diff=lfs merge=lfs -text
37
+ agicli/pfps/anime_pfp2.png filter=lfs diff=lfs merge=lfs -text
38
+ agicli/pfps/anime_pfp3.png filter=lfs diff=lfs merge=lfs -text
39
+ agicli/pfps/anime_pfp4.png filter=lfs diff=lfs merge=lfs -text
40
+ agicli/pfps/anime_pfp5.png filter=lfs diff=lfs merge=lfs -text
41
+ agicli/pfps/anime_pfp6.png filter=lfs diff=lfs merge=lfs -text
42
+ agicli/pfps/anime_pfp7.png filter=lfs diff=lfs merge=lfs -text
43
+ agicli/pfps/anime_pfp8.png filter=lfs diff=lfs merge=lfs -text
44
+ agicli/trees.jpg filter=lfs diff=lfs merge=lfs -text
45
+ logo.png filter=lfs diff=lfs merge=lfs -text
LICENSE ADDED
@@ -0,0 +1,21 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ MIT License
2
+
3
+ Copyright (c) 2026 AG CORP
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.
README.md CHANGED
@@ -1,3 +1,69 @@
1
- ---
2
- license: mit
3
- ---
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # AGI CLI
2
+
3
+ ```text
4
+ █████╗ ██████╗ ██╗ ██████╗██╗ ██╗
5
+ ██╔══██╗██╔════╝ ██║ ██╔════╝██║ ██║
6
+ ███████║██║ ███╗██║ ██║ ██║ ██║
7
+ ██╔══██║██║ ██║██║ ██║ ██║ ██║
8
+ ██║ ██║╚██████╔╝██║ ╚██████╗███████╗██║
9
+ ╚═╝ ╚═╝ ╚═════╝ ╚═╝ ╚═════╝╚══════╝╚═╝
10
+ ```
11
+
12
+ Welcome to **AGI CLI**, an advanced command-line interface developed by AG Corp. This tool provides a powerful environment for interacting with AI agents, managing projects, and more.
13
+
14
+ ## Features
15
+
16
+ - ✦ **AGI Agent**: Launch an autonomous agent that can think, use tools, and complete complex tasks.
17
+ - ✦ **Multiple Model Support**: Support for both **Gemini** and **Poe (claude-haiku-4.5)**.
18
+ - ✦ **AI Assistant**: Quick access to powered AI assistant for general queries.
19
+ - ✦ **Project Management**: Tools to initialize and manage your coding projects.
20
+ - ✦ **Enhanced CLI**: Beautiful colored output and structured UI elements.
21
+
22
+ ## Getting Started
23
+
24
+ ### Prerequisites
25
+
26
+ - Python 3.x
27
+ - A Gemini API Key (you will be prompted to enter this upon first use or when using AI features).
28
+ - Or a Poe API Key (you will be prompted to enter this upon first use or when using AI features).
29
+
30
+ ### Installation
31
+
32
+ 1. Clone this repository or download the source code.
33
+ 2. Navigate to the project directory.
34
+
35
+ ### Running the CLI
36
+
37
+ To start the AGI CLI, run:
38
+
39
+ ```bash
40
+ python3 main.py
41
+ ```
42
+
43
+ ## Available Commands
44
+
45
+ Once inside the CLI, you can use the following commands:
46
+
47
+ - `agi`: Launch the AGI Agent for autonomous task execution.
48
+ - `ai`: Talk directly to the AI assistant.
49
+ - `project`: Create a new project structure.
50
+ - `settings`: Choose between Gemini and Poe models and manage API keys.
51
+ - `help`: Display the list of available commands.
52
+ - `echo [message]`: Echo back a message in the CLI.
53
+ - `exit`: Safely exit the application.
54
+
55
+ ## Project Structure
56
+
57
+ - `agicli/`: Core package containing all logic.
58
+ - `agi.py`: AGI Agent implementation.
59
+ - `ai.py`: Gemini AI interface.
60
+ - `welcome.py`: Initialization and user setup.
61
+ - `printplus.py`: Enhanced printing and ASCII art engine.
62
+ - `sandbox/`: Secure area for agent-generated files.
63
+ - `user/`: Secure Local storage for user settings and API keys.
64
+
65
+ ## License
66
+
67
+ MIT
68
+
69
+ Created by AG Corp.
agicli/agi.py ADDED
@@ -0,0 +1,240 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ from agicli.printplus import pr, inp
2
+ from agicli.welcome import get_detail, save_detail
3
+ from agicli.ai import ask
4
+ import os
5
+ import re
6
+ from agicli.tools import agi_tool, get_tools_prompt, handle_custom_tool, TOOL_REGISTRY
7
+ from agicli.agi_armamentarium import search_armamentarium
8
+ import agicli.agi_armamentarium_expanded
9
+ DEBUG = False
10
+
11
+ def get_currrent_date():
12
+ from datetime import datetime
13
+ return datetime.now().strftime("%B %d, %Y")
14
+
15
+ def get_current_time():
16
+ from datetime import datetime
17
+ return datetime.now().strftime("%I:%M %p")
18
+
19
+ @agi_tool
20
+ def current_weather():
21
+ """Returns the current weather in the user's location based on the season."""
22
+ from datetime import datetime
23
+ month = datetime.now().month
24
+ if month in [12, 1, 2]:
25
+ return "It's winter season with cold temperatures."
26
+ elif month in [3, 4, 5]:
27
+ return "It's spring season with mild temperatures."
28
+ elif month in [6, 7, 8]:
29
+ return "It's summer season with warm temperatures."
30
+ else:
31
+ return "It's autumn season with cool temperatures."
32
+
33
+ @agi_tool
34
+ def write(filename, content, mode="rewrite"):
35
+ """Writes a file with the specified filename and content. supported modes are 'rewrite' and 'append'."""
36
+ SANDBOX_PATH = os.path.join(os.getcwd(), "agicli", "sandbox")
37
+ try:
38
+ if not os.path.exists(SANDBOX_PATH):
39
+ os.makedirs(SANDBOX_PATH)
40
+
41
+ filepath = os.path.join(SANDBOX_PATH, filename)
42
+
43
+ if mode == "append":
44
+ with open(filepath, "a") as f:
45
+ f.write(content)
46
+ return f"Successfully appended to file: {filename}"
47
+ else:
48
+ with open(filepath, "w") as f:
49
+ f.write(content)
50
+ return f"Successfully wrote file: {filename} ({len(content)} bytes)"
51
+ except Exception as e:
52
+ return f"Error writing file: {str(e)}"
53
+
54
+ @agi_tool
55
+ def filetree():
56
+ """Lists the current files in the workspace (sandbox)."""
57
+ SANDBOX_PATH = os.path.join(os.getcwd(), "agicli", "sandbox")
58
+ try:
59
+ if not os.path.exists(SANDBOX_PATH):
60
+ return "Filetree: Sandbox is empty (no files yet)"
61
+
62
+ file_list = []
63
+ for root, dirs, files in os.walk(SANDBOX_PATH):
64
+ rel_root = os.path.relpath(root, SANDBOX_PATH)
65
+ for f in files:
66
+ if rel_root == ".":
67
+ file_list.append(f)
68
+ else:
69
+ file_list.append(os.path.join(rel_root, f))
70
+
71
+ if file_list:
72
+ tree_output = "\n".join(f" - {f}" for f in sorted(file_list))
73
+ return f"Filetree:\n{tree_output}"
74
+ else:
75
+ return "Filetree: Sandbox is empty (no files yet)"
76
+ except Exception as e:
77
+ return f"Error reading filetree: {str(e)}"
78
+
79
+ @agi_tool
80
+ def read(filename):
81
+ """Reads the content of the specified file and returns it."""
82
+ SANDBOX_PATH = os.path.join(os.getcwd(), "agicli", "sandbox")
83
+ try:
84
+ filepath = os.path.join(SANDBOX_PATH, filename)
85
+ if not os.path.exists(filepath):
86
+ return f"Error: File not found: {filename}"
87
+ else:
88
+ with open(filepath, "r") as f:
89
+ file_content = f.read()
90
+ return f"Content of {filename}:\n{file_content}"
91
+ except Exception as e:
92
+ return f"Error reading file: {str(e)}"
93
+
94
+ def parse_tools(response):
95
+ """
96
+ Extracts tool calls from a response string and returns a list of dictionaries with name and attrs.
97
+ """
98
+ if "<tools>" not in response:
99
+ return []
100
+
101
+ tools_found = []
102
+ tool_sections = re.findall(r'<tools>([\s\S]*?)</tools>', response)
103
+
104
+ for section in tool_sections:
105
+ # More robust tag matching: skip over quoted strings when looking for closing > or />
106
+ tag_pattern = r'<(\w+)\s*((?:[^"\'/>]|"[^"]*"|\'[^\']*\')*?)\s*/?>'
107
+ # Wait, the regex I used before was better: r'<(\w+)\s*((?:[^"\'/>]|"[^"]*"|\'[^\']*\')*?)\s*/?>'
108
+ # Let's use the one that works.
109
+ tag_pattern = r'<(\w+)\s*((?:[^"\'/>]|"[^"]*"|\'[^\']*\')*?)\s*/?>'
110
+ tags = re.findall(tag_pattern, section)
111
+
112
+ for tag_name, attr_string in tags:
113
+ # Robust attribute extraction for both single and double quotes
114
+ attrs = {}
115
+ attr_pattern = r'(\w+)=((?:"[\s\S]*?(?<!\\)")|(?:\'[\s\S]*?(?<!\\)\'))'
116
+ for attr_match in re.finditer(attr_pattern, attr_string):
117
+ key = attr_match.group(1)
118
+ val = attr_match.group(2)[1:-1] # strip outer quotes
119
+
120
+ # Unescape basic escape sequences and common XML entities
121
+ val = val.replace("\\n", "\n").replace("\\t", "\t").replace('\\"', '"').replace("\\'", "'")
122
+ val = val.replace("&quot;", '"').replace("&apos;", "'").replace("&lt;", "<").replace("&gt;", ">").replace("&amp;", "&")
123
+ attrs[key] = val
124
+ tools_found.append({"name": tag_name, "attrs": attrs})
125
+
126
+ return tools_found
127
+
128
+ def launch_agent():
129
+ if len(str(get_detail("gemini_api_key"))) == 0:
130
+ pr("Welcome to AGI CLI! Let's set up your Gemini API key.")
131
+ api_key = inp("Please enter your Gemini API key: ")
132
+ save_detail("gemini_api_key", api_key)
133
+ pr("API key saved successfully.", color="green")
134
+
135
+ pr("AGI Agent is online.", color="green")
136
+
137
+ user_input = inp("What would you like the agent to do? ", color="red", margin=0)
138
+ context = []
139
+ context.append(user_input)
140
+
141
+ system_prompt = f"""
142
+ You are Alpha, an AGI Agent developed by AG Corp, today is {get_currrent_date()} at {get_current_time()}.
143
+ The user is called {get_detail("user_name")}. They have tasked you with the following prompt: {user_input}
144
+ As an AI Agent you are not limited to a single response, you can think step by step and take actions to complete the task.
145
+ In any part of your response, include one or more tool calls using XML format wrapped in a <tools> block like this:
146
+ <tools>
147
+ <tool_name arg1="value1" arg2="value2" />
148
+ </tools>
149
+
150
+ You now have access to the following CORE tools:
151
+ {get_tools_prompt()}
152
+ <preamble text="I will..." /> - you may use preamble text to say the next tool you will use
153
+ <done result="Provide a summary of your work and result here." /> - indicates you have completed the user's task, once you provide this tool the system will terminate you.
154
+
155
+ IMPORTANT: You have an "Armamentarium" of hundreds of other tools (calculators, stock prices, news, translations, etc.).
156
+ If you need a tool that is not listed above, you MUST call <search_armamentarium query="search term" /> to find and register it for use in subsequent turns.
157
+
158
+ Use these tools as needed to complete the user's task.
159
+ When using tool attributes, ensure you escape double quotes with a backslash (\") or use single quotes if the content contains double quotes.
160
+ You MUST call the <done /> tool within a <tools> block when you have completed the user's task.
161
+ If you call a tool, ALWAYS wait for the tool call result before calling the <done /> tool.
162
+ Never call the <done> tool your first turn when you call multiple tools, wait for the system to get back to you with the tool call results before calling the <done> tool.
163
+ The armamentarium has many tools like design guidelines, user timezone, random number generator, etc.
164
+ """
165
+ not_complete = True
166
+
167
+ context.append(system_prompt)
168
+ i = 0
169
+ max_iterations = 100 # Prevent infinite loops
170
+
171
+ # SANDBOX_PATH = os.path.join(os.getcwd(), "agicli", "sandbox") # This is now defined within each tool function
172
+
173
+ while not_complete and i < max_iterations:
174
+ i += 1
175
+
176
+ if DEBUG:
177
+ pr(f"\n--- Agent Iteration {i} ---\n", color="magenta")
178
+ pr(f"Current Context:\n" + "\n".join(context), color="yellow")
179
+
180
+ response = ask("\n".join(context))
181
+ pr(response, color="cyan")
182
+
183
+ if response.startswith("Error:"):
184
+ pr("API error encountered. Stopping agent.", color="red")
185
+ break
186
+
187
+ context.append(f"Assistant: {response}")
188
+
189
+ tools = parse_tools(response)
190
+ if tools:
191
+ results = []
192
+ for tool in tools:
193
+ tag_name = tool["name"]
194
+ attrs = tool["attrs"]
195
+
196
+ if tag_name == "done":
197
+ pr("Task completed by the agent.", color="green")
198
+ summary = attrs.get("result", "Task completed.")
199
+ pr(f"Summary of work: {summary}", color="blue")
200
+ results.append(f"Summary of work: {summary}")
201
+ not_complete = False
202
+ break
203
+
204
+ elif tag_name == "preamble":
205
+ preamble_text = attrs.get("text", "")
206
+ if preamble_text:
207
+ pr(f"🔮 {preamble_text}", color="magenta")
208
+ results.append(f"Preamble acknowledged: {preamble_text}")
209
+ else:
210
+ pr("🔮 Agent is thinking...", color="magenta")
211
+ results.append("Preamble acknowledged.")
212
+
213
+ else:
214
+ # Check dynamic registry
215
+ result = handle_custom_tool(tag_name, attrs)
216
+ if result is not None:
217
+ # Print relevant feedback for core tools
218
+ if tag_name == "current_weather":
219
+ pr(f"Current weather: {result}", color="blue")
220
+ elif tag_name == "write":
221
+ pr(f"📝 {result}", color="green")
222
+ elif tag_name == "filetree":
223
+ pr(f"📂 {result}", color="green")
224
+ elif tag_name == "read":
225
+ pr(f"📖 Read file: {attrs.get('filename')}", color="green")
226
+ else:
227
+ pr(f"🛠 Executed tool: {tag_name}", color="yellow")
228
+
229
+ results.append(result)
230
+ else:
231
+ results.append(f"Error: Tool '{tag_name}' not found.")
232
+
233
+ if results:
234
+ context.append(f"Tool results:\n" + "\n".join(results))
235
+
236
+ if not not_complete:
237
+ break
238
+
239
+ if i >= max_iterations:
240
+ pr("Max iterations reached. Stopping agent.", color="red")
agicli/agi_armamentarium.py ADDED
@@ -0,0 +1,162 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import difflib
2
+ import random
3
+ import secrets
4
+ import string
5
+ import math
6
+ from datetime import datetime, timezone, timedelta
7
+ import zoneinfo
8
+ import time
9
+ from agicli.tools import agi_tool, TOOL_REGISTRY
10
+ from agicli.printplus import pr
11
+
12
+ # This dictionary holds all the potential tools that are NOT in the model's immediate context.
13
+ ARMAMENTARIUM = {
14
+ "random_joke": {
15
+ "description": "Returns a random joke from a curated collection.",
16
+ "parameters": [],
17
+ "impl": lambda: random.choice([
18
+ "Why did the AI go to therapy? It had too many complex dependencies.",
19
+ "How many programmers does it take to change a light bulb? None, that's a hardware problem.",
20
+ "There are 10 types of people: those who understand binary, and those who don't.",
21
+ "Why do programmers always mix up Halloween and Christmas? Because Oct 31 == Dec 25.",
22
+ "An SQL query walks into a bar, walks up to two tables, and asks, 'Can I join you?'",
23
+ "A programmer's wife tells him: 'Go to the store and get a loaf of bread. If they have eggs, get a dozen.' He returns with 12 loaves of bread.",
24
+ "Why do Java developers wear glasses? Because they don't C#."
25
+ ])
26
+ },
27
+ "world_clock": {
28
+ "description": "Shows the current time for major cities around the world, handling DST properly.",
29
+ "parameters": [],
30
+ "impl": lambda: (
31
+ f"UTC: {datetime.now(timezone.utc).strftime('%H:%M:%S')} | "
32
+ f"New York: {datetime.now(zoneinfo.ZoneInfo('America/New_York')).strftime('%H:%M:%S')} | "
33
+ f"London: {datetime.now(zoneinfo.ZoneInfo('Europe/London')).strftime('%H:%M:%S')} | "
34
+ f"Tokyo: {datetime.now(zoneinfo.ZoneInfo('Asia/Tokyo')).strftime('%H:%M:%S')} | "
35
+ f"Local: {datetime.now().strftime('%H:%M:%S')}"
36
+ )
37
+ },
38
+ "get_user_timezone": {
39
+ "description": "Returns the current user's timezone name and UTC offset.",
40
+ "parameters": [],
41
+ "impl": lambda: f"Timezone: {datetime.now().astimezone().tzname()} | Offset: {datetime.now().astimezone().strftime('%z')}"
42
+ },
43
+ "generate_password": {
44
+ "description": "Generates a secure, random password. args: length, use_symbols ('true'/'false')",
45
+ "parameters": ["length", "use_symbols"],
46
+ "impl": lambda length=12, use_symbols="true": "".join(
47
+ secrets.choice(
48
+ string.ascii_letters + string.digits + (string.punctuation if str(use_symbols).lower() == "true" else "")
49
+ ) for _ in range(int(length))
50
+ )
51
+ },
52
+ "random_number": {
53
+ "description": "Generates a random integer between min and max.",
54
+ "parameters": ["min_val", "max_val"],
55
+ "impl": lambda min_val=0, max_val=100: random.randint(int(min_val), int(max_val))
56
+ },
57
+ "throw_dice": {
58
+ "description": "Simulates throwing a dice with N sides.",
59
+ "parameters": ["sides"],
60
+ "impl": lambda sides=6: random.randint(1, int(sides))
61
+ },
62
+ "throw_coin": {
63
+ "description": "Simulates a coin flip, returning 'Heads' or 'Tails'.",
64
+ "parameters": [],
65
+ "impl": lambda: random.choice(["Heads", "Tails"])
66
+ },
67
+ "calculator": {
68
+ "description": "Performs complex mathematical operations. Supported: +, -, *, /, (, ), and digits.",
69
+ "parameters": ["expression"],
70
+ "impl": lambda expression: (
71
+ str(eval(expression, {"__builtins__": None, "math": math}, {}))
72
+ if all(c in "0123456789+-*/()%. " for c in expression)
73
+ else "Error: Invalid characters in expression."
74
+ )
75
+ },
76
+ "get_system_info": {
77
+ "description": "Returns basic information about the current platform.",
78
+ "parameters": [],
79
+ "impl": lambda: f"OS: {__import__('platform').system()} {__import__('platform').release()} | Python: {__import__('platform').python_version()}"
80
+ }
81
+ }
82
+
83
+ import inspect
84
+ import functools
85
+
86
+ def armamentarium_tool(func):
87
+ """
88
+ Decorator to register a function as an armamentarium tool.
89
+ Extracts description from docstring and parameters from signature.
90
+ """
91
+ @functools.wraps(func)
92
+ def wrapper(*args, **kwargs):
93
+ return func(*args, **kwargs)
94
+
95
+ sig = inspect.signature(func)
96
+ params = [p.name for p in sig.parameters.values() if p.default == inspect.Parameter.empty or p.default != inspect.Parameter.empty]
97
+ # Filter out self/cls if methods, though these are likely standalone functions
98
+ params = [p for p in params if p not in ('self', 'cls')]
99
+
100
+ description = func.__doc__.strip() if func.__doc__ else "No description provided."
101
+
102
+ ARMAMENTARIUM[func.__name__] = {
103
+ "description": description,
104
+ "parameters": params,
105
+ "impl": func
106
+ }
107
+ return wrapper
108
+
109
+
110
+ @agi_tool
111
+ def search_armamentarium(query: str):
112
+ """
113
+ Searches the Armamentarium for a tool that matches the query.
114
+ If a match is found, the tool is dynamically revealed to the model.
115
+ """
116
+ query = query.lower()
117
+ query_words = [w for w in query.split() if len(w) > 2] # Ignore tiny words
118
+ matches = []
119
+
120
+ for name, info in ARMAMENTARIUM.items():
121
+ name_lower = name.lower()
122
+ desc_lower = info["description"].lower()
123
+
124
+ # 1. Direct substring match (good for specific tool names)
125
+ if query in name_lower or query in desc_lower:
126
+ matches.append(name)
127
+ continue
128
+
129
+ # 2. Keyword match (any word in query matches name or desc)
130
+ if any(word in name_lower or word in desc_lower for word in query_words):
131
+ matches.append(name)
132
+
133
+ # Fuzzy search as fallback
134
+ if not matches:
135
+ all_names = list(ARMAMENTARIUM.keys())
136
+ fuzzy_matches = difflib.get_close_matches(query, all_names, n=3, cutoff=0.2)
137
+ matches.extend(fuzzy_matches)
138
+
139
+ if not matches:
140
+ pr(f"❌ No tools matching '{query}' found in armamentarium.", color="red")
141
+ return "No tools matching your query were found in the armamentarium."
142
+
143
+ pr(f"🔍 Armamentarium Search: '{query}'", color="cyan")
144
+
145
+ result_text = "The following tools were found and are now AVAILABLE for you to use:\n\n"
146
+ for name in set(matches):
147
+ info = ARMAMENTARIUM[name]
148
+
149
+ # Register the tool dynamically into the global registry so the next call works
150
+ if name not in TOOL_REGISTRY:
151
+ TOOL_REGISTRY[name] = {
152
+ "func": info["impl"],
153
+ "description": info["description"],
154
+ "parameters": info["parameters"]
155
+ }
156
+
157
+ attr_part = " ".join([f'{p}=""' for p in info['parameters']])
158
+ tool_line = f'<{name} {attr_part} /> - {info["description"]}'
159
+ result_text += tool_line + "\n"
160
+ pr(f" ✨ {tool_line}", color="orange")
161
+
162
+ return result_text
agicli/agi_armamentarium_expanded.py ADDED
@@ -0,0 +1,60 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ from agicli.agi_armamentarium import armamentarium_tool
2
+ import random
3
+ import datetime
4
+ import os
5
+
6
+ @armamentarium_tool
7
+ def get_programming_quote():
8
+ """
9
+ Returns a random inspirational quote about programming.
10
+ """
11
+ quotes = [
12
+ "First, solve the problem. Then, write the code. - John Johnson",
13
+ "Experience is the name everyone gives to their mistakes. - Oscar Wilde",
14
+ "Code is like humor. When you have to explain it, it's bad. - Cory House",
15
+ "Fix the cause, not the symptom. - Steve McConnell",
16
+ "Optimism is an occupational hazard of programming: feedback is the treatment. - Kent Beck"
17
+ ]
18
+ return random.choice(quotes)
19
+
20
+ @armamentarium_tool
21
+ def calculate_days_between(date1_str, date2_str):
22
+ """
23
+ Calculates the number of days between two dates.
24
+ Format for dates should be YYYY-MM-DD.
25
+ """
26
+ d1 = datetime.datetime.strptime(date1_str, "%Y-%m-%d")
27
+ d2 = datetime.datetime.strptime(date2_str, "%Y-%m-%d")
28
+ return abs((d2 - d1).days)
29
+
30
+ @armamentarium_tool
31
+ def reverse_string(text):
32
+ """
33
+ Reverses the given string.
34
+ """
35
+ return text[::-1]
36
+
37
+ @armamentarium_tool
38
+ def count_character_occurrences(text, character):
39
+ """
40
+ Counts how many times a specific character appears in the given text.
41
+ Example: finding how many 'r's are in 'strawberry'.
42
+ Case-sensitive.
43
+ """
44
+ return text.count(character)
45
+
46
+ @armamentarium_tool
47
+ def get_design_guidelines():
48
+ """
49
+ Returns the content of the UI design guidelines (ui.html) to help models
50
+ understand how to design consistent sites.
51
+ """
52
+ try:
53
+ # data file is in the same directory as this module (agicli/)
54
+ current_dir = os.path.dirname(os.path.abspath(__file__))
55
+ file_path = os.path.join(current_dir, "ui.html")
56
+
57
+ with open(file_path, "r", encoding="utf-8") as f:
58
+ return f.read()
59
+ except Exception as e:
60
+ return f"Error reading design guidelines: {str(e)}"
agicli/ai.py ADDED
@@ -0,0 +1,197 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import requests
2
+ import json
3
+ import os
4
+ import sys
5
+ import time
6
+ import threading
7
+ from agicli.printplus import pr, inp, _token_tracker
8
+
9
+ from agicli.welcome import get_detail, save_detail
10
+
11
+ # Gemini API Configuration
12
+ MODEL_ID = "gemini-flash-latest"
13
+ GENERATE_CONTENT_API = "streamGenerateContent"
14
+
15
+
16
+ def estimate_tokens(text: str) -> int:
17
+ """Estimate token count for text (roughly 4 chars per token)."""
18
+ return max(1, len(text) // 4)
19
+
20
+
21
+ class LoadingAnimation:
22
+ """Animated loading indicator with alternating ✦✧ symbols."""
23
+
24
+ def __init__(self):
25
+ self.running = False
26
+ self.thread = None
27
+
28
+ def start(self):
29
+ """Start the loading animation."""
30
+ self.running = True
31
+ self.thread = threading.Thread(target=self._animate)
32
+ self.thread.start()
33
+
34
+ def stop(self):
35
+ """Stop the loading animation."""
36
+ self.running = False
37
+ if self.thread:
38
+ self.thread.join()
39
+ # Clear the animation line
40
+ sys.stdout.write('\r' + ' ' * 20 + '\r')
41
+ sys.stdout.flush()
42
+
43
+ def _animate(self):
44
+ """Run the animation loop."""
45
+ symbols = ['✦', '✧']
46
+ idx = 0
47
+ while self.running:
48
+ sys.stdout.write(f'\r {symbols[idx]} ')
49
+ sys.stdout.flush()
50
+ idx = (idx + 1) % len(symbols)
51
+ time.sleep(0.3)
52
+
53
+ def build_gemini_payload(user_input: str) -> dict:
54
+ """Build the request payload for the Gemini API."""
55
+ return {
56
+ "contents": [
57
+ {
58
+ "role": "user",
59
+ "parts": [
60
+ {
61
+ "text": user_input
62
+ }
63
+ ]
64
+ }
65
+ ],
66
+ "generationConfig": {
67
+ "thinkingConfig": {
68
+ "thinkingBudget": 0
69
+ }
70
+ }
71
+ }
72
+
73
+ def ask_gemini(prompt: str, api_key: str) -> str:
74
+ """Helper to call Gemini API."""
75
+ url = f"https://generativelanguage.googleapis.com/v1beta/models/{MODEL_ID}:{GENERATE_CONTENT_API}?alt=sse&key={api_key}"
76
+ payload = build_gemini_payload(prompt)
77
+
78
+ # Track tokens sent
79
+ tokens_sent = estimate_tokens(prompt)
80
+ if _token_tracker:
81
+ _token_tracker.add_tokens(sent=tokens_sent)
82
+
83
+ loader = LoadingAnimation()
84
+ loader.start()
85
+
86
+ try:
87
+ response = requests.post(
88
+ url,
89
+ headers={"Content-Type": "application/json"},
90
+ json=payload,
91
+ stream=True
92
+ )
93
+
94
+ loader.stop()
95
+
96
+ if response.status_code != 200:
97
+ return f"Error: Gemini API returned status code {response.status_code}"
98
+
99
+ full_response = ""
100
+ for line in response.iter_lines():
101
+ if line:
102
+ line_text = line.decode('utf-8')
103
+ if line_text.startswith('data: '):
104
+ json_str = line_text[6:]
105
+ try:
106
+ data = json.loads(json_str)
107
+ if 'candidates' in data:
108
+ for candidate in data['candidates']:
109
+ if 'content' in candidate and 'parts' in candidate['content']:
110
+ for part in candidate['content']['parts']:
111
+ if 'text' in part:
112
+ full_response += part['text']
113
+ except json.JSONDecodeError:
114
+ continue
115
+
116
+ # Track tokens received
117
+ if full_response and _token_tracker:
118
+ tokens_received = estimate_tokens(full_response)
119
+ _token_tracker.add_tokens(received=tokens_received)
120
+
121
+ return full_response if full_response else "No response received from Gemini."
122
+ except requests.RequestException as e:
123
+ loader.stop()
124
+ return f"Error: {str(e)}"
125
+
126
+ def ask_poe(prompt: str, api_key: str) -> str:
127
+ """Helper to call Poe API."""
128
+ url = "https://api.poe.com/v1/chat/completions"
129
+ headers = {
130
+ "Content-Type": "application/json",
131
+ "Authorization": f"Bearer {api_key}"
132
+ }
133
+ payload = {
134
+ "model": "claude-haiku-4.5",
135
+ "messages": [
136
+ {
137
+ "role": "user",
138
+ "content": prompt
139
+ }
140
+ ]
141
+ }
142
+
143
+ # Track tokens sent
144
+ tokens_sent = estimate_tokens(prompt)
145
+ if _token_tracker:
146
+ _token_tracker.add_tokens(sent=tokens_sent)
147
+
148
+ loader = LoadingAnimation()
149
+ loader.start()
150
+
151
+ try:
152
+ response = requests.post(url, headers=headers, json=payload)
153
+ loader.stop()
154
+
155
+ if response.status_code != 200:
156
+ return f"Error: Poe API returned status code {response.status_code}. {response.text}"
157
+
158
+ data = response.json()
159
+ if "choices" in data and len(data["choices"]) > 0:
160
+ result = data["choices"][0]["message"]["content"]
161
+ # Track tokens received
162
+ if result and _token_tracker:
163
+ tokens_received = estimate_tokens(result)
164
+ _token_tracker.add_tokens(received=tokens_received)
165
+ return result
166
+ return "No response received from Poe."
167
+ except requests.RequestException as e:
168
+ loader.stop()
169
+ return f"Error: {str(e)}"
170
+
171
+ def ask(prompt: str) -> str:
172
+ """
173
+ Send a prompt to the active AI service and return the response.
174
+ """
175
+ active_model = get_detail("active_model") or "gemini"
176
+
177
+ if active_model == "poe":
178
+ api_key = get_detail("poe_api_key")
179
+ if not api_key:
180
+ pr("Poe API key not found.", color="red")
181
+ api_key = inp("Please enter your Poe API key: ", color="red", margin=0).strip()
182
+ save_detail("poe_api_key", api_key)
183
+
184
+ if not api_key:
185
+ return "Error: No Poe API key provided."
186
+ return ask_poe(prompt, api_key)
187
+
188
+ else: # Default to Gemini
189
+ api_key = get_detail("gemini_api_key")
190
+ if not api_key:
191
+ pr("Gemini API key not found.", color="red")
192
+ api_key = inp("Please enter your Gemini API key: ", color="red", margin=0).strip()
193
+ save_detail("gemini_api_key", api_key)
194
+
195
+ if not api_key:
196
+ return "Error: No Gemini API key provided."
197
+ return ask_gemini(prompt, api_key)
agicli/home.py ADDED
@@ -0,0 +1,57 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ from agicli.printplus import pr, inp
2
+ from agicli.welcome import get_detail, save_detail
3
+ from agicli.ai import ask
4
+
5
+ def home():
6
+ pr(f"Welcome, {get_detail('user_name')}!", color="red")
7
+
8
+ running = True
9
+
10
+ while running:
11
+ input_command = inp("AGI CLI > ", color="red", margin=0).strip().lower()
12
+ if input_command == "exit":
13
+ pr("Exiting AGI CLI. Goodbye!", color="red")
14
+ running = False
15
+ elif input_command == "help":
16
+ help_docs = """
17
+ Available Commands:
18
+ - help: Show this help message
19
+ - exit: Exit the AGI CLI
20
+ - echo [message]: Echo the provided message
21
+ - project: creates a new project
22
+ - ai: talk to the AI assistant & set-up API key
23
+ - agi: launch the AGI Agent
24
+ - settings: Change model settings (Gemini/Poe)
25
+
26
+ """
27
+ pr(help_docs, color="cyan")
28
+ elif "echo " in input_command:
29
+ to_echo = input_command.replace("echo ", "", 1)
30
+ pr(to_echo, color="green")
31
+
32
+ elif "project" in input_command:
33
+ from agicli.project import create_project
34
+ create_project()
35
+
36
+ elif "ai" in input_command:
37
+ user_input = inp("You: ", color="red", margin=0).strip()
38
+ pr("✦ AI is thinking...", color="magenta")
39
+ response = ask(user_input)
40
+ pr(f"AI: {response}", color="green")
41
+
42
+ elif "agi" in input_command:
43
+ from agicli.agi import launch_agent
44
+ launch_agent()
45
+
46
+ elif "settings" in input_command:
47
+ current_model = get_detail("active_model") or "gemini"
48
+ pr(f"Current model: {current_model}", color="blue")
49
+ new_model = inp("Set model (gemini/poe): ", color="green", margin=0).strip().lower()
50
+ if new_model in ["gemini", "poe"]:
51
+ save_detail("active_model", new_model)
52
+ pr(f"Active model set to {new_model}", color="green")
53
+ else:
54
+ pr("Invalid model. Please choose 'gemini' or 'poe'.", color="red")
55
+
56
+ else:
57
+ pr("Unknown command. Type 'help' for a list of commands.", color="yellow")
agicli/pfps/anime_pfp1.png ADDED

Git LFS Details

  • SHA256: 3ae5b2a9d926ed6d15fe04575c3a7a848f62cfd79626f8a4f158576b1db658b6
  • Pointer size: 132 Bytes
  • Size of remote file: 3.54 MB
agicli/pfps/anime_pfp2.png ADDED

Git LFS Details

  • SHA256: 7aee0437ab386b9891d7d707d8098ac6560731bb927f0a8dc0eddfa8ba0b2d78
  • Pointer size: 132 Bytes
  • Size of remote file: 4.07 MB
agicli/pfps/anime_pfp3.png ADDED

Git LFS Details

  • SHA256: 5caf12944929e65da18de5bf99bfde92f6cbd84ee201d3cf008985eb90697844
  • Pointer size: 132 Bytes
  • Size of remote file: 3.08 MB
agicli/pfps/anime_pfp4.png ADDED

Git LFS Details

  • SHA256: 95f8449a96342e55234ac2c7245a8a1710b6c11d13c51cb4ca4c6a2c5fd50e7a
  • Pointer size: 132 Bytes
  • Size of remote file: 4.42 MB
agicli/pfps/anime_pfp5.png ADDED

Git LFS Details

  • SHA256: d32f010b3d24918f242b72bf75c81784fdc9fd9a882bc3fa10ffd991d5f49dc7
  • Pointer size: 133 Bytes
  • Size of remote file: 14.7 MB
agicli/pfps/anime_pfp6.png ADDED

Git LFS Details

  • SHA256: c77612e2af69e7ba6d21cad75081f436f380f7b4e7eeb42e70e6e56f0309802b
  • Pointer size: 133 Bytes
  • Size of remote file: 15.8 MB
agicli/pfps/anime_pfp7.png ADDED

Git LFS Details

  • SHA256: ad4d2efc2077fd8329923ad58db4b6f83342e1bc93b4db82dc0e80ecf0ac295d
  • Pointer size: 133 Bytes
  • Size of remote file: 19.2 MB
agicli/pfps/anime_pfp8.png ADDED

Git LFS Details

  • SHA256: 2c2ca537ed53de69cf3da471dcef0cbcd0b28ae57191785acbc6725c8574a5bb
  • Pointer size: 133 Bytes
  • Size of remote file: 18.3 MB
agicli/printplus.py ADDED
@@ -0,0 +1,632 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+
2
+ """
3
+
4
+ A module that provides enhanced printing functionality with colored text and optional borders.
5
+ Supports both traditional terminal output and custom GUI terminal output.
6
+
7
+ """
8
+ import threading
9
+
10
+ # Global terminal reference (set by the GUI app)
11
+ _terminal = None
12
+ _token_tracker = None
13
+ _input_result = None
14
+ _input_event = threading.Event()
15
+
16
+
17
+ def _output(text: str, color: str = "white"):
18
+ """Output text to either the GUI terminal or standard console."""
19
+ if _terminal is not None:
20
+ _terminal.print(text, color)
21
+ else:
22
+ # Fallback to standard print with ANSI colors
23
+ text_color = colors.get(color, colors['reset'])
24
+ print(f"{text_color}{text}{colors['reset']}")
25
+
26
+ colors = {
27
+ "red": "\033[91m",
28
+ "green": "\033[92m",
29
+ "yellow": "\033[93m",
30
+ "blue": "\033[94m",
31
+ "magenta": "\033[95m",
32
+ "orange": "\033[33m",
33
+ "black": "\033[90m",
34
+ "cyan": "\033[96m",
35
+ "white": "\033[97m",
36
+ "reset": "\033[0m",
37
+ }
38
+ def pr(x, color="red", suround=False, surround_color="red", big_text=False, surround_char="", margin=1):
39
+
40
+ for i in range(margin):
41
+ _output("", color)
42
+
43
+ if not big_text:
44
+
45
+ if suround:
46
+ # Handle surround_char
47
+ if surround_char:
48
+ # If multiple chars, alternate them
49
+ if len(surround_char) > 1:
50
+ chars = list(surround_char)
51
+ border_top = ""
52
+ border_bottom = ""
53
+ for i in range(len(x) + 4):
54
+ border_top += chars[i % len(chars)]
55
+ border_bottom += chars[i % len(chars)]
56
+ left_char = chars[0]
57
+ right_char = chars[1 % len(chars)]
58
+ else:
59
+ # Single char
60
+ border_top = surround_char * (len(x) + 4)
61
+ border_bottom = surround_char * (len(x) + 4)
62
+ left_char = surround_char
63
+ right_char = surround_char
64
+
65
+ _output(border_top, surround_color)
66
+ _output(f"{left_char} {x} {right_char}", color)
67
+ _output(border_bottom, surround_color)
68
+ else:
69
+ # Default box drawing characters
70
+ border_top = "╔" + "═" * (len(x) + 2) + "╗"
71
+ border_bottom = "╚" + "═" * (len(x) + 2) + "╝"
72
+ _output(border_top, surround_color)
73
+ _output(f"║ {x} ║", color)
74
+ _output(border_bottom, surround_color)
75
+ else:
76
+ _output(str(x), color)
77
+
78
+ if big_text:
79
+ # Unicode block font character definitions
80
+ block_font = {
81
+ 'A': [
82
+ " █████╗ ",
83
+ "██╔══██╗",
84
+ "███████║",
85
+ "██╔══██║",
86
+ "██║ ██║",
87
+ "╚═╝ ╚═╝"
88
+ ],
89
+ 'B': [
90
+ "██████╗ ",
91
+ "██╔══██╗",
92
+ "██████╔╝",
93
+ "██╔══██╗",
94
+ "██████╔╝",
95
+ "╚═════╝ "
96
+ ],
97
+ 'C': [
98
+ " ██████╗",
99
+ "██╔════╝",
100
+ "██║ ",
101
+ "██║ ",
102
+ "╚██████╗",
103
+ " ╚═════╝"
104
+ ],
105
+ 'D': [
106
+ "██████╗ ",
107
+ "██╔══██╗",
108
+ "██║ ██║",
109
+ "██║ ██║",
110
+ "██████╔╝",
111
+ "╚═════╝ "
112
+ ],
113
+ 'E': [
114
+ "███████╗",
115
+ "██╔════╝",
116
+ "█████╗ ",
117
+ "██╔══╝ ",
118
+ "███████╗",
119
+ "╚══════╝"
120
+ ],
121
+ 'F': [
122
+ "███████╗",
123
+ "██╔════╝",
124
+ "█████╗ ",
125
+ "██╔══╝ ",
126
+ "██║ ",
127
+ "╚═╝ "
128
+ ],
129
+ 'G': [
130
+ " ██████╗ ",
131
+ "██╔════╝ ",
132
+ "██║ ███╗",
133
+ "██║ ██║",
134
+ "╚██████╔╝",
135
+ " ╚═════╝ "
136
+ ],
137
+ 'H': [
138
+ "██╗ ██╗",
139
+ "██║ ██║",
140
+ "███████║",
141
+ "██╔══██║",
142
+ "██║ ██║",
143
+ "╚═╝ ╚═╝"
144
+ ],
145
+ 'I': [
146
+ "██╗",
147
+ "██║",
148
+ "██║",
149
+ "██║",
150
+ "██║",
151
+ "╚═╝"
152
+ ],
153
+ 'J': [
154
+ " ██╗",
155
+ " ██║",
156
+ " ██║",
157
+ "██ ██║",
158
+ "╚█████╔╝",
159
+ " ╚════╝ "
160
+ ],
161
+ 'K': [
162
+ "██╗ ██╗",
163
+ "██║ ██╔╝",
164
+ "█████╔╝ ",
165
+ "██╔═██╗ ",
166
+ "██║ ██╗",
167
+ "╚═╝ ╚═╝"
168
+ ],
169
+ 'L': [
170
+ "██╗ ",
171
+ "██║ ",
172
+ "██║ ",
173
+ "██║ ",
174
+ "███████╗",
175
+ "╚══════╝"
176
+ ],
177
+ 'M': [
178
+ "███╗ ███╗",
179
+ "████╗ ████║",
180
+ "██╔████╔██║",
181
+ "██║╚██╔╝██║",
182
+ "██║ ╚═╝ ██║",
183
+ "╚═╝ ╚═╝"
184
+ ],
185
+ 'N': [
186
+ "███╗ ██╗",
187
+ "████╗ ██║",
188
+ "██╔██╗ ██║",
189
+ "██║╚██╗██║",
190
+ "██║ ╚████║",
191
+ "╚═╝ ╚═══╝"
192
+ ],
193
+ 'O': [
194
+ " ██████╗ ",
195
+ "██╔═══██╗",
196
+ "██║ ██║",
197
+ "██║ ██║",
198
+ "╚██████╔╝",
199
+ " ╚═════╝ "
200
+ ],
201
+ 'P': [
202
+ "██████╗ ",
203
+ "██╔══██╗",
204
+ "██████╔╝",
205
+ "██╔═══╝ ",
206
+ "██║ ",
207
+ "╚═╝ "
208
+ ],
209
+ 'Q': [
210
+ " ██████╗ ",
211
+ "██╔═══██╗",
212
+ "██║ ██║",
213
+ "██║▄▄ ██║",
214
+ "╚██████╔╝",
215
+ " ╚══▀▀═╝ "
216
+ ],
217
+ 'R': [
218
+ "██████╗ ",
219
+ "██╔══██╗",
220
+ "██████╔╝",
221
+ "██╔══██╗",
222
+ "██║ ██║",
223
+ "╚═╝ ╚═╝"
224
+ ],
225
+ 'S': [
226
+ "███████╗",
227
+ "██╔════╝",
228
+ "███████╗",
229
+ "╚════██║",
230
+ "███████║",
231
+ "╚══════╝"
232
+ ],
233
+ 'T': [
234
+ "████████╗",
235
+ "╚══██╔══╝",
236
+ " ██║ ",
237
+ " ██║ ",
238
+ " ██║ ",
239
+ " ╚═╝ "
240
+ ],
241
+ 'U': [
242
+ "██╗ ██╗",
243
+ "██║ ██║",
244
+ "██║ ██║",
245
+ "██║ ██║",
246
+ "╚██████╔╝",
247
+ " ╚═════╝ "
248
+ ],
249
+ 'V': [
250
+ "██╗ ██╗",
251
+ "██║ ██║",
252
+ "██║ ██║",
253
+ "╚██╗ ██╔╝",
254
+ " ╚████╔╝ ",
255
+ " ╚═══╝ "
256
+ ],
257
+ 'W': [
258
+ "██╗ ██╗",
259
+ "██║ ██║",
260
+ "██║ █╗ ██║",
261
+ "██║███╗██║",
262
+ "╚███╔███╔╝",
263
+ " ╚══╝╚══╝ "
264
+ ],
265
+ 'X': [
266
+ "██╗ ██╗",
267
+ "╚██╗██╔╝",
268
+ " ╚███╔╝ ",
269
+ " ██╔██╗ ",
270
+ "██╔╝ ██╗",
271
+ "╚═╝ ╚═╝"
272
+ ],
273
+ 'Y': [
274
+ "██╗ ██╗",
275
+ "╚██╗ ██╔╝",
276
+ " ╚████╔╝ ",
277
+ " ╚██╔╝ ",
278
+ " ██║ ",
279
+ " ╚═╝ "
280
+ ],
281
+ 'Z': [
282
+ "███████╗",
283
+ "╚══██╔══╝",
284
+ " ██╔╝ ",
285
+ " ██╔╝ ",
286
+ "███████╗",
287
+ "╚══════╝"
288
+ ],
289
+ '0': [
290
+ " ██████╗ ",
291
+ "██╔═████╗",
292
+ "██║██╔██║",
293
+ "████╔╝██║",
294
+ "╚██████╔╝",
295
+ " ╚═════╝ "
296
+ ],
297
+ '1': [
298
+ " ██╗",
299
+ "███║",
300
+ "╚██║",
301
+ " ██║",
302
+ " ██║",
303
+ " ╚═╝"
304
+ ],
305
+ '2': [
306
+ "██████╗ ",
307
+ "╚════██╗",
308
+ " █████╔╝",
309
+ "██╔═══╝ ",
310
+ "███████╗",
311
+ "╚══════╝"
312
+ ],
313
+ '3': [
314
+ "██████╗ ",
315
+ "╚════██╗",
316
+ " █████╔╝",
317
+ " ╚═══██╗",
318
+ "██████╔╝",
319
+ "╚═════╝ "
320
+ ],
321
+ '4': [
322
+ "██╗ ██╗",
323
+ "██║ ██║",
324
+ "███████║",
325
+ "╚════██║",
326
+ " ██║",
327
+ " ╚═╝"
328
+ ],
329
+ '5': [
330
+ "███████╗",
331
+ "██╔════╝",
332
+ "███████╗",
333
+ "╚════██║",
334
+ "███████║",
335
+ "╚══════╝"
336
+ ],
337
+ '6': [
338
+ " ██████╗ ",
339
+ "██╔════╝ ",
340
+ "███████╗ ",
341
+ "██╔═══██╗",
342
+ "╚██████╔╝",
343
+ " ╚═════╝ "
344
+ ],
345
+ '7': [
346
+ "███████╗",
347
+ "╚════██║",
348
+ " ██╔╝",
349
+ " ██╔╝ ",
350
+ " ██║ ",
351
+ " ╚═╝ "
352
+ ],
353
+ '8': [
354
+ " ██████╗ ",
355
+ "██╔═══██╗",
356
+ "╚█████╔╝ ",
357
+ "██╔═══██╗",
358
+ "╚██████╔╝",
359
+ " ╚═════╝ "
360
+ ],
361
+ '9': [
362
+ " ██████╗ ",
363
+ "██╔═══██╗",
364
+ "╚██████╔╝",
365
+ " ╚════██║",
366
+ " █████╔╝ ",
367
+ " ╚════╝ "
368
+ ],
369
+ ' ': [
370
+ " ",
371
+ " ",
372
+ " ",
373
+ " ",
374
+ " ",
375
+ " "
376
+ ],
377
+ '!': [
378
+ "██╗",
379
+ "██║",
380
+ "██║",
381
+ "╚═╝",
382
+ "██╗",
383
+ "╚═╝"
384
+ ],
385
+ '?': [
386
+ "██████╗ ",
387
+ "╚════██╗",
388
+ " ▄███╔╝",
389
+ " ▀▀══╝ ",
390
+ " ██╗ ",
391
+ " ╚═╝ "
392
+ ],
393
+ '.': [
394
+ " ",
395
+ " ",
396
+ " ",
397
+ " ",
398
+ "██╗",
399
+ "╚═╝"
400
+ ],
401
+ ',': [
402
+ " ",
403
+ " ",
404
+ " ",
405
+ " ",
406
+ "▄█╗",
407
+ "▀═╝"
408
+ ],
409
+ '-': [
410
+ " ",
411
+ " ",
412
+ "█████╗",
413
+ "╚════╝",
414
+ " ",
415
+ " "
416
+ ],
417
+ '_': [
418
+ " ",
419
+ " ",
420
+ " ",
421
+ " ",
422
+ "███████╗",
423
+ "╚══════╝"
424
+ ],
425
+ '/': [
426
+ " ██╗",
427
+ " ██╔╝",
428
+ " ██╔╝ ",
429
+ " ██╔╝ ",
430
+ "██╔╝ ",
431
+ "╚═╝ "
432
+ ],
433
+ '\\': [
434
+ "██╗ ",
435
+ "╚██╗ ",
436
+ " ╚██╗ ",
437
+ " ╚██╗ ",
438
+ " ╚██╗",
439
+ " ╚═╝"
440
+ ],
441
+ '(': [
442
+ " ██╗",
443
+ "██╔╝",
444
+ "██║ ",
445
+ "██║ ",
446
+ "╚██╗",
447
+ " ╚═╝"
448
+ ],
449
+ ')': [
450
+ "██╗ ",
451
+ "╚██╗",
452
+ " ██║",
453
+ " ██║",
454
+ "██╔╝",
455
+ "╚═╝ "
456
+ ],
457
+ ':': [
458
+ " ",
459
+ "██╗",
460
+ "╚═╝",
461
+ "██╗",
462
+ "╚═╝",
463
+ " "
464
+ ],
465
+ ';': [
466
+ " ",
467
+ "██╗",
468
+ "╚═╝",
469
+ "▄█╗",
470
+ "▀═╝",
471
+ " "
472
+ ],
473
+ '@': [
474
+ " ██████╗ ",
475
+ "██╔═══██╗",
476
+ "██║██╗██║",
477
+ "██║██║██║",
478
+ "╚█║████╔╝",
479
+ " ╚╝╚═══╝ "
480
+ ],
481
+ '#': [
482
+ " ██╗ ██╗ ",
483
+ "████████╗",
484
+ "╚██╔═██╔╝",
485
+ "████████╗",
486
+ "╚██╔═██╔╝",
487
+ " ╚═╝ ╚═╝ "
488
+ ],
489
+ '$': [
490
+ "▄▄███▄▄·",
491
+ "██╔════╝",
492
+ "███████╗",
493
+ "╚════██║",
494
+ "███████║",
495
+ "╚═▀▀▀══╝"
496
+ ],
497
+ '%': [
498
+ "██╗ ██╗",
499
+ "╚═╝██╔╝",
500
+ " ██╔╝ ",
501
+ " ██╔╝ ",
502
+ "██╔╝██╗",
503
+ "╚═╝ ╚═╝"
504
+ ],
505
+ '&': [
506
+ " ██╗ ",
507
+ "██╔╝ ",
508
+ "██║█████╗",
509
+ "╚██╔═══╝ ",
510
+ " ╚██████╗",
511
+ " ╚═════╝"
512
+ ],
513
+ '*': [
514
+ "▄ ██╗▄",
515
+ " ████╗",
516
+ "▀╚██╔▀",
517
+ " ╚═╝ ",
518
+ " ",
519
+ " "
520
+ ],
521
+ '+': [
522
+ " ",
523
+ " ██╗ ",
524
+ "██████╗",
525
+ "╚═██╔═╝",
526
+ " ╚═╝ ",
527
+ " "
528
+ ],
529
+ '=': [
530
+ " ",
531
+ "█████╗",
532
+ "╚════╝",
533
+ "█████╗",
534
+ "╚════╝",
535
+ " "
536
+ ],
537
+ }
538
+
539
+ # Convert text to uppercase and get characters
540
+ text = str(x).upper()
541
+ lines = ["", "", "", "", "", ""]
542
+
543
+ for char in text:
544
+ if char in block_font:
545
+ char_lines = block_font[char]
546
+ for i in range(6):
547
+ lines[i] += char_lines[i]
548
+ else:
549
+ # For unsupported characters, use a placeholder
550
+ for i in range(6):
551
+ lines[i] += " "
552
+
553
+ # Apply color
554
+ text_color = colors.get(color, colors['reset'])
555
+
556
+ # Handle surround for big text
557
+ if suround:
558
+ border_color = colors.get(surround_color, colors['reset'])
559
+
560
+ # Calculate max width of the big text
561
+ max_width = max(len(line) for line in lines)
562
+
563
+ # Handle surround_char
564
+ if surround_char:
565
+ # If multiple chars, alternate them
566
+ if len(surround_char) > 1:
567
+ chars = list(surround_char)
568
+ border_top = ""
569
+ border_bottom = ""
570
+ for i in range(max_width + 4):
571
+ border_top += chars[i % len(chars)]
572
+ border_bottom += chars[i % len(chars)]
573
+ left_char = chars[0]
574
+ right_char = chars[1 % len(chars)]
575
+ else:
576
+ # Single char
577
+ border_top = surround_char * (max_width + 4)
578
+ border_bottom = surround_char * (max_width + 4)
579
+ left_char = surround_char
580
+ right_char = surround_char
581
+ else:
582
+ # Default box drawing characters
583
+ border_top = "╔" + "═" * (max_width + 2) + "╗"
584
+ border_bottom = "╚" + "═" * (max_width + 2) + "╝"
585
+ left_char = "║"
586
+ right_char = "║"
587
+ # Print with border
588
+ _output(border_top, surround_color)
589
+ # Join all lines and output as a single block for proper alignment
590
+ ascii_block = '\n'.join(f"{left_char} {line.ljust(max_width)} {right_char}" for line in lines)
591
+ _output(ascii_block, color)
592
+ _output(border_bottom, surround_color)
593
+ else:
594
+ # Print without border - join all lines as single block
595
+ ascii_block = '\n'.join(lines)
596
+ _output(ascii_block, color)
597
+
598
+ for i in range(margin):
599
+ _output("", color)
600
+
601
+
602
+ def inp(prompt, color="red", margin=1):
603
+ """Get input from the user, using GUI terminal if available."""
604
+ global _input_result
605
+
606
+ for i in range(margin):
607
+ _output("", color)
608
+
609
+ if _terminal is not None:
610
+ # Use the GUI terminal's input system
611
+ _input_event.clear()
612
+ _input_result = None
613
+
614
+ def on_input(user_input):
615
+ global _input_result
616
+ _input_result = user_input
617
+ _input_event.set()
618
+
619
+ _terminal.set_input_callback(on_input, prompt, color)
620
+ _input_event.wait()
621
+
622
+ for i in range(margin):
623
+ _output("", color)
624
+
625
+ return _input_result
626
+ else:
627
+ # Fallback to standard input
628
+ text_color = colors.get(color, colors['reset'])
629
+ user_input = input(f"{text_color}{prompt}{colors['reset']}")
630
+ for i in range(margin):
631
+ print()
632
+ return user_input
agicli/project.py ADDED
@@ -0,0 +1,210 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ from agicli.printplus import pr, inp
2
+ import os
3
+ from agicli.welcome import get_detail
4
+
5
+ def current_year():
6
+ from datetime import datetime
7
+ return datetime.now().year
8
+
9
+ def create_project(path="agicli/projects/"):
10
+ """Creates a new project directory and initializes a README file."""
11
+ os.makedirs(path, exist_ok=True)
12
+
13
+ project_name = inp("Enter the project name: ", color="red", margin=1).strip()
14
+ while not project_name.isidentifier():
15
+ pr("Invalid project name. Please use only letters, numbers, and underscores, and do not start with a number.", color="yellow")
16
+ project_name = inp("Enter the project name: ", color="red", margin=1).strip()
17
+
18
+ project_path = os.path.join(path, project_name)
19
+ os.makedirs(project_path, exist_ok=True)
20
+
21
+ readme_path = os.path.join(project_path, "README.md")
22
+ with open(readme_path, 'w') as f:
23
+ f.write(f"# {project_name}\n\nWelcome to {project_name}! \n\n# AUTHOR \nThis project was created by {get_detail('user_name')}.\n\n# LICENSE \n\n See the [LICENSE](LICENSE) file for license information.\n")
24
+
25
+ license = inp("Choose a license 'mit', 'none', 'agicli': ", color="red", margin=1).strip().lower()
26
+ if license == "mit":
27
+ license_text = f"""
28
+ MIT License
29
+
30
+ Copyright (c) {current_year()} {get_detail("user_name")}
31
+
32
+ Permission is hereby granted, free of charge, to any person obtaining a copy
33
+ of this software and associated documentation files (the "Software"), to deal
34
+ in the Software without restriction, including without limitation the rights
35
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
36
+ copies of the Software, and to permit persons to whom the Software is
37
+ furnished to do so, subject to the following conditions:
38
+
39
+ The above copyright notice and this permission notice shall be included in all
40
+ copies or substantial portions of the Software.
41
+
42
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
43
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
44
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
45
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
46
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
47
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
48
+ SOFTWARE.
49
+ """
50
+ elif license == "agicli":
51
+ license_text = f"""
52
+ AG Corp's AGI CLI License
53
+ Copyright (c) {current_year()} {get_detail("user_name")}
54
+ All rights reserved. This project is licensed under AG Corp's AGI CLI License. Unauthorized copying of this file, via any medium is strictly prohibited.
55
+ """
56
+ else:
57
+ license_text = "No license specified. Ask the author for permission to use."
58
+
59
+ license_path = os.path.join(project_path, "LICENSE")
60
+ with open(license_path, 'w') as f:
61
+ f.write(license_text)
62
+
63
+ initialize_prefab = inp("Initialize with prefab structure? ('python', 'website', 'bf'/'b', 'b++' or 'none'): ", color="red", margin=1).strip().lower()
64
+
65
+ if initialize_prefab == "python":
66
+ # make main.py file in the project directory
67
+ main_path = os.path.join(project_path, "main.py")
68
+ with open(main_path, 'w') as f:
69
+ f.write(f"# {project_name}")
70
+
71
+ elif initialize_prefab == "website":
72
+ # create index.html, styles.css, script.js files in the project directory
73
+ index_path = os.path.join(project_path, "index.html")
74
+ with open(index_path, 'w') as f:
75
+ f.write(f"<!DOCTYPE html>\n<html>\n<head>\n <title>{project_name}</title>\n <link rel='stylesheet' href='styles.css'>\n</head>\n<body>\n <h1>Welcome to {project_name}!</h1>\n <script src='script.js'></script>\n</body>\n</html>")
76
+
77
+ styles_path = os.path.join(project_path, "styles.css")
78
+ with open(styles_path, 'w') as f:
79
+ f.write("body { font-family: Arial, sans-serif; }")
80
+
81
+ script_path = os.path.join(project_path, "script.js")
82
+ with open(script_path, 'w') as f:
83
+ f.write(f"console.log('Welcome to {project_name}!');")
84
+
85
+ elif initialize_prefab in ["bf", "b"]:
86
+ # create a hello world brainfuck file (.b)
87
+ bf_path = os.path.join(project_path, "main.b")
88
+ with open(bf_path, 'w') as f:
89
+ code = """
90
+
91
+ ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
92
+ ++++++++++++++++++++++++++++++++++++++++++++.---.+++++++..+++.
93
+ -------------------------------------------------------------
94
+ ------------------.
95
+ >++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
96
+ +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++.
97
+ >++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
98
+ +++++++++++++++++++++++++++++++++++++++++++++++++++.
99
+ >++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
100
+ ++++++++++++++++++++++++++++++++++++++++++++++++++++++.
101
+ >++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
102
+ ++++++++++++++++++++++++++++++++++++++++++++++++.
103
+ >++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
104
+ ++++++++++++++++++++++++++++++++++++++++.
105
+ >++++++++++.
106
+
107
+
108
+
109
+
110
+ """
111
+ f.write(code)
112
+
113
+ elif initialize_prefab == "b++":
114
+ pr("Initializing B++ project. B++ is a custom sdk for brainfuck development.", color="yellow")
115
+ # create a hello world B++ file (.bpp)
116
+ bpp_path = os.path.join(project_path, "main.bpp")
117
+ with open(bpp_path, 'w') as f:
118
+ code = """
119
+ // main.bpp
120
+
121
+ print("B++ Supports print statements!")
122
+ ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
123
+ ++++++++++++++++++++++++++++++++++++++++++++.---.+++++++..+++.
124
+ -------------------------------------------------------------
125
+ ------------------.
126
+ >++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
127
+ +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++.
128
+ >++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
129
+ +++++++++++++++++++++++++++++++++++++++++++++++++++.
130
+ >++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
131
+ ++++++++++++++++++++++++++++++++++++++++++++++++++++++.
132
+ >++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
133
+ ++++++++++++++++++++++++++++++++++++++++++++++++.
134
+ >++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
135
+ ++++++++++++++++++++++++++++++++++++++++.
136
+ >++++++++++.
137
+
138
+ """
139
+ f.write(code)
140
+
141
+ interpreter_path = os.path.join(project_path, "interpreter.py")
142
+ with open(interpreter_path, 'w') as f:
143
+ code = """
144
+ # Simple B++ Interpreter
145
+ import re
146
+
147
+ def bpp_interpreter(code):
148
+ # Handle print statements first
149
+ print_pattern = r'print\\s*\\(\\s*"([^"]*)"\\s*\\)'
150
+ for match in re.finditer(print_pattern, code):
151
+ print(match.group(1))
152
+
153
+ # Remove print statements and comments from code for brainfuck execution
154
+ code = re.sub(print_pattern, '', code)
155
+ code = re.sub(r'//.*', '', code) # Remove single-line comments
156
+
157
+ # Filter to only brainfuck commands
158
+ code = ''.join(filter(lambda x: x in ['+', '-', '<', '>', '.', ',', '[', ']'], code))
159
+ tape = [0] * 30000
160
+ pointer = 0
161
+ pc = 0
162
+ loop_stack = []
163
+ while pc < len(code):
164
+ cmd = code[pc]
165
+ if cmd == '+':
166
+ tape[pointer] = (tape[pointer] + 1) % 256
167
+ elif cmd == '-':
168
+ tape[pointer] = (tape[pointer] - 1) % 256
169
+ elif cmd == '>':
170
+ pointer += 1
171
+ elif cmd == '<':
172
+ pointer -= 1
173
+ elif cmd == '.':
174
+ print(chr(tape[pointer]), end='')
175
+ elif cmd == ',':
176
+ tape[pointer] = ord(input()[0]) % 256
177
+ elif cmd == '[':
178
+ if tape[pointer] == 0:
179
+ open_brackets = 1
180
+ while open_brackets > 0:
181
+ pc += 1
182
+ if code[pc] == '[':
183
+ open_brackets += 1
184
+ elif code[pc] == ']':
185
+ open_brackets -= 1
186
+ else:
187
+ loop_stack.append(pc)
188
+ elif cmd == ']':
189
+ if tape[pointer] != 0:
190
+ pc = loop_stack[-1]
191
+ else:
192
+ loop_stack.pop()
193
+ pc += 1
194
+
195
+ if __name__ == "__main__":
196
+ with open("main.bpp", "r") as f:
197
+ bpp_interpreter(f.read())
198
+ """
199
+ f.write(code)
200
+
201
+ elif initialize_prefab == "none":
202
+ pr("No prefab structure initialized.", color="yellow")
203
+ else:
204
+ pr("Unknown prefab option. No prefab structure initialized.", color="yellow")
205
+
206
+
207
+
208
+
209
+
210
+ pr(f"Project '{project_name}' created successfully at {project_path}", color="red")
agicli/tools.py ADDED
@@ -0,0 +1,56 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import inspect
2
+ import functools
3
+
4
+ # Global registry for AGI tools
5
+ TOOL_REGISTRY = {}
6
+
7
+ def agi_tool(func):
8
+ """
9
+ Decorator to register a function as an AGI tool.
10
+ The function's docstring will be used as the tool description.
11
+ """
12
+ @functools.wraps(func)
13
+ def wrapper(*args, **kwargs):
14
+ return func(*args, **kwargs)
15
+
16
+ # Get function signature
17
+ sig = inspect.signature(func)
18
+ params = list(sig.parameters.keys())
19
+
20
+ # Store tool info
21
+ TOOL_REGISTRY[func.__name__] = {
22
+ "func": func,
23
+ "description": func.__doc__.strip() if func.__doc__ else "No description provided.",
24
+ "parameters": params
25
+ }
26
+
27
+ return wrapper
28
+
29
+ def get_tools_prompt():
30
+ """
31
+ Generates the XML-style prompt for all registered tools.
32
+ """
33
+ prompt_lines = []
34
+ for name, info in TOOL_REGISTRY.items():
35
+ attr_part = " ".join([f'{p}=""' for p in info['parameters']])
36
+ line = f'<{name} {attr_part} /> - {info["description"]}'
37
+ prompt_lines.append(line)
38
+ return "\n".join(prompt_lines)
39
+
40
+ def handle_custom_tool(name, attrs):
41
+ """
42
+ Calls a registered tool with the provided attributes.
43
+ """
44
+ if name in TOOL_REGISTRY:
45
+ info = TOOL_REGISTRY[name]
46
+ func = info["func"]
47
+
48
+ # Filter attributes to match function parameters and handle type conversion (optional)
49
+ # For now, pass all attrs as strings, letting the function handle them
50
+ try:
51
+ # We only pass attributes that the function expects
52
+ valid_args = {k: v for k, v in attrs.items() if k in info['parameters']}
53
+ return func(**valid_args)
54
+ except Exception as e:
55
+ return f"Error executing tool {name}: {str(e)}"
56
+ return None
agicli/trees.jpg ADDED

Git LFS Details

  • SHA256: 4898c69cdf4862fff875f64c13418c59980cdf4343031996648e04c0a788253e
  • Pointer size: 131 Bytes
  • Size of remote file: 155 kB
agicli/ui.html ADDED
@@ -0,0 +1,980 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <!DOCTYPE html>
2
+ <html lang="en">
3
+
4
+ <head>
5
+ <meta charset="UTF-8">
6
+ <meta name="viewport" content="width=device-width, initial-scale=1.0">
7
+ <title>Sleek UI Design Guidelines</title>
8
+ <style>
9
+ :root {
10
+ /* Color Palette - Deep Neutrals */
11
+ --bg-primary: #000000;
12
+ --bg-secondary: #121212;
13
+ --bg-tertiary: #1c1c1e;
14
+
15
+ --border-subtle: #2c2c2e;
16
+ --border-active: #3a3a3c;
17
+
18
+ --text-primary: #ffffff;
19
+ --text-secondary: #8e8e93;
20
+ --text-tertiary: #48484a;
21
+
22
+ --accent: #0a84ff;
23
+ --accent-soft: rgba(10, 132, 255, 0.1);
24
+
25
+ --radius-sm: 6px;
26
+ --radius-md: 10px;
27
+ --radius-lg: 16px;
28
+ --radius-pill: 55px;
29
+
30
+ --font-main: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, Helvetica, Arial, sans-serif;
31
+ --transition: all 0.2s cubic-bezier(0.4, 0, 0.2, 1);
32
+ }
33
+
34
+ * {
35
+ margin: 0;
36
+ padding: 0;
37
+ box-sizing: border-box;
38
+ -webkit-font-smoothing: antialiased;
39
+ }
40
+
41
+ body {
42
+ background-color: var(--bg-primary);
43
+ color: var(--text-primary);
44
+ font-family: var(--font-main);
45
+ line-height: 1.6;
46
+ overflow-x: hidden;
47
+ }
48
+
49
+ /* Scrollbar Styling */
50
+ ::-webkit-scrollbar {
51
+ width: 8px;
52
+ }
53
+
54
+ ::-webkit-scrollbar-track {
55
+ background: var(--bg-primary);
56
+ }
57
+
58
+ ::-webkit-scrollbar-thumb {
59
+ background: var(--border-subtle);
60
+ border-radius: 10px;
61
+ }
62
+
63
+ ::-webkit-scrollbar-thumb:hover {
64
+ background: var(--border-active);
65
+ }
66
+
67
+ /* Layout */
68
+ .app-container {
69
+ display: grid;
70
+ grid-template-columns: 260px 1fr;
71
+ min-height: 100vh;
72
+ }
73
+
74
+ /* Sidebar Navigation */
75
+ aside {
76
+ border-right: 1px solid var(--border-subtle);
77
+ padding: 2rem 1.5rem;
78
+ position: sticky;
79
+ top: 0;
80
+ height: 100vh;
81
+ background: var(--bg-primary);
82
+ }
83
+
84
+ .brand {
85
+ font-size: 1.25rem;
86
+ font-weight: 600;
87
+ margin-bottom: 2.5rem;
88
+ display: flex;
89
+ align-items: center;
90
+ letter-spacing: -0.02em;
91
+ }
92
+
93
+ .nav-list {
94
+ list-style: none;
95
+ }
96
+
97
+ .nav-item {
98
+ margin-bottom: 0.5rem;
99
+ }
100
+
101
+ .nav-link {
102
+ text-decoration: none;
103
+ color: var(--text-secondary);
104
+ font-size: 0.9rem;
105
+ display: block;
106
+ padding: 0.6rem 0.8rem;
107
+ border-radius: var(--radius-pill);
108
+ transition: var(--transition);
109
+ }
110
+
111
+ .nav-link:hover {
112
+ color: var(--text-primary);
113
+ background: var(--bg-secondary);
114
+ }
115
+
116
+ .nav-link.active {
117
+ color: var(--text-primary);
118
+ background: var(--bg-tertiary);
119
+ font-weight: 500;
120
+ }
121
+
122
+ /* Main Content */
123
+ main {
124
+ padding: 4rem 6rem;
125
+ max-width: 1200px;
126
+ }
127
+
128
+ header {
129
+ margin-bottom: 4rem;
130
+ }
131
+
132
+ h1 {
133
+ font-size: 3rem;
134
+ font-weight: 700;
135
+ letter-spacing: -0.04em;
136
+ margin-bottom: 1rem;
137
+ }
138
+
139
+ .subtitle {
140
+ font-size: 1.25rem;
141
+ color: var(--text-secondary);
142
+ max-width: 600px;
143
+ }
144
+
145
+ section {
146
+ margin-bottom: 5rem;
147
+ }
148
+
149
+ h2 {
150
+ font-size: 0.85rem;
151
+ text-transform: uppercase;
152
+ letter-spacing: 0.15em;
153
+ color: var(--text-secondary);
154
+ margin-bottom: 2rem;
155
+ padding-bottom: 0.5rem;
156
+ }
157
+
158
+ /* Typography Grid */
159
+ .type-grid {
160
+ display: flex;
161
+ flex-direction: column;
162
+ gap: 2rem;
163
+ }
164
+
165
+ .type-row {
166
+ display: grid;
167
+ grid-template-columns: 150px 1fr;
168
+ align-items: baseline;
169
+ }
170
+
171
+ .label-small {
172
+ font-size: 0.75rem;
173
+ color: var(--text-tertiary);
174
+ font-weight: 600;
175
+ text-transform: uppercase;
176
+ letter-spacing: 0.05em;
177
+ }
178
+
179
+ /* Color Cards */
180
+ .color-grid {
181
+ display: grid;
182
+ grid-template-columns: repeat(auto-fill, minmax(180px, 1fr));
183
+ gap: 1.5rem;
184
+ }
185
+
186
+ .color-card {
187
+ border-radius: var(--radius-md);
188
+ overflow: hidden;
189
+ transition: var(--transition);
190
+ }
191
+
192
+ .color-card:hover {
193
+ border-color: var(--border-active);
194
+ }
195
+
196
+ .swatch {
197
+ height: 100px;
198
+ }
199
+
200
+ .color-info {
201
+ padding: 1rem;
202
+ background: var(--bg-secondary);
203
+ }
204
+
205
+ .color-name {
206
+ font-size: 0.85rem;
207
+ font-weight: 500;
208
+ margin-bottom: 0.25rem;
209
+ }
210
+
211
+ .color-hex {
212
+ font-size: 0.75rem;
213
+ color: var(--text-secondary);
214
+ font-family: monospace;
215
+ }
216
+
217
+ /* UI Components */
218
+ .component-grid {
219
+ display: grid;
220
+ grid-template-columns: repeat(auto-fill, minmax(400px, 1fr));
221
+ gap: 2rem;
222
+ }
223
+
224
+ .demo-card {
225
+ background: var(--bg-secondary);
226
+ border-radius: var(--radius-lg);
227
+ padding: 2rem;
228
+ display: flex;
229
+ flex-direction: column;
230
+ gap: 1.5rem;
231
+ }
232
+
233
+ /* Buttons */
234
+ .btn-group {
235
+ display: flex;
236
+ gap: 1rem;
237
+ flex-wrap: wrap;
238
+ align-items: center;
239
+ }
240
+
241
+ button {
242
+ padding: 0.7rem 1.5rem;
243
+ border-radius: var(--radius-pill);
244
+ font-size: 0.9rem;
245
+ font-weight: 500;
246
+ cursor: pointer;
247
+ transition: var(--transition);
248
+ border: none;
249
+ }
250
+
251
+ .btn-primary {
252
+ background: var(--text-primary);
253
+ color: var(--bg-primary);
254
+ }
255
+
256
+ .btn-primary:hover {
257
+ background: #e5e5e5;
258
+ }
259
+
260
+ .btn-secondary {
261
+ background: var(--bg-tertiary);
262
+ color: var(--text-primary);
263
+ }
264
+
265
+ .btn-secondary:hover {
266
+ background: var(--border-active);
267
+ }
268
+
269
+ .btn-outline {
270
+ background: transparent;
271
+ background: transparent;
272
+ color: var(--text-primary);
273
+ /* Border removed */
274
+ }
275
+
276
+ .btn-outline:hover {
277
+ background: var(--bg-secondary);
278
+ }
279
+
280
+ /* Tags/Badges */
281
+ .tag {
282
+ display: inline-flex;
283
+ align-items: center;
284
+ padding: 0.3rem 0.9rem;
285
+ background: var(--bg-tertiary);
286
+ border-radius: var(--radius-pill);
287
+ font-size: 0.75rem;
288
+ color: var(--text-secondary);
289
+ font-weight: 500;
290
+ }
291
+
292
+ /* Inputs */
293
+ .input-field {
294
+ background: var(--bg-primary);
295
+ border: 1px solid var(--border-subtle);
296
+ color: var(--text-primary);
297
+ padding: 0.75rem 1rem;
298
+ border-radius: var(--radius-sm);
299
+ width: 100%;
300
+ outline: none;
301
+ transition: var(--transition);
302
+ font-size: 0.9rem;
303
+ }
304
+
305
+ .input-field:focus {
306
+ border-color: var(--accent);
307
+ }
308
+
309
+ /* Toggle Switch */
310
+ .toggle {
311
+ position: relative;
312
+ display: inline-block;
313
+ width: 44px;
314
+ height: 24px;
315
+ }
316
+
317
+ .toggle input {
318
+ opacity: 0;
319
+ width: 0;
320
+ height: 0;
321
+ }
322
+
323
+ .slider {
324
+ position: absolute;
325
+ cursor: pointer;
326
+ top: 0;
327
+ left: 0;
328
+ right: 0;
329
+ bottom: 0;
330
+ background-color: var(--bg-tertiary);
331
+ transition: .3s;
332
+ border-radius: 34px;
333
+ border: 1px solid var(--border-subtle);
334
+ }
335
+
336
+ .slider:before {
337
+ position: absolute;
338
+ content: "";
339
+ height: 18px;
340
+ width: 18px;
341
+ left: 2px;
342
+ bottom: 2px;
343
+ background-color: white;
344
+ transition: .3s;
345
+ border-radius: 50%;
346
+ }
347
+
348
+ input:checked+.slider {
349
+ background-color: var(--accent);
350
+ border-color: var(--accent);
351
+ }
352
+
353
+ input:checked+.slider:before {
354
+ transform: translateX(20px);
355
+ }
356
+
357
+ /* Checkbox */
358
+ .checkbox-container {
359
+ display: flex;
360
+ align-items: center;
361
+ cursor: pointer;
362
+ gap: 0.75rem;
363
+ font-size: 0.9rem;
364
+ color: var(--text-secondary);
365
+ }
366
+
367
+ .checkbox-container input {
368
+ display: none;
369
+ }
370
+
371
+ .checkmark {
372
+ height: 20px;
373
+ width: 20px;
374
+ background-color: var(--bg-primary);
375
+ border: 1px solid var(--border-subtle);
376
+ border-radius: 4px;
377
+ display: flex;
378
+ align-items: center;
379
+ justify-content: center;
380
+ }
381
+
382
+ .checkbox-container input:checked+.checkmark {
383
+ background-color: var(--accent);
384
+ border-color: var(--accent);
385
+ }
386
+
387
+ .checkmark svg {
388
+ width: 12px;
389
+ height: 12px;
390
+ fill: none;
391
+ stroke: white;
392
+ stroke-width: 3;
393
+ display: none;
394
+ }
395
+
396
+ .checkbox-container input:checked+.checkmark svg {
397
+ display: block;
398
+ }
399
+
400
+ /* Range Slider */
401
+ input[type=range] {
402
+ -webkit-appearance: none;
403
+ appearance: none;
404
+ width: 100%;
405
+ background: transparent;
406
+ }
407
+
408
+ input[type=range]::-webkit-slider-runnable-track {
409
+ width: 100%;
410
+ height: 4px;
411
+ background: var(--bg-tertiary);
412
+ border-radius: 2px;
413
+ }
414
+
415
+ input[type=range]::-webkit-slider-thumb {
416
+ -webkit-appearance: none;
417
+ height: 18px;
418
+ width: 18px;
419
+ border-radius: 50%;
420
+ background: var(--text-primary);
421
+ cursor: pointer;
422
+ margin-top: -7px;
423
+ }
424
+
425
+ /* Search Bar */
426
+ .search-wrapper {
427
+ position: relative;
428
+ width: 100%;
429
+ max-width: 300px;
430
+ }
431
+
432
+ .search-icon {
433
+ position: absolute;
434
+ left: 12px;
435
+ top: 50%;
436
+ transform: translateY(-50%);
437
+ color: var(--text-secondary);
438
+ width: 16px;
439
+ height: 16px;
440
+ }
441
+
442
+ .search-input {
443
+ width: 100%;
444
+ background: var(--bg-tertiary);
445
+ border: none;
446
+ padding: 0.6rem 1rem 0.6rem 2.5rem;
447
+ border-radius: var(--radius-pill);
448
+ color: var(--text-primary);
449
+ font-size: 0.9rem;
450
+ transition: var(--transition);
451
+ }
452
+
453
+ .search-input:focus {
454
+ background: var(--bg-primary);
455
+ outline: none;
456
+ }
457
+
458
+ /* Icon Buttons */
459
+ .icon-btn-group {
460
+ display: flex;
461
+ gap: 0.5rem;
462
+ }
463
+
464
+ .icon-btn {
465
+ background: transparent;
466
+ color: var(--text-secondary);
467
+ width: 36px;
468
+ height: 36px;
469
+ display: flex;
470
+ align-items: center;
471
+ justify-content: center;
472
+ border-radius: 50%;
473
+ transition: var(--transition);
474
+ padding: 0;
475
+ }
476
+
477
+ .icon-btn:hover {
478
+ background: var(--bg-tertiary);
479
+ color: var(--text-primary);
480
+ }
481
+
482
+ .icon-btn.active {
483
+ color: var(--accent);
484
+ background: var(--accent-soft);
485
+ }
486
+
487
+ .icon-btn svg {
488
+ width: 18px;
489
+ height: 18px;
490
+ fill: none;
491
+ stroke: currentColor;
492
+ stroke-width: 2;
493
+ stroke-linecap: round;
494
+ stroke-linejoin: round;
495
+ }
496
+
497
+ /* Profile Pictures */
498
+ .pfp-list {
499
+ display: flex;
500
+ gap: -10px;
501
+ }
502
+
503
+ .pfp {
504
+ width: 48px;
505
+ height: 48px;
506
+ border-radius: 50%;
507
+ border: 2px solid var(--bg-secondary);
508
+ margin-right: -12px;
509
+ object-fit: cover;
510
+ transition: transform 0.2s;
511
+ }
512
+
513
+ .pfp:hover {
514
+ transform: translateY(-4px);
515
+ z-index: 10;
516
+ }
517
+
518
+
519
+
520
+ /* Code Block */
521
+ .code-container {
522
+ position: relative;
523
+ background: #0a0a0a;
524
+ border-radius: var(--radius-md);
525
+ overflow: hidden;
526
+ margin-top: 1rem;
527
+ }
528
+
529
+ .code-header {
530
+ display: flex;
531
+ justify-content: space-between;
532
+ align-items: center;
533
+ padding: 0.5rem 1rem;
534
+ background: var(--bg-tertiary);
535
+ }
536
+
537
+ .code-lang {
538
+ font-size: 0.7rem;
539
+ color: var(--text-tertiary);
540
+ font-weight: 700;
541
+ text-transform: uppercase;
542
+ }
543
+
544
+ .copy-btn {
545
+ background: transparent;
546
+ border: none;
547
+ color: var(--text-secondary);
548
+ cursor: pointer;
549
+ padding: 4px;
550
+ display: flex;
551
+ align-items: center;
552
+ justify-content: center;
553
+ transition: var(--transition);
554
+ border-radius: 4px;
555
+ }
556
+
557
+ .copy-btn:hover {
558
+ background: var(--bg-secondary);
559
+ color: var(--text-primary);
560
+ }
561
+
562
+ pre {
563
+ padding: 1.5rem;
564
+ font-family: 'SFMono-Regular', Consolas, 'Liberation Mono', Menlo, monospace;
565
+ font-size: 0.85rem;
566
+ line-height: 1.5;
567
+ overflow-x: auto;
568
+ color: #d1d1d1;
569
+ }
570
+
571
+ .token-keyword {
572
+ color: #ff79c6;
573
+ }
574
+
575
+ .token-string {
576
+ color: #f1fa8c;
577
+ }
578
+
579
+ .token-function {
580
+ color: #50fa7b;
581
+ }
582
+
583
+ .token-comment {
584
+ color: #6272a4;
585
+ }
586
+
587
+ /* Media & Images */
588
+ .media-grid {
589
+ display: grid;
590
+ grid-template-columns: repeat(auto-fill, minmax(300px, 1fr));
591
+ gap: 2rem;
592
+ }
593
+
594
+ .image-card {
595
+ border-radius: var(--radius-lg);
596
+ overflow: hidden;
597
+ background: var(--bg-secondary);
598
+ transition: var(--transition);
599
+ }
600
+
601
+ .image-card img {
602
+ width: 100%;
603
+ height: 200px;
604
+ object-fit: cover;
605
+ display: block;
606
+ }
607
+
608
+ .image-content {
609
+ padding: 1.5rem;
610
+ }
611
+
612
+ .image-title {
613
+ font-size: 1rem;
614
+ font-weight: 600;
615
+ margin-bottom: 0.5rem;
616
+ color: var(--text-primary);
617
+ }
618
+
619
+ .image-desc {
620
+ font-size: 0.85rem;
621
+ color: var(--text-secondary);
622
+ }
623
+
624
+ /* Responsive */
625
+ @media (max-width: 1024px) {
626
+ .app-container {
627
+ grid-template-columns: 1fr;
628
+ }
629
+
630
+ aside {
631
+ display: none;
632
+ }
633
+
634
+ main {
635
+ padding: 3rem 2rem;
636
+ }
637
+
638
+ .component-grid {
639
+ grid-template-columns: 1fr;
640
+ }
641
+ }
642
+
643
+ /* Toast */
644
+ .toast {
645
+ position: fixed;
646
+ bottom: 2rem;
647
+ right: 2rem;
648
+ background: var(--accent);
649
+ color: white;
650
+ padding: 0.75rem 1.5rem;
651
+ border-radius: var(--radius-sm);
652
+ font-size: 0.85rem;
653
+ font-weight: 500;
654
+ transform: translateY(100px);
655
+ transition: transform 0.3s cubic-bezier(0.175, 0.885, 0.32, 1.275);
656
+ z-index: 1000;
657
+ }
658
+
659
+ .toast.show {
660
+ transform: translateY(0);
661
+ }
662
+ </style>
663
+ </head>
664
+
665
+ <body>
666
+
667
+ <div class="app-container">
668
+ <aside>
669
+ <div class="brand">System·UI</div>
670
+ <nav>
671
+ <ul class="nav-list">
672
+ <li class="nav-item"><a href="#typography" class="nav-link active">Typography</a></li>
673
+ <li class="nav-item"><a href="#colors" class="nav-link">Colors</a></li>
674
+ <li class="nav-item"><a href="#components" class="nav-link">Components</a></li>
675
+ <li class="nav-item"><a href="#images" class="nav-link">Images & Content</a></li>
676
+ <li class="nav-item"><a href="#controls" class="nav-link">Controls</a></li>
677
+ <li class="nav-item"><a href="#dev" class="nav-link">Development</a></li>
678
+ </ul>
679
+ </nav>
680
+ </aside>
681
+
682
+ <main>
683
+ <header>
684
+ <h1>Design Guidelines</h1>
685
+ <p class="subtitle">A minimalist design system focusing on clarity, flat pill-shaped interfaces, and
686
+ high-fidelity interactions.</p>
687
+ </header>
688
+
689
+ <section id="typography">
690
+ <h2>Typography</h2>
691
+ <div class="type-grid">
692
+ <div class="type-row">
693
+ <span class="label-small">DISPLAY</span>
694
+ <h1 style="margin:0; font-size: 2.5rem;">The quick brown fox.</h1>
695
+ </div>
696
+ <div class="type-row">
697
+ <span class="label-small">BODY</span>
698
+ <p style="color: var(--text-secondary);">Grumpy wizards make toxic brew for the evil Queen and
699
+ Jack. Everything is perfectly legible and spaced.</p>
700
+ </div>
701
+ </div>
702
+ </section>
703
+
704
+ <section id="colors">
705
+ <h2>Color Palette</h2>
706
+ <div class="color-grid">
707
+ <div class="color-card copyable" data-hex="#000000">
708
+ <div class="swatch" style="background: #000000;"></div>
709
+ <div class="color-info">
710
+ <div class="color-name">Black</div>
711
+ <div class="color-hex">#000000</div>
712
+ </div>
713
+ </div>
714
+ <div class="color-card copyable" data-hex="#0a84ff">
715
+ <div class="swatch" style="background: #0a84ff;"></div>
716
+ <div class="color-info">
717
+ <div class="color-name">Accent</div>
718
+ <div class="color-hex">#0A84FF</div>
719
+ </div>
720
+ </div>
721
+ </div>
722
+ </section>
723
+
724
+ <section id="components">
725
+ <h2>Standard Components</h2>
726
+ <div class="component-grid">
727
+ <div class="demo-card">
728
+ <span class="label-small">BUTTONS</span>
729
+ <div class="btn-group">
730
+ <button class="btn-primary">Primary Action</button>
731
+ <button class="btn-secondary">Secondary</button>
732
+ </div>
733
+ <span class="label-small">BADGES</span>
734
+ <div class="btn-group">
735
+ <span class="tag">Status</span>
736
+ <span class="tag"
737
+ style="color: var(--accent); background: var(--accent-soft);">Feature</span>
738
+ <span class="tag">v.1.0</span>
739
+ </div>
740
+ </div>
741
+ <div class="demo-card">
742
+ <span class="label-small">AVATARS</span>
743
+ <div class="pfp-list">
744
+ <img src="pfps/anime_pfp1.png" alt="User 1" class="pfp">
745
+ <img src="pfps/anime_pfp2.png" alt="User 2" class="pfp">
746
+ <img src="pfps/anime_pfp3.png" alt="User 3" class="pfp">
747
+ <img src="pfps/anime_pfp4.png" alt="User 4" class="pfp">
748
+ <img src="pfps/anime_pfp5.png" alt="User 5" class="pfp">
749
+ <img src="pfps/anime_pfp6.png" alt="User 6" class="pfp">
750
+ </div>
751
+ </div>
752
+ </div>
753
+ <!-- New Components -->
754
+ <h2 style="margin-top: 4rem;">Interactive</h2>
755
+ <div class="component-grid">
756
+ <div class="demo-card">
757
+ <span class="label-small">SEARCH</span>
758
+ <div class="search-wrapper">
759
+ <svg class="search-icon" viewBox="0 0 24 24" fill="none" stroke="currentColor"
760
+ stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
761
+ <circle cx="11" cy="11" r="8"></circle>
762
+ <line x1="21" y1="21" x2="16.65" y2="16.65"></line>
763
+ </svg>
764
+ <input type="text" class="search-input" placeholder="Search...">
765
+ </div>
766
+ </div>
767
+ <div class="demo-card">
768
+ <span class="label-small">ACTIONS</span>
769
+ <div class="icon-btn-group">
770
+ <button class="icon-btn" aria-label="Like">
771
+ <svg viewBox="0 0 24 24">
772
+ <path
773
+ d="M20.84 4.61a5.5 5.5 0 0 0-7.78 0L12 5.67l-1.06-1.06a5.5 5.5 0 0 0-7.78 7.78l1.06 1.06L12 21.23l7.78-7.78 1.06-1.06a5.5 5.5 0 0 0 0-7.78z">
774
+ </path>
775
+ </svg>
776
+ </button>
777
+ <button class="icon-btn active" aria-label="Bookmark">
778
+ <svg viewBox="0 0 24 24">
779
+ <path d="M19 21l-7-5-7 5V5a2 2 0 0 1 2-2h10a2 2 0 0 1 2 2z"></path>
780
+ </svg>
781
+ </button>
782
+ <button class="icon-btn" aria-label="Share">
783
+ <svg viewBox="0 0 24 24">
784
+ <circle cx="18" cy="5" r="3"></circle>
785
+ <circle cx="6" cy="12" r="3"></circle>
786
+ <circle cx="18" cy="19" r="3"></circle>
787
+ <line x1="8.59" y1="13.51" x2="15.42" y2="17.49"></line>
788
+ <line x1="15.41" y1="6.51" x2="8.59" y2="10.49"></line>
789
+ </svg>
790
+ </button>
791
+ <button class="icon-btn" aria-label="More">
792
+ <svg viewBox="0 0 24 24">
793
+ <circle cx="12" cy="12" r="1"></circle>
794
+ <circle cx="12" cy="5" r="1"></circle>
795
+ <circle cx="12" cy="19" r="1"></circle>
796
+ </svg>
797
+ </button>
798
+ </div>
799
+
800
+ </section>
801
+
802
+ <section id="images">
803
+ <h2>Images & Content</h2>
804
+ <div class="media-grid">
805
+ <div class="image-card">
806
+ <img src="trees.jpg" alt="Nature">
807
+ <div class="image-content">
808
+ <div class="image-title">Full Width Media</div>
809
+ <div class="image-desc">Images should span the full width of their container with consistent
810
+ rounded corners.</div>
811
+ </div>
812
+ </div>
813
+ <div class="demo-card">
814
+ <span class="label-small">USER CONTENT</span>
815
+ <div style="display: flex; gap: 1rem; align-items: flex-start; margin-top: 0.5rem;">
816
+ <img src="pfps/anime_pfp1.png"
817
+ style="width: 40px; height: 40px; border-radius: 50%; object-fit: cover;" alt="User">
818
+ <div>
819
+ <div style="font-weight: 600; font-size: 0.9rem; margin-bottom: 0.2rem;">System User
820
+ </div>
821
+ <div style="font-size: 0.85rem; color: var(--text-secondary); line-height: 1.4;">
822
+ Content structure should follow a clear hierarchy. Images combined with text create
823
+ engaging layouts.
824
+ </div>
825
+ <img src="trees.jpg"
826
+ style="width: 100%; border-radius: var(--radius-md); margin-top: 1rem; height: 150px; object-fit: cover;"
827
+ alt="Content Image">
828
+ </div>
829
+ </div>
830
+ </div>
831
+ </div>
832
+ </section>
833
+
834
+ <section id="controls">
835
+ <h2>Input & Controls</h2>
836
+ <div class="component-grid">
837
+ <div class="demo-card">
838
+ <span class="label-small">FORM ELEMENTS</span>
839
+ <div style="display: flex; flex-direction: column; gap: 1.25rem;">
840
+ <input type="text" class="input-field" placeholder="Username">
841
+ <select class="input-field" style="appearance: none;">
842
+ <option>Select Location</option>
843
+ <option>North America</option>
844
+ <option>Europe</option>
845
+ </select>
846
+ <label class="checkbox-container">
847
+ <input type="checkbox" checked>
848
+ <div class="checkmark">
849
+ <svg viewBox="0 0 24 24">
850
+ <path d="M20 6L9 17l-5-5" />
851
+ </svg>
852
+ </div>
853
+ Subscribe to newsletter
854
+ </label>
855
+ </div>
856
+ </div>
857
+ <div class="demo-card">
858
+ <span class="label-small">INTERACTIVE CONTROLS</span>
859
+ <div style="display: flex; flex-direction: column; gap: 1.5rem;">
860
+ <div style="display: flex; align-items: center; gap: 1rem;">
861
+ <label class="toggle">
862
+ <input type="checkbox" checked>
863
+ <span class="slider"></span>
864
+ </label>
865
+ <span style="font-size: 0.9rem; color: var(--text-secondary);">Dark Mode</span>
866
+ </div>
867
+ <div style="display: flex; flex-direction: column; gap: 0.5rem;">
868
+ <div style="display: flex; justify-content: space-between;">
869
+ <span class="label-small">BRIGHTNESS</span>
870
+ <span class="label-small">80%</span>
871
+ </div>
872
+ <input type="range" min="0" max="100" value="80">
873
+ </div>
874
+ </div>
875
+ </div>
876
+ </div>
877
+ </section>
878
+
879
+ <section id="dev">
880
+ <h2>Code Guidelines</h2>
881
+ <div class="code-container">
882
+ <div class="code-header">
883
+ <span class="code-lang">JavaScript</span>
884
+ <button class="copy-btn" id="copyCode">
885
+ <svg class="icon-copy" width="16" height="16" viewBox="0 0 24 24" fill="none"
886
+ stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
887
+ <rect x="9" y="9" width="13" height="13" rx="2" ry="2"></rect>
888
+ <path d="M5 15H4a2 2 0 0 1-2-2V4a2 2 0 0 1 2-2h9a2 2 0 0 1 2 2v1"></path>
889
+ </svg>
890
+ <svg class="icon-check" width="16" height="16" viewBox="0 0 24 24" fill="none"
891
+ stroke="currentColor" stroke-width="3" stroke-linecap="round" stroke-linejoin="round"
892
+ style="display:none; color: var(--accent);">
893
+ <polyline points="20 6 9 17 4 12"></polyline>
894
+ </svg>
895
+ </button>
896
+ </div>
897
+ <pre id="codeContent"><span class="token-keyword">const</span> theme = {
898
+ name: <span class="token-string">'Minimal'</span>,
899
+ borderRadius: <span class="token-string">'55px'</span>,
900
+ shadows: <span class="token-keyword">false</span>,
901
+ <span class="token-function">init</span>() {
902
+ console.<span class="token-function">log</span>(<span class="token-string">'Design system loaded'</span>);
903
+ }
904
+ };</pre>
905
+ </div>
906
+ </section>
907
+ </main>
908
+ </div>
909
+
910
+ <div id="toast" class="toast">Copied to clipboard</div>
911
+
912
+ <script>
913
+ // Copy to clipboard for Color Cards
914
+ const colorCards = document.querySelectorAll('.color-card.copyable');
915
+ const toast = document.getElementById('toast');
916
+
917
+ colorCards.forEach(card => {
918
+ card.addEventListener('click', () => {
919
+ const hex = card.getAttribute('data-hex');
920
+ copyToClipboard(hex);
921
+ showToast(`Copied ${hex}`);
922
+ });
923
+ });
924
+
925
+ // Code Copy functionality
926
+ const copyBtn = document.getElementById('copyCode');
927
+ const codeContent = document.getElementById('codeContent');
928
+ const iconCopy = copyBtn.querySelector('.icon-copy');
929
+ const iconCheck = copyBtn.querySelector('.icon-check');
930
+
931
+ copyBtn.addEventListener('click', () => {
932
+ const text = codeContent.innerText;
933
+ copyToClipboard(text);
934
+
935
+ // Toggle icons
936
+ iconCopy.style.display = 'none';
937
+ iconCheck.style.display = 'block';
938
+
939
+ setTimeout(() => {
940
+ iconCopy.style.display = 'block';
941
+ iconCheck.style.display = 'none';
942
+ }, 2000);
943
+ });
944
+
945
+ function copyToClipboard(text) {
946
+ const tempInput = document.createElement('textarea');
947
+ tempInput.value = text;
948
+ document.body.appendChild(tempInput);
949
+ tempInput.select();
950
+ document.execCommand('copy');
951
+ document.body.removeChild(tempInput);
952
+ }
953
+
954
+ function showToast(msg) {
955
+ toast.textContent = msg;
956
+ toast.classList.add('show');
957
+ setTimeout(() => toast.classList.remove('show'), 2000);
958
+ }
959
+
960
+ // Navigation logic for active states
961
+ const navLinks = document.querySelectorAll('.nav-link');
962
+ window.addEventListener('scroll', () => {
963
+ let current = "";
964
+ const sections = document.querySelectorAll('section');
965
+ sections.forEach(section => {
966
+ if (pageYOffset >= section.offsetTop - 150) {
967
+ current = section.getAttribute('id');
968
+ }
969
+ });
970
+ navLinks.forEach(link => {
971
+ link.classList.remove('active');
972
+ if (link.getAttribute('href').includes(current)) {
973
+ link.classList.add('active');
974
+ }
975
+ });
976
+ });
977
+ </script>
978
+ </body>
979
+
980
+ </html>
agicli/welcome.py ADDED
@@ -0,0 +1,66 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ from agicli.printplus import pr, inp
2
+ import os
3
+
4
+ def is_first_time_user(path="agicli/user/user_status.txt"):
5
+ os.makedirs(os.path.dirname(path), exist_ok=True)
6
+
7
+ if not os.path.exists(path):
8
+ with open(path, 'w') as f:
9
+ f.write('not_first_time')
10
+ return True
11
+
12
+ with open(path, 'r') as f:
13
+ status = f.read().strip().lower()
14
+
15
+ if status == 'first_time':
16
+ with open(path, 'w') as f:
17
+ f.write('not_first_time')
18
+ return True
19
+
20
+ return False
21
+
22
+ def save_detail(detail_name, detail_value, path="agicli/user/user_details.txt"):
23
+ os.makedirs(os.path.dirname(path), exist_ok=True)
24
+
25
+ details = {}
26
+ if os.path.exists(path):
27
+ with open(path, 'r') as f:
28
+ for line in f:
29
+ if '=' in line:
30
+ key, value = line.strip().split('=', 1)
31
+ details[key] = value
32
+
33
+ details[detail_name] = detail_value
34
+
35
+ with open(path, 'w') as f:
36
+ for key, value in details.items():
37
+ f.write(f"{key}={value}\n")
38
+
39
+ def get_detail(detail_name, path="agicli/user/user_details.txt"):
40
+ if not os.path.exists(path):
41
+ return None
42
+
43
+ with open(path, 'r') as f:
44
+ for line in f:
45
+ if '=' in line:
46
+ key, value = line.strip().split('=', 1)
47
+ if key == detail_name:
48
+ return value
49
+ return None
50
+
51
+ def initialize():
52
+ from agicli.home import home
53
+
54
+ pr("AGI CLI", color="red", big_text=True)
55
+ if is_first_time_user():
56
+ pr("✦ Welcome to AG Corp's AGI CLI!", color="red")
57
+ user_name = inp("What is your name? > ", color="green", margin=1)
58
+ # make sure username has no numbers, only letters and less than 20 characters
59
+ while not (user_name.isalpha() and len(user_name) < 20):
60
+ pr("Invalid name. Please enter a name with only letters and less than 20 characters.", color="red")
61
+ user_name = inp("What is your name? > ", color="green", margin=1)
62
+ save_detail("user_name", user_name)
63
+ pr(f"Nice to meet you, {user_name}!", color="green")
64
+ home()
65
+ else:
66
+ home()
app.py ADDED
@@ -0,0 +1,583 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ """
2
+ AGI CLI - Custom Terminal Application
3
+ A beautiful, minimal terminal interface for AGI CLI
4
+ """
5
+
6
+ import customtkinter as ctk
7
+ import threading
8
+ import queue
9
+ import sys
10
+ import os
11
+ from typing import Callable, Optional, List
12
+ from PIL import Image
13
+ import tkinter as tk
14
+
15
+ # Add the project root to path
16
+ sys.path.insert(0, os.path.dirname(os.path.abspath(__file__)))
17
+
18
+ # Version
19
+ VERSION = "1.0.0"
20
+
21
+
22
+ class TokenTracker:
23
+ """Global token usage tracker for AI model interactions."""
24
+
25
+ def __init__(self):
26
+ self.tokens_sent = 0
27
+ self.tokens_received = 0
28
+ self._lock = threading.Lock()
29
+ self._callbacks: List[Callable] = []
30
+
31
+ def add_tokens(self, sent: int = 0, received: int = 0):
32
+ """Add tokens to the counters."""
33
+ with self._lock:
34
+ self.tokens_sent += sent
35
+ self.tokens_received += received
36
+ self._notify_callbacks()
37
+
38
+ def reset(self):
39
+ """Reset the token counters."""
40
+ with self._lock:
41
+ self.tokens_sent = 0
42
+ self.tokens_received = 0
43
+ self._notify_callbacks()
44
+
45
+ def get_totals(self) -> tuple:
46
+ """Get current token totals."""
47
+ with self._lock:
48
+ return (self.tokens_sent, self.tokens_received)
49
+
50
+ def register_callback(self, callback: Callable):
51
+ """Register a callback to be notified on token updates."""
52
+ self._callbacks.append(callback)
53
+
54
+ def _notify_callbacks(self):
55
+ """Notify all registered callbacks."""
56
+ for callback in self._callbacks:
57
+ try:
58
+ callback()
59
+ except Exception:
60
+ pass
61
+
62
+
63
+ # Global token tracker instance
64
+ token_tracker = TokenTracker()
65
+
66
+
67
+ class SettingsWindow(ctk.CTkToplevel):
68
+ """Settings window for AGI CLI configuration."""
69
+
70
+ def __init__(self, parent):
71
+ super().__init__(parent)
72
+
73
+ self.title("AGI CLI Settings")
74
+ self.geometry("500x400")
75
+ self.minsize(400, 300)
76
+
77
+ # Make it modal-like
78
+ self.transient(parent)
79
+ self.grab_set()
80
+
81
+ # Dark theme
82
+ self.configure(fg_color="#0D1117")
83
+
84
+ # Header
85
+ header = ctk.CTkFrame(self, fg_color="#161B22", corner_radius=0, height=50)
86
+ header.pack(fill="x")
87
+ header.pack_propagate(False)
88
+
89
+ title = ctk.CTkLabel(
90
+ header,
91
+ text="⚙️ Settings",
92
+ font=ctk.CTkFont(family="SF Mono, Monaco, Consolas, monospace", size=16, weight="bold"),
93
+ text_color="#F8F9FA"
94
+ )
95
+ title.pack(side="left", padx=20, pady=12)
96
+
97
+ # Content area
98
+ content = ctk.CTkFrame(self, fg_color="transparent")
99
+ content.pack(fill="both", expand=True, padx=20, pady=20)
100
+
101
+ # Placeholder message
102
+ placeholder = ctk.CTkLabel(
103
+ content,
104
+ text="Settings coming soon...\n\nThis is a placeholder settings page.\nConfiguration options will be added here.",
105
+ font=ctk.CTkFont(family="SF Mono, Monaco, Consolas, monospace", size=14),
106
+ text_color="#8B949E",
107
+ justify="center"
108
+ )
109
+ placeholder.pack(expand=True)
110
+
111
+ # Close button
112
+ close_btn = ctk.CTkButton(
113
+ content,
114
+ text="Close",
115
+ font=ctk.CTkFont(family="SF Mono, Monaco, Consolas, monospace", size=13),
116
+ fg_color="#30363D",
117
+ hover_color="#484F58",
118
+ text_color="#F8F9FA",
119
+ corner_radius=6,
120
+ height=36,
121
+ command=self.destroy
122
+ )
123
+ close_btn.pack(side="bottom", pady=10)
124
+
125
+ # Center on parent
126
+ self.update_idletasks()
127
+ parent_x = parent.winfo_x()
128
+ parent_y = parent.winfo_y()
129
+ parent_w = parent.winfo_width()
130
+ parent_h = parent.winfo_height()
131
+ w = self.winfo_width()
132
+ h = self.winfo_height()
133
+ x = parent_x + (parent_w - w) // 2
134
+ y = parent_y + (parent_h - h) // 2
135
+ self.geometry(f"+{x}+{y}")
136
+
137
+
138
+ class CustomTerminal(ctk.CTkFrame):
139
+ """A custom terminal widget with modern, minimal design."""
140
+
141
+ # ANSI color mapping to CTk-compatible colors (dark theme optimized)
142
+ COLOR_MAP = {
143
+ "red": "#FF6B6B",
144
+ "green": "#69DB7C",
145
+ "yellow": "#FFE066",
146
+ "blue": "#74C0FC",
147
+ "magenta": "#DA77F2",
148
+ "cyan": "#66D9E8",
149
+ "orange": "#FFA94D",
150
+ "black": "#868E96",
151
+ "white": "#F8F9FA",
152
+ "reset": "#DEE2E6",
153
+ }
154
+
155
+ def __init__(self, master, on_settings_click: Callable = None, **kwargs):
156
+ super().__init__(master, **kwargs)
157
+
158
+ self.configure(fg_color="transparent")
159
+ self.on_settings_click = on_settings_click
160
+
161
+ # Command history
162
+ self.command_history = []
163
+ self.history_index = -1
164
+
165
+ # Input callback
166
+ self.input_callback: Optional[Callable] = None
167
+ self.waiting_for_input = False
168
+ self.input_prompt = ""
169
+ self.input_color = "green"
170
+
171
+ # Message queue for thread-safe updates
172
+ self.message_queue = queue.Queue()
173
+
174
+ # User message queue (messages queued while AI is processing)
175
+ self.user_message_queue: List[str] = []
176
+ self._queue_lock = threading.Lock()
177
+
178
+ # Create main container - pure black, no border
179
+ self.main_container = ctk.CTkFrame(
180
+ self,
181
+ fg_color="#000000",
182
+ corner_radius=0,
183
+ border_width=0
184
+ )
185
+ self.main_container.pack(fill="both", expand=True, padx=0, pady=0)
186
+
187
+ # Scrollable output area
188
+ self.output_frame = ctk.CTkScrollableFrame(
189
+ self.main_container,
190
+ fg_color="#000000",
191
+ corner_radius=0,
192
+ scrollbar_button_color="#30363D",
193
+ scrollbar_button_hover_color="#484F58"
194
+ )
195
+ self.output_frame.pack(fill="both", expand=True, padx=16, pady=(8, 0))
196
+
197
+ # Input area container (fixed at bottom, above status bar)
198
+ self.input_container = ctk.CTkFrame(
199
+ self.main_container,
200
+ fg_color="#0A0A0A",
201
+ corner_radius=0,
202
+ height=48
203
+ )
204
+ self.input_container.pack(fill="x", side="bottom", padx=0, pady=0)
205
+ self.input_container.pack_propagate(False)
206
+
207
+ # Prompt label
208
+ self.prompt_label = ctk.CTkLabel(
209
+ self.input_container,
210
+ text="›",
211
+ font=ctk.CTkFont(family="SF Mono, Monaco, Consolas, monospace", size=16, weight="bold"),
212
+ text_color="#69DB7C",
213
+ width=24
214
+ )
215
+ self.prompt_label.pack(side="left", padx=(16, 8), pady=10)
216
+
217
+ # Queue indicator (shows number of queued messages)
218
+ self.queue_indicator = ctk.CTkLabel(
219
+ self.input_container,
220
+ text="",
221
+ font=ctk.CTkFont(family="SF Mono, Monaco, Consolas, monospace", size=10),
222
+ text_color="#FFE066",
223
+ width=40
224
+ )
225
+ self.queue_indicator.pack(side="right", padx=(0, 12), pady=10)
226
+
227
+ # Input entry
228
+ self.input_entry = ctk.CTkEntry(
229
+ self.input_container,
230
+ placeholder_text="Type a message to the AI...",
231
+ font=ctk.CTkFont(family="SF Mono, Monaco, Consolas, monospace", size=14),
232
+ fg_color="transparent",
233
+ border_width=0,
234
+ text_color="#DEE2E6",
235
+ placeholder_text_color="#484F58"
236
+ )
237
+ self.input_entry.pack(fill="x", expand=True, side="left", padx=(0, 8), pady=10)
238
+
239
+ # Status bar at the very bottom
240
+ self.status_bar = ctk.CTkFrame(
241
+ self.main_container,
242
+ fg_color="#0D1117",
243
+ corner_radius=0,
244
+ height=28,
245
+ border_width=1,
246
+ border_color="#30363D"
247
+ )
248
+ self.status_bar.pack(fill="x", side="bottom", padx=0, pady=0)
249
+ self.status_bar.pack_propagate(False)
250
+
251
+ # Version label (left side)
252
+ self.version_label = ctk.CTkLabel(
253
+ self.status_bar,
254
+ text=f"AGI CLI v{VERSION}",
255
+ font=ctk.CTkFont(family="SF Mono, Monaco, Consolas, monospace", size=11),
256
+ text_color="#6E7681"
257
+ )
258
+ self.version_label.pack(side="left", padx=12, pady=4)
259
+
260
+ # Separator
261
+ sep1 = ctk.CTkLabel(
262
+ self.status_bar,
263
+ text="│",
264
+ font=ctk.CTkFont(size=11),
265
+ text_color="#30363D"
266
+ )
267
+ sep1.pack(side="left", padx=4)
268
+
269
+ # Token counters
270
+ self.tokens_label = ctk.CTkLabel(
271
+ self.status_bar,
272
+ text="↑ 0 ↓ 0",
273
+ font=ctk.CTkFont(family="SF Mono, Monaco, Consolas, monospace", size=11),
274
+ text_color="#8B949E"
275
+ )
276
+ self.tokens_label.pack(side="left", padx=8, pady=4)
277
+
278
+ # Register for token updates
279
+ token_tracker.register_callback(self._update_token_display)
280
+
281
+ # Settings button (right side)
282
+ self.settings_btn = ctk.CTkButton(
283
+ self.status_bar,
284
+ text="⚙",
285
+ font=ctk.CTkFont(size=14),
286
+ fg_color="transparent",
287
+ hover_color="#30363D",
288
+ text_color="#8B949E",
289
+ width=28,
290
+ height=22,
291
+ corner_radius=4,
292
+ command=self._on_settings_click
293
+ )
294
+ self.settings_btn.pack(side="right", padx=8, pady=3)
295
+
296
+ # Bind events
297
+ self.input_entry.bind("<Return>", self._on_enter)
298
+ self.input_entry.bind("<Up>", self._on_up)
299
+ self.input_entry.bind("<Down>", self._on_down)
300
+
301
+ # Start message processor
302
+ self._process_messages()
303
+
304
+ # Focus input
305
+ self.input_entry.focus_set()
306
+
307
+ def _on_settings_click(self):
308
+ """Handle settings button click."""
309
+ if self.on_settings_click:
310
+ self.on_settings_click()
311
+
312
+ def _update_token_display(self):
313
+ """Update the token counter display."""
314
+ sent, received = token_tracker.get_totals()
315
+ # Format numbers with K suffix for thousands
316
+ sent_str = f"{sent/1000:.1f}K" if sent >= 1000 else str(sent)
317
+ recv_str = f"{received/1000:.1f}K" if received >= 1000 else str(received)
318
+ self.after(0, lambda: self.tokens_label.configure(text=f"↑ {sent_str} ↓ {recv_str}"))
319
+
320
+ def _update_queue_indicator(self):
321
+ """Update the queue indicator."""
322
+ with self._queue_lock:
323
+ count = len(self.user_message_queue)
324
+ if count > 0:
325
+ self.queue_indicator.configure(text=f"[{count}]")
326
+ else:
327
+ self.queue_indicator.configure(text="")
328
+
329
+ def queue_user_message(self, message: str):
330
+ """Add a message to the user message queue."""
331
+ with self._queue_lock:
332
+ self.user_message_queue.append(message)
333
+ self._update_queue_indicator()
334
+ self.print(f"📝 Queued: {message}", "yellow")
335
+
336
+ def get_queued_messages(self) -> List[str]:
337
+ """Get and clear all queued messages."""
338
+ with self._queue_lock:
339
+ messages = self.user_message_queue.copy()
340
+ self.user_message_queue.clear()
341
+ self.after(0, self._update_queue_indicator)
342
+ return messages
343
+
344
+ def has_queued_messages(self) -> bool:
345
+ """Check if there are queued messages."""
346
+ with self._queue_lock:
347
+ return len(self.user_message_queue) > 0
348
+
349
+ def _process_messages(self):
350
+ """Process queued messages from other threads."""
351
+ try:
352
+ while True:
353
+ msg_type, content, color = self.message_queue.get_nowait()
354
+ if msg_type == "print":
355
+ self._add_output(content, color)
356
+ elif msg_type == "clear":
357
+ self._clear_output()
358
+ except queue.Empty:
359
+ pass
360
+ self.after(50, self._process_messages)
361
+
362
+ def _add_output(self, text: str, color: str = "white"):
363
+ """Add text to the output area."""
364
+ hex_color = self.COLOR_MAP.get(color, self.COLOR_MAP["white"])
365
+
366
+ # Check if text contains ASCII art characters
367
+ is_ascii_art = any(c in text for c in '═║╔╗╚╝█▀▄▌▐░▒▓╠╣╦╩╬╮╯╰╭╗╔')
368
+
369
+ if is_ascii_art and '\n' in text:
370
+ # For multi-line ASCII art, use tkinter Text widget for precise control
371
+ lines = text.split('\n')
372
+ line_count = len(lines)
373
+
374
+ # Create a frame to hold the text widget
375
+ text_frame = ctk.CTkFrame(self.output_frame, fg_color="transparent")
376
+ text_frame.pack(fill="x", anchor="w", pady=0)
377
+
378
+ # Use standard tk.Text for better control over spacing
379
+ text_widget = tk.Text(
380
+ text_frame,
381
+ font=("Menlo", 11),
382
+ fg=hex_color,
383
+ bg="#000000",
384
+ height=line_count,
385
+ relief="flat",
386
+ borderwidth=0,
387
+ highlightthickness=0,
388
+ spacing1=0, # Space above first line
389
+ spacing2=0, # Space between lines
390
+ spacing3=0, # Space after last line
391
+ padx=0,
392
+ pady=0,
393
+ wrap="none"
394
+ )
395
+ text_widget.insert("1.0", text)
396
+ text_widget.configure(state="disabled")
397
+ text_widget.pack(fill="x", anchor="w")
398
+ else:
399
+ # Handle regular text (line by line)
400
+ lines = text.split('\n')
401
+
402
+ for line in lines:
403
+ # Check if this specific line is ASCII art
404
+ line_is_ascii = any(c in line for c in '═║╔╗╚╝█▀▄▌▐░▒▓╠╣╦╩╬╮╯╰╭')
405
+
406
+ label = ctk.CTkLabel(
407
+ self.output_frame,
408
+ text=line if line else " ",
409
+ font=ctk.CTkFont(
410
+ family="Menlo" if line_is_ascii else "SF Mono, Monaco, Consolas, monospace",
411
+ size=11 if line_is_ascii else 13
412
+ ),
413
+ text_color=hex_color,
414
+ anchor="w",
415
+ justify="left",
416
+ wraplength=0 if line_is_ascii else 900
417
+ )
418
+ label.pack(fill="x", anchor="w", pady=0)
419
+
420
+ # Auto-scroll to bottom - update canvas first, then scroll
421
+ self.output_frame.update_idletasks()
422
+ self.output_frame._parent_canvas.yview_moveto(1.0)
423
+
424
+ def _clear_output(self):
425
+ """Clear all outputs."""
426
+ for widget in self.output_frame.winfo_children():
427
+ widget.destroy()
428
+
429
+ def print(self, text: str, color: str = "white"):
430
+ """Thread-safe print to terminal."""
431
+ self.message_queue.put(("print", str(text), color))
432
+
433
+ def clear(self):
434
+ """Thread-safe clear terminal."""
435
+ self.message_queue.put(("clear", None, None))
436
+
437
+ def set_input_callback(self, callback: Callable, prompt: str = "", color: str = "green"):
438
+ """Set a callback for when the user enters input."""
439
+ self.input_callback = callback
440
+ self.waiting_for_input = True
441
+ self.input_prompt = prompt
442
+ self.input_color = color
443
+
444
+ # Update prompt appearance
445
+ hex_color = self.COLOR_MAP.get(color, self.COLOR_MAP["green"])
446
+ self.prompt_label.configure(text_color=hex_color)
447
+
448
+ # Show prompt in output
449
+ if prompt:
450
+ self.print(prompt, color)
451
+
452
+ def _on_enter(self, event):
453
+ """Handle enter key press."""
454
+ command = self.input_entry.get().strip()
455
+
456
+ if not command:
457
+ return "break"
458
+
459
+ self.command_history.append(command)
460
+ self.history_index = len(self.command_history)
461
+ self.input_entry.delete(0, "end")
462
+
463
+ # If waiting for input and have a callback, process normally
464
+ if self.waiting_for_input and self.input_callback:
465
+
466
+ self.waiting_for_input = False
467
+ callback = self.input_callback
468
+ self.input_callback = None
469
+
470
+ # Show user input
471
+ self.print(f"› {command}", self.input_color)
472
+
473
+ # Reset status after processing
474
+ def run_callback():
475
+ try:
476
+ callback(command)
477
+ finally:
478
+ pass # Status updates removed with header
479
+
480
+ # Run in thread to prevent blocking
481
+ threading.Thread(target=run_callback, daemon=True).start()
482
+ else:
483
+ # AI is processing, queue the message for later
484
+ self.queue_user_message(command)
485
+
486
+ return "break"
487
+
488
+ def _on_up(self, event):
489
+ """Navigate command history up."""
490
+ if self.command_history and self.history_index > 0:
491
+ self.history_index -= 1
492
+ self.input_entry.delete(0, "end")
493
+ self.input_entry.insert(0, self.command_history[self.history_index])
494
+ return "break"
495
+
496
+ def _on_down(self, event):
497
+ """Navigate command history down."""
498
+ if self.command_history and self.history_index < len(self.command_history) - 1:
499
+ self.history_index += 1
500
+ self.input_entry.delete(0, "end")
501
+ self.input_entry.insert(0, self.command_history[self.history_index])
502
+ elif self.history_index >= len(self.command_history) - 1:
503
+ self.history_index = len(self.command_history)
504
+ self.input_entry.delete(0, "end")
505
+ return "break"
506
+
507
+ def set_processing(self, is_processing: bool):
508
+ """Update the status indicator for processing state."""
509
+ # Header removed, status no longer displayed
510
+ pass
511
+
512
+
513
+ class AGICliApp(ctk.CTk):
514
+ """Main AGI CLI Application Window."""
515
+
516
+ def __init__(self):
517
+ super().__init__()
518
+
519
+ # Configure appearance
520
+ ctk.set_appearance_mode("dark")
521
+ ctk.set_default_color_theme("dark-blue")
522
+
523
+ # Window setup
524
+ self.title("AGI CLI")
525
+ self.geometry("1100x700")
526
+ self.minsize(800, 500)
527
+
528
+ # Set dock icon
529
+ try:
530
+ icon_path = os.path.join(os.path.dirname(os.path.abspath(__file__)), "logo.png")
531
+ if os.path.exists(icon_path):
532
+ # For macOS dock icon
533
+ self.iconphoto(True, tk.PhotoImage(file=icon_path))
534
+ except Exception:
535
+ pass # Ignore icon errors
536
+
537
+ # Set pure black background
538
+ self.configure(fg_color="#000000")
539
+
540
+ # Settings window reference
541
+ self.settings_window = None
542
+
543
+ # Create custom terminal
544
+ self.terminal = CustomTerminal(self, on_settings_click=self._open_settings)
545
+ self.terminal.pack(fill="both", expand=True, padx=0, pady=0)
546
+
547
+ # Make terminal globally accessible for printplus module
548
+ import agicli.printplus as pp
549
+ pp._terminal = self.terminal
550
+
551
+ # Make token tracker globally accessible
552
+ pp._token_tracker = token_tracker
553
+
554
+ # Start the CLI in a separate thread
555
+ self.after(500, self._start_cli)
556
+
557
+ def _open_settings(self):
558
+ """Open the settings window."""
559
+ if self.settings_window is None or not self.settings_window.winfo_exists():
560
+ self.settings_window = SettingsWindow(self)
561
+ else:
562
+ self.settings_window.focus()
563
+
564
+ def _start_cli(self):
565
+ """Start the CLI initialization."""
566
+ def run_cli():
567
+ try:
568
+ from agicli.welcome import initialize
569
+ initialize()
570
+ except Exception as e:
571
+ self.terminal.print(f"Error: {e}", "red")
572
+
573
+ threading.Thread(target=run_cli, daemon=True).start()
574
+
575
+
576
+ def main():
577
+ """Main entry point for the AGI CLI application."""
578
+ app = AGICliApp()
579
+ app.mainloop()
580
+
581
+
582
+ if __name__ == "__main__":
583
+ main()
logo.png ADDED

Git LFS Details

  • SHA256: 8d30186b8908811f3481202e4f7f85bb1bc0cfe1a5455d3246ebad5196c7d177
  • Pointer size: 131 Bytes
  • Size of remote file: 910 kB
main.py ADDED
@@ -0,0 +1,10 @@
 
 
 
 
 
 
 
 
 
 
 
1
+ from agicli.tools import agi_tool
2
+ import agicli.welcome
3
+
4
+ @agi_tool
5
+ def get_company_news():
6
+ """Returns the latest news from AG Corp headquarters."""
7
+ return "AG Corp has successfully implemented the AGI Tool Parsing system v2.0!"
8
+
9
+ if __name__ == "__main__":
10
+ agicli.welcome.initialize()