connectonion 0.5.8__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- connectonion/__init__.py +78 -0
- connectonion/address.py +320 -0
- connectonion/agent.py +450 -0
- connectonion/announce.py +84 -0
- connectonion/asgi.py +287 -0
- connectonion/auto_debug_exception.py +181 -0
- connectonion/cli/__init__.py +3 -0
- connectonion/cli/browser_agent/__init__.py +5 -0
- connectonion/cli/browser_agent/browser.py +243 -0
- connectonion/cli/browser_agent/prompt.md +107 -0
- connectonion/cli/commands/__init__.py +1 -0
- connectonion/cli/commands/auth_commands.py +527 -0
- connectonion/cli/commands/browser_commands.py +27 -0
- connectonion/cli/commands/create.py +511 -0
- connectonion/cli/commands/deploy_commands.py +220 -0
- connectonion/cli/commands/doctor_commands.py +173 -0
- connectonion/cli/commands/init.py +469 -0
- connectonion/cli/commands/project_cmd_lib.py +828 -0
- connectonion/cli/commands/reset_commands.py +149 -0
- connectonion/cli/commands/status_commands.py +168 -0
- connectonion/cli/docs/co-vibecoding-principles-docs-contexts-all-in-one.md +2010 -0
- connectonion/cli/docs/connectonion.md +1256 -0
- connectonion/cli/docs.md +123 -0
- connectonion/cli/main.py +148 -0
- connectonion/cli/templates/meta-agent/README.md +287 -0
- connectonion/cli/templates/meta-agent/agent.py +196 -0
- connectonion/cli/templates/meta-agent/prompts/answer_prompt.md +9 -0
- connectonion/cli/templates/meta-agent/prompts/docs_retrieve_prompt.md +15 -0
- connectonion/cli/templates/meta-agent/prompts/metagent.md +71 -0
- connectonion/cli/templates/meta-agent/prompts/think_prompt.md +18 -0
- connectonion/cli/templates/minimal/README.md +56 -0
- connectonion/cli/templates/minimal/agent.py +40 -0
- connectonion/cli/templates/playwright/README.md +118 -0
- connectonion/cli/templates/playwright/agent.py +336 -0
- connectonion/cli/templates/playwright/prompt.md +102 -0
- connectonion/cli/templates/playwright/requirements.txt +3 -0
- connectonion/cli/templates/web-research/agent.py +122 -0
- connectonion/connect.py +128 -0
- connectonion/console.py +539 -0
- connectonion/debug_agent/__init__.py +13 -0
- connectonion/debug_agent/agent.py +45 -0
- connectonion/debug_agent/prompts/debug_assistant.md +72 -0
- connectonion/debug_agent/runtime_inspector.py +406 -0
- connectonion/debug_explainer/__init__.py +10 -0
- connectonion/debug_explainer/explain_agent.py +114 -0
- connectonion/debug_explainer/explain_context.py +263 -0
- connectonion/debug_explainer/explainer_prompt.md +29 -0
- connectonion/debug_explainer/root_cause_analysis_prompt.md +43 -0
- connectonion/debugger_ui.py +1039 -0
- connectonion/decorators.py +208 -0
- connectonion/events.py +248 -0
- connectonion/execution_analyzer/__init__.py +9 -0
- connectonion/execution_analyzer/execution_analysis.py +93 -0
- connectonion/execution_analyzer/execution_analysis_prompt.md +47 -0
- connectonion/host.py +579 -0
- connectonion/interactive_debugger.py +342 -0
- connectonion/llm.py +801 -0
- connectonion/llm_do.py +307 -0
- connectonion/logger.py +300 -0
- connectonion/prompt_files/__init__.py +1 -0
- connectonion/prompt_files/analyze_contact.md +62 -0
- connectonion/prompt_files/eval_expected.md +12 -0
- connectonion/prompt_files/react_evaluate.md +11 -0
- connectonion/prompt_files/react_plan.md +16 -0
- connectonion/prompt_files/reflect.md +22 -0
- connectonion/prompts.py +144 -0
- connectonion/relay.py +200 -0
- connectonion/static/docs.html +688 -0
- connectonion/tool_executor.py +279 -0
- connectonion/tool_factory.py +186 -0
- connectonion/tool_registry.py +105 -0
- connectonion/trust.py +166 -0
- connectonion/trust_agents.py +71 -0
- connectonion/trust_functions.py +88 -0
- connectonion/tui/__init__.py +57 -0
- connectonion/tui/divider.py +39 -0
- connectonion/tui/dropdown.py +251 -0
- connectonion/tui/footer.py +31 -0
- connectonion/tui/fuzzy.py +56 -0
- connectonion/tui/input.py +278 -0
- connectonion/tui/keys.py +35 -0
- connectonion/tui/pick.py +130 -0
- connectonion/tui/providers.py +155 -0
- connectonion/tui/status_bar.py +163 -0
- connectonion/usage.py +161 -0
- connectonion/useful_events_handlers/__init__.py +16 -0
- connectonion/useful_events_handlers/reflect.py +116 -0
- connectonion/useful_plugins/__init__.py +20 -0
- connectonion/useful_plugins/calendar_plugin.py +163 -0
- connectonion/useful_plugins/eval.py +139 -0
- connectonion/useful_plugins/gmail_plugin.py +162 -0
- connectonion/useful_plugins/image_result_formatter.py +127 -0
- connectonion/useful_plugins/re_act.py +78 -0
- connectonion/useful_plugins/shell_approval.py +159 -0
- connectonion/useful_tools/__init__.py +44 -0
- connectonion/useful_tools/diff_writer.py +192 -0
- connectonion/useful_tools/get_emails.py +183 -0
- connectonion/useful_tools/gmail.py +1596 -0
- connectonion/useful_tools/google_calendar.py +613 -0
- connectonion/useful_tools/memory.py +380 -0
- connectonion/useful_tools/microsoft_calendar.py +604 -0
- connectonion/useful_tools/outlook.py +488 -0
- connectonion/useful_tools/send_email.py +205 -0
- connectonion/useful_tools/shell.py +97 -0
- connectonion/useful_tools/slash_command.py +201 -0
- connectonion/useful_tools/terminal.py +285 -0
- connectonion/useful_tools/todo_list.py +241 -0
- connectonion/useful_tools/web_fetch.py +216 -0
- connectonion/xray.py +467 -0
- connectonion-0.5.8.dist-info/METADATA +741 -0
- connectonion-0.5.8.dist-info/RECORD +113 -0
- connectonion-0.5.8.dist-info/WHEEL +4 -0
- connectonion-0.5.8.dist-info/entry_points.txt +3 -0
|
@@ -0,0 +1,511 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Purpose: Create new ConnectOnion project in new directory with template files, authentication, and configuration
|
|
3
|
+
LLM-Note:
|
|
4
|
+
Dependencies: imports from [os, signal, sys, shutil, toml, datetime, pathlib, rich.console, rich.prompt, rich.panel, __version__, address, auth_commands.authenticate, project_cmd_lib] | imported by [cli/main.py via handle_create()] | uses templates from [cli/templates/{minimal,playwright}] | tested by [tests/cli/test_cli_create.py]
|
|
5
|
+
Data flow: receives args (name, ai, key, template, description, yes) from CLI parser → validate_project_name() checks name validity → ensure_global_config() creates ~/.co/ with master keypair if needed → check_environment_for_api_keys() detects existing keys → interactive_menu() or api_key_setup_menu() gets user choices → generate_custom_template_with_name() if template='custom' → create new directory with project name → copy template files from cli/templates/{template}/ to new dir → authenticate() to get OPENONION_API_KEY → create .env with API keys → create .co/config.toml with project metadata and global identity → copy vibe coding docs → create .gitignore → display success message with next steps
|
|
6
|
+
State/Effects: modifies ~/.co/ (config.toml, keys.env, keys/, logs/) on first run | creates new directory {name}/ in current dir | writes to {name}/: .co/config.toml, .env, agent.py (if template), .gitignore, co-vibecoding-principles-docs-contexts-all-in-one.md | calls authenticate() which writes OPENONION_API_KEY to ~/.co/keys.env | copies template files | writes to stdout via rich.Console
|
|
7
|
+
Integration: exposes handle_create(name, ai, key, template, description, yes) | similar to init.py but creates new directory first | calls ensure_global_config() for global identity | calls authenticate(global_co_dir, save_to_project=False) for managed keys | uses template files from cli/templates/ | relies on project_cmd_lib for shared functions | uses address.generate() for Ed25519 keypair | template options: 'minimal' (default), 'playwright', 'custom'
|
|
8
|
+
Performance: authenticate() makes network call (2-5s) | generate_custom_template_with_name() calls LLM API if template='custom' | directory creation is O(1) | template file copying is O(n) files
|
|
9
|
+
Errors: fails if project name invalid (spaces, special chars) | fails if directory already exists | fails if cli/templates/{template}/ not found | fails if API key invalid during authenticate() | catches KeyboardInterrupt during interactive menus (cleans up partial state)
|
|
10
|
+
"""
|
|
11
|
+
|
|
12
|
+
import os
|
|
13
|
+
import sys
|
|
14
|
+
import shutil
|
|
15
|
+
import toml
|
|
16
|
+
from datetime import datetime
|
|
17
|
+
from pathlib import Path
|
|
18
|
+
from typing import Optional, Dict, Any
|
|
19
|
+
from rich.console import Console
|
|
20
|
+
from rich.prompt import Prompt, IntPrompt
|
|
21
|
+
from rich.panel import Panel
|
|
22
|
+
from rich.syntax import Syntax
|
|
23
|
+
from rich.text import Text
|
|
24
|
+
|
|
25
|
+
from ... import __version__
|
|
26
|
+
from ... import address
|
|
27
|
+
from .auth_commands import authenticate
|
|
28
|
+
|
|
29
|
+
# Import shared functions from project_cmd_lib
|
|
30
|
+
from .project_cmd_lib import (
|
|
31
|
+
LoadingAnimation,
|
|
32
|
+
validate_project_name,
|
|
33
|
+
check_environment_for_api_keys,
|
|
34
|
+
detect_api_provider,
|
|
35
|
+
get_template_info,
|
|
36
|
+
generate_custom_template_with_name,
|
|
37
|
+
show_progress,
|
|
38
|
+
configure_env_for_provider
|
|
39
|
+
)
|
|
40
|
+
|
|
41
|
+
console = Console()
|
|
42
|
+
|
|
43
|
+
|
|
44
|
+
def ensure_global_config() -> Dict[str, Any]:
|
|
45
|
+
"""Simple function to ensure ~/.co/ exists with global identity."""
|
|
46
|
+
global_dir = Path.home() / ".co"
|
|
47
|
+
config_path = global_dir / "config.toml"
|
|
48
|
+
|
|
49
|
+
# If exists, just load and return
|
|
50
|
+
if config_path.exists():
|
|
51
|
+
with open(config_path, 'r', encoding='utf-8') as f:
|
|
52
|
+
return toml.load(f)
|
|
53
|
+
|
|
54
|
+
# First time - create global config
|
|
55
|
+
console.print(f"\n🚀 Welcome to ConnectOnion!")
|
|
56
|
+
console.print(f"✨ Setting up global configuration...")
|
|
57
|
+
|
|
58
|
+
# Create directories
|
|
59
|
+
global_dir.mkdir(exist_ok=True)
|
|
60
|
+
(global_dir / "keys").mkdir(exist_ok=True)
|
|
61
|
+
(global_dir / "logs").mkdir(exist_ok=True)
|
|
62
|
+
|
|
63
|
+
# Generate master keys - fail fast if libraries missing
|
|
64
|
+
addr_data = address.generate()
|
|
65
|
+
address.save(addr_data, global_dir)
|
|
66
|
+
console.print(f" ✓ Generated master keypair")
|
|
67
|
+
console.print(f" ✓ Your address: {addr_data['short_address']}")
|
|
68
|
+
|
|
69
|
+
# Create config (simplified - address/email now in .env)
|
|
70
|
+
config = {
|
|
71
|
+
"connectonion": {
|
|
72
|
+
"framework_version": __version__,
|
|
73
|
+
"created": datetime.now().isoformat(),
|
|
74
|
+
},
|
|
75
|
+
"cli": {
|
|
76
|
+
"version": "1.0.0",
|
|
77
|
+
},
|
|
78
|
+
"agent": {
|
|
79
|
+
"algorithm": "ed25519",
|
|
80
|
+
"default_model": "co/gemini-2.5-pro",
|
|
81
|
+
"max_iterations": 10,
|
|
82
|
+
"created_at": datetime.now().isoformat(),
|
|
83
|
+
},
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
# Save config
|
|
87
|
+
with open(config_path, 'w', encoding='utf-8') as f:
|
|
88
|
+
toml.dump(config, f)
|
|
89
|
+
console.print(f" ✓ Created ~/.co/config.toml")
|
|
90
|
+
|
|
91
|
+
# Create keys.env with config path and agent address
|
|
92
|
+
keys_env = global_dir / "keys.env"
|
|
93
|
+
if not keys_env.exists():
|
|
94
|
+
with open(keys_env, 'w', encoding='utf-8') as f:
|
|
95
|
+
f.write(f"AGENT_CONFIG_PATH={global_dir}\n")
|
|
96
|
+
f.write(f"AGENT_ADDRESS={addr_data['address']}\n")
|
|
97
|
+
f.write("# Your agent address (Ed25519 public key) is used for:\n")
|
|
98
|
+
f.write("# - Secure agent communication (encrypt/decrypt with private key)\n")
|
|
99
|
+
f.write("# - Authentication with OpenOnion managed LLM provider\n")
|
|
100
|
+
f.write(f"# - Email address: {addr_data['address'][:10]}@mail.openonion.ai\n")
|
|
101
|
+
if sys.platform != 'win32':
|
|
102
|
+
os.chmod(keys_env, 0o600) # Read/write for owner only (Unix/Mac only)
|
|
103
|
+
else:
|
|
104
|
+
# Append if not exists
|
|
105
|
+
existing = keys_env.read_text()
|
|
106
|
+
if 'AGENT_CONFIG_PATH=' not in existing:
|
|
107
|
+
with open(keys_env, 'a', encoding='utf-8') as f:
|
|
108
|
+
f.write(f"AGENT_CONFIG_PATH={global_dir}\n")
|
|
109
|
+
if 'AGENT_ADDRESS=' not in existing:
|
|
110
|
+
with open(keys_env, 'a', encoding='utf-8') as f:
|
|
111
|
+
f.write(f"AGENT_ADDRESS={addr_data['address']}\n")
|
|
112
|
+
console.print(f" ✓ Created ~/.co/keys.env")
|
|
113
|
+
|
|
114
|
+
return config
|
|
115
|
+
|
|
116
|
+
|
|
117
|
+
def handle_create(name: Optional[str], ai: Optional[bool], key: Optional[str],
|
|
118
|
+
template: Optional[str], description: Optional[str], yes: bool):
|
|
119
|
+
"""Create a new ConnectOnion project in a new directory."""
|
|
120
|
+
# Ensure global config exists first
|
|
121
|
+
global_config = ensure_global_config()
|
|
122
|
+
|
|
123
|
+
# Header removed for cleaner output
|
|
124
|
+
|
|
125
|
+
# Template selection - default to minimal unless --template provided
|
|
126
|
+
if not template:
|
|
127
|
+
template = 'minimal'
|
|
128
|
+
# Silent - no verbose messages
|
|
129
|
+
|
|
130
|
+
# Auto-detect API keys from environment (no menu, just detect)
|
|
131
|
+
detected_keys = {}
|
|
132
|
+
provider = None
|
|
133
|
+
|
|
134
|
+
# Check for API keys in environment
|
|
135
|
+
env_api = check_environment_for_api_keys()
|
|
136
|
+
if env_api:
|
|
137
|
+
provider, env_key = env_api
|
|
138
|
+
detected_keys[provider] = env_key
|
|
139
|
+
if not yes:
|
|
140
|
+
console.print(f"[green]✓ Detected {provider.title()} API key[/green]")
|
|
141
|
+
|
|
142
|
+
# If --key provided via flag, use it
|
|
143
|
+
if key:
|
|
144
|
+
provider, key_type = detect_api_provider(key)
|
|
145
|
+
detected_keys[provider] = key
|
|
146
|
+
|
|
147
|
+
# Always authenticate to get OPENONION_API_KEY
|
|
148
|
+
global_dir = Path.home() / ".co"
|
|
149
|
+
if not yes:
|
|
150
|
+
console.print("\n[cyan]🔐 Authenticating with OpenOnion for managed keys...[/cyan]")
|
|
151
|
+
|
|
152
|
+
success = authenticate(global_dir, save_to_project=False)
|
|
153
|
+
if not success and not yes:
|
|
154
|
+
console.print("[yellow]⚠️ Authentication failed - you can still use your own API keys[/yellow]")
|
|
155
|
+
|
|
156
|
+
# Check global keys.env for API keys
|
|
157
|
+
global_keys_env = global_dir / "keys.env"
|
|
158
|
+
if global_keys_env.exists():
|
|
159
|
+
with open(global_keys_env, 'r', encoding='utf-8') as f:
|
|
160
|
+
for line in f:
|
|
161
|
+
line = line.strip()
|
|
162
|
+
if line and not line.startswith('#') and '=' in line:
|
|
163
|
+
env_key_name, env_value = line.split('=', 1)
|
|
164
|
+
# Detect provider from key name
|
|
165
|
+
if env_key_name == "OPENAI_API_KEY" and env_value.strip():
|
|
166
|
+
detected_keys["openai"] = env_value.strip()
|
|
167
|
+
elif env_key_name == "ANTHROPIC_API_KEY" and env_value.strip():
|
|
168
|
+
detected_keys["anthropic"] = env_value.strip()
|
|
169
|
+
elif env_key_name == "GEMINI_API_KEY" and env_value.strip():
|
|
170
|
+
detected_keys["google"] = env_value.strip()
|
|
171
|
+
elif env_key_name == "GROQ_API_KEY" and env_value.strip():
|
|
172
|
+
detected_keys["groq"] = env_value.strip()
|
|
173
|
+
elif env_key_name == "OPENONION_API_KEY" and env_value.strip():
|
|
174
|
+
detected_keys["openonion"] = env_value.strip()
|
|
175
|
+
|
|
176
|
+
# Use first detected key for template generation if needed
|
|
177
|
+
if detected_keys and not provider:
|
|
178
|
+
provider = list(detected_keys.keys())[0]
|
|
179
|
+
|
|
180
|
+
# For custom template generation, we need an API key
|
|
181
|
+
template_key = ""
|
|
182
|
+
if template == 'custom':
|
|
183
|
+
if detected_keys:
|
|
184
|
+
# Prefer OpenAI for custom generation, fallback to first available
|
|
185
|
+
if "openai" in detected_keys:
|
|
186
|
+
template_key = detected_keys["openai"]
|
|
187
|
+
provider = "openai"
|
|
188
|
+
else:
|
|
189
|
+
template_key = list(detected_keys.values())[0]
|
|
190
|
+
provider = list(detected_keys.keys())[0]
|
|
191
|
+
|
|
192
|
+
# Handle custom template
|
|
193
|
+
custom_code = None
|
|
194
|
+
ai_suggested_name = None
|
|
195
|
+
if template == 'custom':
|
|
196
|
+
# Custom template requires AI
|
|
197
|
+
if not template_key:
|
|
198
|
+
console.print("[red]❌ Custom template requires an API key for AI generation[/red]")
|
|
199
|
+
console.print("[yellow]Please run 'co create' again and provide an API key[/yellow]")
|
|
200
|
+
return
|
|
201
|
+
if not description and not yes:
|
|
202
|
+
console.print("\n[cyan]🤖 Describe your agent:[/cyan]")
|
|
203
|
+
description = Prompt.ask(" What should your agent do?")
|
|
204
|
+
elif not description:
|
|
205
|
+
description = "A general purpose agent"
|
|
206
|
+
|
|
207
|
+
# Use loading animation for AI generation
|
|
208
|
+
console.print("\n[cyan]🤖 AI is generating your custom agent...[/cyan]")
|
|
209
|
+
|
|
210
|
+
with LoadingAnimation("Preparing AI generation...") as loading:
|
|
211
|
+
# Use detected API key for generation
|
|
212
|
+
loading.update(f"Analyzing: {description[:40]}...")
|
|
213
|
+
custom_code, ai_suggested_name = generate_custom_template_with_name(
|
|
214
|
+
description, template_key, model=None, loading_animation=loading
|
|
215
|
+
)
|
|
216
|
+
|
|
217
|
+
console.print("[green]✓ Generated custom agent code[/green]")
|
|
218
|
+
console.print(f"[green]✓ Suggested project name: {ai_suggested_name}[/green]")
|
|
219
|
+
|
|
220
|
+
# Get project name
|
|
221
|
+
if not name and not yes:
|
|
222
|
+
if template == 'custom':
|
|
223
|
+
# For custom template, ask for project name using AI suggestion
|
|
224
|
+
if ai_suggested_name:
|
|
225
|
+
# Use arrow key navigation for name selection
|
|
226
|
+
try:
|
|
227
|
+
import questionary
|
|
228
|
+
from questionary import Style
|
|
229
|
+
|
|
230
|
+
custom_style = Style([
|
|
231
|
+
('question', 'fg:#00ffff bold'),
|
|
232
|
+
('pointer', 'fg:#00ff00 bold'),
|
|
233
|
+
('highlighted', 'fg:#00ff00 bold'),
|
|
234
|
+
('selected', 'fg:#00ffff'),
|
|
235
|
+
])
|
|
236
|
+
|
|
237
|
+
choices = [
|
|
238
|
+
questionary.Choice(
|
|
239
|
+
title=f"🤖 {ai_suggested_name} (AI suggested)",
|
|
240
|
+
value=ai_suggested_name
|
|
241
|
+
),
|
|
242
|
+
questionary.Choice(
|
|
243
|
+
title="✏️ Type your own name",
|
|
244
|
+
value="custom"
|
|
245
|
+
)
|
|
246
|
+
]
|
|
247
|
+
|
|
248
|
+
result = questionary.select(
|
|
249
|
+
"\nChoose a project name:",
|
|
250
|
+
choices=choices,
|
|
251
|
+
style=custom_style,
|
|
252
|
+
instruction="(Use ↑/↓ arrows, press Enter to confirm)",
|
|
253
|
+
default=choices[0] # Default to AI suggestion
|
|
254
|
+
).ask()
|
|
255
|
+
|
|
256
|
+
if result == "custom":
|
|
257
|
+
name = Prompt.ask("[cyan]Project name[/cyan]")
|
|
258
|
+
else:
|
|
259
|
+
name = result
|
|
260
|
+
|
|
261
|
+
console.print(f"[green]✓ Project name:[/green] {name}")
|
|
262
|
+
|
|
263
|
+
except ImportError:
|
|
264
|
+
# Fallback to numbered menu
|
|
265
|
+
console.print("\n[cyan]Choose a project name:[/cyan]")
|
|
266
|
+
console.print(f" 1. [green]{ai_suggested_name}[/green] (AI suggested)")
|
|
267
|
+
console.print(" 2. Type your own")
|
|
268
|
+
|
|
269
|
+
choice = IntPrompt.ask("Select [1-2]", choices=["1", "2"], default="1")
|
|
270
|
+
|
|
271
|
+
if choice == 1:
|
|
272
|
+
name = ai_suggested_name
|
|
273
|
+
else:
|
|
274
|
+
name = Prompt.ask("[cyan]Project name[/cyan]")
|
|
275
|
+
else:
|
|
276
|
+
# No AI suggestion, ask for name
|
|
277
|
+
name = Prompt.ask("\n[cyan]Project name[/cyan]", default="custom-agent")
|
|
278
|
+
else:
|
|
279
|
+
# For non-custom templates, use template name directly
|
|
280
|
+
name = f"{template}-agent"
|
|
281
|
+
|
|
282
|
+
# Validate project name
|
|
283
|
+
is_valid, error_msg = validate_project_name(name)
|
|
284
|
+
while not is_valid:
|
|
285
|
+
console.print(f"[red]❌ {error_msg}[/red]")
|
|
286
|
+
name = Prompt.ask("[cyan]Project name[/cyan]", default="my-agent")
|
|
287
|
+
is_valid, error_msg = validate_project_name(name)
|
|
288
|
+
elif not name:
|
|
289
|
+
# Auto mode - use template name for non-custom, AI suggestion for custom
|
|
290
|
+
if template != 'custom':
|
|
291
|
+
name = f"{template}-agent"
|
|
292
|
+
elif ai_suggested_name:
|
|
293
|
+
# Use AI-suggested name for custom template
|
|
294
|
+
name = ai_suggested_name
|
|
295
|
+
else:
|
|
296
|
+
name = "my-agent"
|
|
297
|
+
else:
|
|
298
|
+
# Validate provided name
|
|
299
|
+
is_valid, error_msg = validate_project_name(name)
|
|
300
|
+
if not is_valid:
|
|
301
|
+
console.print(f"[red]❌ {error_msg}[/red]")
|
|
302
|
+
return
|
|
303
|
+
|
|
304
|
+
# Create new project directory
|
|
305
|
+
project_dir = Path(name)
|
|
306
|
+
|
|
307
|
+
# Check if directory exists and suggest alternative
|
|
308
|
+
if project_dir.exists():
|
|
309
|
+
base_name = name
|
|
310
|
+
counter = 2
|
|
311
|
+
suggested_name = f"{base_name}-{counter}"
|
|
312
|
+
while Path(suggested_name).exists():
|
|
313
|
+
counter += 1
|
|
314
|
+
suggested_name = f"{base_name}-{counter}"
|
|
315
|
+
|
|
316
|
+
# Show error with suggestion
|
|
317
|
+
console.print(f"\n[red]❌ '{base_name}' exists. Try: [bold]co create {suggested_name}[/bold][/red]\n")
|
|
318
|
+
return
|
|
319
|
+
|
|
320
|
+
# Create project directory
|
|
321
|
+
project_dir.mkdir(parents=True, exist_ok=True)
|
|
322
|
+
|
|
323
|
+
# Get template files
|
|
324
|
+
cli_dir = Path(__file__).parent.parent
|
|
325
|
+
template_dir = cli_dir / "templates" / template
|
|
326
|
+
|
|
327
|
+
if not template_dir.exists() and template != 'custom':
|
|
328
|
+
console.print(f"[red]❌ Template '{template}' not found![/red]")
|
|
329
|
+
shutil.rmtree(project_dir)
|
|
330
|
+
return
|
|
331
|
+
|
|
332
|
+
# Copy template files
|
|
333
|
+
files_created = []
|
|
334
|
+
|
|
335
|
+
if template != 'custom' and template_dir.exists():
|
|
336
|
+
for item in template_dir.iterdir():
|
|
337
|
+
if item.name.startswith('.') and item.name != '.env.example':
|
|
338
|
+
continue
|
|
339
|
+
|
|
340
|
+
dest_path = project_dir / item.name
|
|
341
|
+
|
|
342
|
+
if item.is_dir():
|
|
343
|
+
shutil.copytree(item, dest_path)
|
|
344
|
+
files_created.append(f"{item.name}/")
|
|
345
|
+
else:
|
|
346
|
+
if item.name != '.env.example':
|
|
347
|
+
shutil.copy2(item, dest_path)
|
|
348
|
+
files_created.append(item.name)
|
|
349
|
+
|
|
350
|
+
# Create custom agent.py if custom template
|
|
351
|
+
if custom_code:
|
|
352
|
+
agent_file = project_dir / "agent.py"
|
|
353
|
+
agent_file.write_text(custom_code, encoding='utf-8')
|
|
354
|
+
files_created.append("agent.py")
|
|
355
|
+
|
|
356
|
+
# Create .co directory (skip if it already exists from temp project)
|
|
357
|
+
co_dir = project_dir / ".co"
|
|
358
|
+
if not co_dir.exists():
|
|
359
|
+
co_dir.mkdir(exist_ok=True)
|
|
360
|
+
|
|
361
|
+
# Create docs directory
|
|
362
|
+
docs_dir = co_dir / "docs"
|
|
363
|
+
docs_dir.mkdir(exist_ok=True)
|
|
364
|
+
|
|
365
|
+
# Copy ConnectOnion documentation from single master source
|
|
366
|
+
cli_dir = Path(__file__).parent.parent
|
|
367
|
+
|
|
368
|
+
# Copy the main vibe-coding documentation - keep original filename
|
|
369
|
+
master_vibe_doc = cli_dir / "docs" / "co-vibecoding-principles-docs-contexts-all-in-one.md"
|
|
370
|
+
if master_vibe_doc.exists():
|
|
371
|
+
# Copy to .co/docs/ (project metadata)
|
|
372
|
+
shutil.copy2(master_vibe_doc, docs_dir / "co-vibecoding-principles-docs-contexts-all-in-one.md")
|
|
373
|
+
files_created.append(".co/docs/co-vibecoding-principles-docs-contexts-all-in-one.md")
|
|
374
|
+
|
|
375
|
+
# ALSO copy to project root (always visible, easier to find)
|
|
376
|
+
root_doc = project_dir / "co-vibecoding-principles-docs-contexts-all-in-one.md"
|
|
377
|
+
shutil.copy2(master_vibe_doc, root_doc)
|
|
378
|
+
files_created.append("co-vibecoding-principles-docs-contexts-all-in-one.md")
|
|
379
|
+
else:
|
|
380
|
+
console.print(f"[yellow]⚠️ Warning: Vibe coding documentation not found at {master_vibe_doc}[/yellow]")
|
|
381
|
+
|
|
382
|
+
# Create config.toml (simplified - agent metadata now in .env)
|
|
383
|
+
config = {
|
|
384
|
+
"project": {
|
|
385
|
+
"name": name,
|
|
386
|
+
"created": datetime.now().isoformat(),
|
|
387
|
+
"framework_version": __version__,
|
|
388
|
+
"secrets": ".env", # Path to secrets file
|
|
389
|
+
},
|
|
390
|
+
"cli": {
|
|
391
|
+
"version": "1.0.0",
|
|
392
|
+
"command": f"co create {name}",
|
|
393
|
+
"template": template,
|
|
394
|
+
},
|
|
395
|
+
"agent": {
|
|
396
|
+
"algorithm": "ed25519",
|
|
397
|
+
"default_model": "co/gemini-2.5-pro", # Default to managed keys
|
|
398
|
+
"max_iterations": 10,
|
|
399
|
+
"created_at": datetime.now().isoformat(),
|
|
400
|
+
},
|
|
401
|
+
}
|
|
402
|
+
|
|
403
|
+
config_path = co_dir / "config.toml"
|
|
404
|
+
with open(config_path, "w", encoding='utf-8') as f:
|
|
405
|
+
toml.dump(config, f)
|
|
406
|
+
files_created.append(".co/config.toml")
|
|
407
|
+
|
|
408
|
+
# Create .env file - copy from global keys.env
|
|
409
|
+
env_path = project_dir / ".env"
|
|
410
|
+
|
|
411
|
+
# Always copy from global keys.env (includes AGENT_ADDRESS, AGENT_EMAIL, and API keys)
|
|
412
|
+
if global_keys_env.exists() and global_keys_env.stat().st_size > 0:
|
|
413
|
+
# Copy global keys to project
|
|
414
|
+
with open(global_keys_env, 'r', encoding='utf-8') as f:
|
|
415
|
+
env_content = f.read()
|
|
416
|
+
|
|
417
|
+
# Add config path and default model comment if not already present
|
|
418
|
+
lines_to_add = []
|
|
419
|
+
if "AGENT_CONFIG_PATH=" not in env_content:
|
|
420
|
+
lines_to_add.append(f"AGENT_CONFIG_PATH={Path.home() / '.co'}\n")
|
|
421
|
+
if "# Default model:" not in env_content:
|
|
422
|
+
lines_to_add.append("# Default model: co/gemini-2.5-pro (managed keys with free credits)\n")
|
|
423
|
+
|
|
424
|
+
if lines_to_add:
|
|
425
|
+
# Add blank line after comments if we're adding any
|
|
426
|
+
lines_to_add.append("\n")
|
|
427
|
+
env_content = "".join(lines_to_add) + env_content
|
|
428
|
+
else:
|
|
429
|
+
# Fallback - create minimal .env with detected keys
|
|
430
|
+
env_lines = [
|
|
431
|
+
f"AGENT_CONFIG_PATH={Path.home() / '.co'}",
|
|
432
|
+
"# Default model: co/gemini-2.5-pro (managed keys with free credits)",
|
|
433
|
+
"",
|
|
434
|
+
]
|
|
435
|
+
|
|
436
|
+
# Add detected API keys
|
|
437
|
+
provider_to_env = {
|
|
438
|
+
"openai": "OPENAI_API_KEY",
|
|
439
|
+
"anthropic": "ANTHROPIC_API_KEY",
|
|
440
|
+
"google": "GEMINI_API_KEY",
|
|
441
|
+
"groq": "GROQ_API_KEY",
|
|
442
|
+
"openonion": "OPENONION_API_KEY",
|
|
443
|
+
}
|
|
444
|
+
|
|
445
|
+
for prov, key_value in detected_keys.items():
|
|
446
|
+
env_var = provider_to_env.get(prov, f"{prov.upper()}_API_KEY")
|
|
447
|
+
env_lines.append(f"{env_var}={key_value}")
|
|
448
|
+
|
|
449
|
+
if len(env_lines) == 3: # Only header, no keys added
|
|
450
|
+
# No keys at all - create template
|
|
451
|
+
env_lines.extend([
|
|
452
|
+
"# Add your LLM API key(s) below",
|
|
453
|
+
"# OPENAI_API_KEY=",
|
|
454
|
+
"# ANTHROPIC_API_KEY=",
|
|
455
|
+
"# GEMINI_API_KEY=",
|
|
456
|
+
])
|
|
457
|
+
|
|
458
|
+
env_content = "\n".join(env_lines) + "\n"
|
|
459
|
+
|
|
460
|
+
env_path.write_text(env_content, encoding='utf-8')
|
|
461
|
+
files_created.append(".env")
|
|
462
|
+
|
|
463
|
+
# Show where the .env file was saved
|
|
464
|
+
if not yes:
|
|
465
|
+
console.print(f"[green]✓ Saved to {env_path}[/green]")
|
|
466
|
+
|
|
467
|
+
# Create .gitignore if in git repo
|
|
468
|
+
if (project_dir / ".git").exists() or (Path.cwd() / ".git").exists():
|
|
469
|
+
gitignore_path = project_dir / ".gitignore"
|
|
470
|
+
gitignore_content = """
|
|
471
|
+
# ConnectOnion
|
|
472
|
+
.env
|
|
473
|
+
.co/keys/
|
|
474
|
+
.co/cache/
|
|
475
|
+
.co/logs/
|
|
476
|
+
.co/history/
|
|
477
|
+
*.py[cod]
|
|
478
|
+
__pycache__/
|
|
479
|
+
todo.md
|
|
480
|
+
"""
|
|
481
|
+
gitignore_path.write_text(gitignore_content.lstrip(), encoding='utf-8')
|
|
482
|
+
files_created.append(".gitignore")
|
|
483
|
+
|
|
484
|
+
# Success message with Rich formatting
|
|
485
|
+
console.print()
|
|
486
|
+
console.print(f"[bold green]✅ Created {name}[/bold green]")
|
|
487
|
+
console.print()
|
|
488
|
+
|
|
489
|
+
# Command with syntax highlighting - compact design
|
|
490
|
+
command = f"cd {name} && python agent.py"
|
|
491
|
+
syntax = Syntax(
|
|
492
|
+
command,
|
|
493
|
+
"bash",
|
|
494
|
+
theme="monokai",
|
|
495
|
+
background_color="#272822", # Monokai background color
|
|
496
|
+
padding=(0, 1) # Minimal padding for tight fit
|
|
497
|
+
)
|
|
498
|
+
console.print(syntax)
|
|
499
|
+
console.print()
|
|
500
|
+
|
|
501
|
+
# Vibe Coding hint - clean formatting with proper spacing
|
|
502
|
+
console.print("[bold yellow]💡 Vibe Coding:[/bold yellow] Use Claude/Cursor/Codex with")
|
|
503
|
+
console.print(f" [cyan]co-vibecoding-principles-docs-contexts-all-in-one.md[/cyan]")
|
|
504
|
+
console.print()
|
|
505
|
+
|
|
506
|
+
# Resources - clean format with arrows for better alignment
|
|
507
|
+
console.print("[bold cyan]📚 Resources:[/bold cyan]")
|
|
508
|
+
console.print(f" Docs [dim]→[/dim] [link=https://docs.connectonion.com][blue]https://docs.connectonion.com[/blue][/link]")
|
|
509
|
+
console.print(f" Discord [dim]→[/dim] [link=https://discord.gg/4xfD9k8AUF][blue]https://discord.gg/4xfD9k8AUF[/blue][/link]")
|
|
510
|
+
console.print(f" GitHub [dim]→[/dim] [link=https://github.com/openonion/connectonion][blue]https://github.com/openonion/connectonion[/blue][/link] [dim](⭐ star us!)[/dim]")
|
|
511
|
+
console.print()
|