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,469 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Purpose: Initialize ConnectOnion project in current directory with template files, authentication, and configuration
|
|
3
|
+
LLM-Note:
|
|
4
|
+
Dependencies: imports from [os, sys, shutil, subprocess, toml, datetime, pathlib, rich.console, rich.prompt, __version__, address, auth_commands.authenticate, project_cmd_lib] | imported by [cli/main.py via handle_init()] | uses templates from [cli/templates/{minimal,playwright}] | tested by [tests/cli/test_cli_init.py]
|
|
5
|
+
Data flow: receives args (ai, key, template, description, yes, force) from CLI parser → ensure_global_config() creates ~/.co/ with master keypair if needed → check_environment_for_api_keys() detects existing keys → api_key_setup_menu() or detect_api_provider() validates API key → generate_custom_template() if template='custom' → copy template files from cli/templates/{template}/ to current dir → authenticate() to get OPENONION_API_KEY → create/update .env with API keys from ~/.co/keys.env → create .co/config.toml with project metadata and global identity → copy vibe coding docs to .co/docs/ and project root → update .gitignore if git repo → display success message with next steps
|
|
6
|
+
State/Effects: modifies ~/.co/ (config.toml, keys.env, keys/, logs/) on first run | writes to current dir: .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 (agent.py, requirements.txt, etc.) | creates temp_project_dir during auth flow (cleaned up at end) | writes to stdout via rich.Console
|
|
7
|
+
Integration: exposes handle_init(ai, key, template, description, yes, force) | calls ensure_global_config() to create 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() and address.save() for Ed25519 keypair | template options: 'minimal', 'playwright', 'custom', 'none' (default)
|
|
8
|
+
Performance: authenticate() makes network call to backend (2-5s) | generate_custom_template() calls LLM API if template='custom' | template file copying is O(n) files | config/env file operations are I/O bound
|
|
9
|
+
Errors: fails if cli/templates/{template}/ not found | fails if API key invalid during authenticate() | warns if directory not empty (requires --force or confirmation) | warns for special directories (home, root, system dirs) | skips duplicate .env keys (safe append) | creates temp_project_dir but cleans up on completion
|
|
10
|
+
"""
|
|
11
|
+
|
|
12
|
+
import os
|
|
13
|
+
import sys
|
|
14
|
+
import shutil
|
|
15
|
+
import subprocess
|
|
16
|
+
import toml
|
|
17
|
+
from datetime import datetime
|
|
18
|
+
from pathlib import Path
|
|
19
|
+
from typing import Optional, Dict, Any
|
|
20
|
+
from rich.console import Console
|
|
21
|
+
from rich.prompt import Prompt, Confirm
|
|
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
|
+
get_special_directory_warning,
|
|
32
|
+
is_directory_empty,
|
|
33
|
+
check_environment_for_api_keys,
|
|
34
|
+
api_key_setup_menu,
|
|
35
|
+
detect_api_provider,
|
|
36
|
+
get_template_info,
|
|
37
|
+
interactive_menu,
|
|
38
|
+
generate_custom_template,
|
|
39
|
+
show_progress,
|
|
40
|
+
configure_env_for_provider
|
|
41
|
+
)
|
|
42
|
+
|
|
43
|
+
console = Console()
|
|
44
|
+
|
|
45
|
+
|
|
46
|
+
def ensure_global_config() -> Dict[str, Any]:
|
|
47
|
+
"""Simple function to ensure ~/.co/ exists with global identity."""
|
|
48
|
+
global_dir = Path.home() / ".co"
|
|
49
|
+
config_path = global_dir / "config.toml"
|
|
50
|
+
|
|
51
|
+
# If exists, just load and return
|
|
52
|
+
if config_path.exists():
|
|
53
|
+
with open(config_path, 'r', encoding='utf-8') as f:
|
|
54
|
+
return toml.load(f)
|
|
55
|
+
|
|
56
|
+
# First time - create global config
|
|
57
|
+
console.print(f"\n🚀 Welcome to ConnectOnion!")
|
|
58
|
+
console.print(f"✨ Setting up global configuration...")
|
|
59
|
+
|
|
60
|
+
# Create directories
|
|
61
|
+
global_dir.mkdir(exist_ok=True)
|
|
62
|
+
(global_dir / "keys").mkdir(exist_ok=True)
|
|
63
|
+
(global_dir / "logs").mkdir(exist_ok=True)
|
|
64
|
+
|
|
65
|
+
# Generate master keys - fail fast if libraries missing
|
|
66
|
+
addr_data = address.generate()
|
|
67
|
+
address.save(addr_data, global_dir)
|
|
68
|
+
console.print(f" ✓ Generated master keypair")
|
|
69
|
+
console.print(f" ✓ Your address: {addr_data['short_address']}")
|
|
70
|
+
|
|
71
|
+
# Create config (simplified - address/email now in .env)
|
|
72
|
+
config = {
|
|
73
|
+
"connectonion": {
|
|
74
|
+
"framework_version": __version__,
|
|
75
|
+
"created": datetime.now().isoformat(),
|
|
76
|
+
},
|
|
77
|
+
"cli": {
|
|
78
|
+
"version": "1.0.0",
|
|
79
|
+
},
|
|
80
|
+
"agent": {
|
|
81
|
+
"algorithm": "ed25519",
|
|
82
|
+
"default_model": "co/gemini-2.5-pro",
|
|
83
|
+
"max_iterations": 10,
|
|
84
|
+
"created_at": datetime.now().isoformat(),
|
|
85
|
+
},
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
# Save config
|
|
89
|
+
with open(config_path, 'w', encoding='utf-8') as f:
|
|
90
|
+
toml.dump(config, f)
|
|
91
|
+
console.print(f" ✓ Created ~/.co/config.toml")
|
|
92
|
+
|
|
93
|
+
# Create keys.env with config path and agent address
|
|
94
|
+
keys_env = global_dir / "keys.env"
|
|
95
|
+
if not keys_env.exists():
|
|
96
|
+
with open(keys_env, 'w', encoding='utf-8') as f:
|
|
97
|
+
f.write(f"AGENT_CONFIG_PATH={global_dir}\n")
|
|
98
|
+
f.write(f"AGENT_ADDRESS={addr_data['address']}\n")
|
|
99
|
+
f.write("# Your agent address (Ed25519 public key) is used for:\n")
|
|
100
|
+
f.write("# - Secure agent communication (encrypt/decrypt with private key)\n")
|
|
101
|
+
f.write("# - Authentication with OpenOnion managed LLM provider\n")
|
|
102
|
+
f.write(f"# - Email address: {addr_data['address'][:10]}@mail.openonion.ai\n")
|
|
103
|
+
if sys.platform != 'win32':
|
|
104
|
+
os.chmod(keys_env, 0o600) # Read/write for owner only (Unix/Mac only)
|
|
105
|
+
else:
|
|
106
|
+
# Append if not exists
|
|
107
|
+
existing = keys_env.read_text()
|
|
108
|
+
if 'AGENT_CONFIG_PATH=' not in existing:
|
|
109
|
+
with open(keys_env, 'a', encoding='utf-8') as f:
|
|
110
|
+
f.write(f"AGENT_CONFIG_PATH={global_dir}\n")
|
|
111
|
+
if 'AGENT_ADDRESS=' not in existing:
|
|
112
|
+
with open(keys_env, 'a', encoding='utf-8') as f:
|
|
113
|
+
f.write(f"AGENT_ADDRESS={addr_data['address']}\n")
|
|
114
|
+
console.print(f" ✓ Created ~/.co/keys.env")
|
|
115
|
+
|
|
116
|
+
return config
|
|
117
|
+
|
|
118
|
+
|
|
119
|
+
def handle_init(ai: Optional[bool], key: Optional[str], template: Optional[str],
|
|
120
|
+
description: Optional[str], yes: bool, force: bool):
|
|
121
|
+
"""Initialize a ConnectOnion project in the current directory."""
|
|
122
|
+
# Ensure global config exists first
|
|
123
|
+
global_config = ensure_global_config()
|
|
124
|
+
global_identity = global_config.get("agent", {})
|
|
125
|
+
|
|
126
|
+
current_dir = os.getcwd()
|
|
127
|
+
project_name = os.path.basename(current_dir) or "my-agent"
|
|
128
|
+
|
|
129
|
+
# Track temp directory for cleanup
|
|
130
|
+
temp_project_dir = None
|
|
131
|
+
|
|
132
|
+
# Header removed for cleaner output
|
|
133
|
+
|
|
134
|
+
# Check for special directories
|
|
135
|
+
warning = get_special_directory_warning(current_dir)
|
|
136
|
+
if warning:
|
|
137
|
+
console.print(f"[yellow]{warning}[/yellow]")
|
|
138
|
+
if not yes and not Confirm.ask("[yellow]Continue anyway?[/yellow]"):
|
|
139
|
+
console.print("[yellow]Initialization cancelled.[/yellow]")
|
|
140
|
+
return
|
|
141
|
+
|
|
142
|
+
# Check if directory is empty
|
|
143
|
+
if not is_directory_empty(current_dir) and not force:
|
|
144
|
+
existing_files = os.listdir(current_dir)[:5]
|
|
145
|
+
console.print("[yellow]⚠️ Directory not empty[/yellow]")
|
|
146
|
+
console.print(f"[yellow]Existing files: {', '.join(existing_files[:5])}[/yellow]")
|
|
147
|
+
if not yes and not Confirm.ask("\n[yellow]Add ConnectOnion to existing project?[/yellow]"):
|
|
148
|
+
console.print("[yellow]Initialization cancelled.[/yellow]")
|
|
149
|
+
return
|
|
150
|
+
|
|
151
|
+
# Auto-detect API keys from environment (no menu, just detect)
|
|
152
|
+
detected_keys = {}
|
|
153
|
+
provider = None
|
|
154
|
+
|
|
155
|
+
# Check for API keys in environment
|
|
156
|
+
env_api = check_environment_for_api_keys()
|
|
157
|
+
if env_api:
|
|
158
|
+
provider, env_key = env_api
|
|
159
|
+
detected_keys[provider] = env_key
|
|
160
|
+
if not yes:
|
|
161
|
+
console.print(f"[green]✓ Detected {provider.title()} API key[/green]")
|
|
162
|
+
|
|
163
|
+
# If --key provided via flag, use it
|
|
164
|
+
if key:
|
|
165
|
+
provider, key_type = detect_api_provider(key)
|
|
166
|
+
detected_keys[provider] = key
|
|
167
|
+
|
|
168
|
+
# Template selection - default to 'none' (just add .co folder) unless --template provided
|
|
169
|
+
if not template:
|
|
170
|
+
# No --template flag provided, just add ConnectOnion config
|
|
171
|
+
template = 'none'
|
|
172
|
+
if not yes:
|
|
173
|
+
console.print("\n[green]✓ Adding ConnectOnion config (.co folder)[/green] (use --template <name> for full templates)")
|
|
174
|
+
# else: template has a specific value from --template <name>
|
|
175
|
+
|
|
176
|
+
# Handle custom template
|
|
177
|
+
custom_code = None
|
|
178
|
+
if template == 'custom':
|
|
179
|
+
if not description and not yes:
|
|
180
|
+
console.print("\n[cyan]🤖 Describe your agent:[/cyan]")
|
|
181
|
+
description = Prompt.ask(" What should your agent do?")
|
|
182
|
+
elif not description:
|
|
183
|
+
description = "A general purpose agent"
|
|
184
|
+
|
|
185
|
+
show_progress("Generating custom template with AI...", 2.0)
|
|
186
|
+
# Use detected key or empty string (will use OPENONION_API_KEY after auth)
|
|
187
|
+
template_key = list(detected_keys.values())[0] if detected_keys else ""
|
|
188
|
+
custom_code = generate_custom_template(description, template_key)
|
|
189
|
+
|
|
190
|
+
# Start initialization
|
|
191
|
+
show_progress("Initializing ConnectOnion project...", 1.0)
|
|
192
|
+
|
|
193
|
+
# Get template directory
|
|
194
|
+
cli_dir = Path(__file__).parent.parent
|
|
195
|
+
template_dir = cli_dir / "templates" / template if template != 'none' else None
|
|
196
|
+
|
|
197
|
+
if template_dir and not template_dir.exists() and template not in ['custom', 'none']:
|
|
198
|
+
console.print(f"[red]❌ Template '{template}' not found![/red]")
|
|
199
|
+
return
|
|
200
|
+
|
|
201
|
+
# Copy template files
|
|
202
|
+
files_created = []
|
|
203
|
+
files_skipped = []
|
|
204
|
+
|
|
205
|
+
if template not in ['custom', 'none'] and template_dir and template_dir.exists():
|
|
206
|
+
for item in template_dir.iterdir():
|
|
207
|
+
# Skip hidden files except .env.example
|
|
208
|
+
if item.name.startswith('.') and item.name != '.env.example':
|
|
209
|
+
continue
|
|
210
|
+
|
|
211
|
+
dest_path = Path(current_dir) / item.name
|
|
212
|
+
|
|
213
|
+
if item.is_dir():
|
|
214
|
+
# Copy directory
|
|
215
|
+
if dest_path.exists() and not force:
|
|
216
|
+
files_skipped.append(f"{item.name}/ (already exists)")
|
|
217
|
+
else:
|
|
218
|
+
if dest_path.exists():
|
|
219
|
+
shutil.rmtree(dest_path)
|
|
220
|
+
shutil.copytree(item, dest_path)
|
|
221
|
+
files_created.append(f"{item.name}/")
|
|
222
|
+
else:
|
|
223
|
+
# Skip .env.example, we'll create .env directly
|
|
224
|
+
if item.name == '.env.example':
|
|
225
|
+
continue
|
|
226
|
+
# Copy file
|
|
227
|
+
if dest_path.exists() and not force:
|
|
228
|
+
files_skipped.append(f"{item.name} (already exists)")
|
|
229
|
+
else:
|
|
230
|
+
shutil.copy2(item, dest_path)
|
|
231
|
+
files_created.append(item.name)
|
|
232
|
+
|
|
233
|
+
# Create custom agent.py if custom template
|
|
234
|
+
if custom_code:
|
|
235
|
+
agent_file = Path(current_dir) / "agent.py"
|
|
236
|
+
agent_file.write_text(custom_code, encoding='utf-8')
|
|
237
|
+
files_created.append("agent.py")
|
|
238
|
+
|
|
239
|
+
# AUTHENTICATE FIRST - so we have OPENONION_API_KEY to add to .env
|
|
240
|
+
global_co_dir = Path.home() / ".co"
|
|
241
|
+
if not global_co_dir.exists():
|
|
242
|
+
ensure_global_config()
|
|
243
|
+
|
|
244
|
+
# Authenticate to get OPENONION_API_KEY (always, for everyone)
|
|
245
|
+
auth_success = authenticate(global_co_dir, save_to_project=False)
|
|
246
|
+
|
|
247
|
+
# Handle .env file - append API keys from global config
|
|
248
|
+
env_path = Path(current_dir) / ".env"
|
|
249
|
+
global_dir = Path.home() / ".co"
|
|
250
|
+
global_keys_env = global_dir / "keys.env"
|
|
251
|
+
|
|
252
|
+
# Read existing .env if it exists
|
|
253
|
+
existing_env_content = ""
|
|
254
|
+
existing_keys = set()
|
|
255
|
+
if env_path.exists():
|
|
256
|
+
with open(env_path, 'r', encoding='utf-8') as f:
|
|
257
|
+
existing_env_content = f.read()
|
|
258
|
+
# Parse existing keys
|
|
259
|
+
for line in existing_env_content.split('\n'):
|
|
260
|
+
if '=' in line and not line.strip().startswith('#'):
|
|
261
|
+
key = line.split('=')[0].strip()
|
|
262
|
+
existing_keys.add(key)
|
|
263
|
+
|
|
264
|
+
# Read global keys (now includes OPENONION_API_KEY from auth)
|
|
265
|
+
keys_to_add = []
|
|
266
|
+
if global_keys_env.exists():
|
|
267
|
+
with open(global_keys_env, 'r', encoding='utf-8') as f:
|
|
268
|
+
for line in f:
|
|
269
|
+
line = line.strip()
|
|
270
|
+
if line and not line.startswith('#') and '=' in line:
|
|
271
|
+
key = line.split('=')[0].strip()
|
|
272
|
+
if key not in existing_keys:
|
|
273
|
+
keys_to_add.append(line)
|
|
274
|
+
|
|
275
|
+
# Add agent address (from global keys.env)
|
|
276
|
+
if global_keys_env.exists():
|
|
277
|
+
# Load from global keys.env to get address
|
|
278
|
+
with open(global_keys_env, 'r', encoding='utf-8') as f:
|
|
279
|
+
for line in f:
|
|
280
|
+
line = line.strip()
|
|
281
|
+
if line.startswith('AGENT_ADDRESS=') and 'AGENT_ADDRESS' not in existing_keys:
|
|
282
|
+
keys_to_add.append(line)
|
|
283
|
+
|
|
284
|
+
# Add detected API keys
|
|
285
|
+
provider_to_env = {
|
|
286
|
+
"openai": "OPENAI_API_KEY",
|
|
287
|
+
"anthropic": "ANTHROPIC_API_KEY",
|
|
288
|
+
"google": "GEMINI_API_KEY",
|
|
289
|
+
"groq": "GROQ_API_KEY",
|
|
290
|
+
}
|
|
291
|
+
for prov, key_value in detected_keys.items():
|
|
292
|
+
env_var = provider_to_env.get(prov, f"{prov.upper()}_API_KEY")
|
|
293
|
+
if env_var not in existing_keys:
|
|
294
|
+
keys_to_add.append(f"{env_var}={key_value}")
|
|
295
|
+
|
|
296
|
+
# Write or append to .env
|
|
297
|
+
if not env_path.exists():
|
|
298
|
+
# Create new .env
|
|
299
|
+
if keys_to_add:
|
|
300
|
+
# Add global config path and default model comment
|
|
301
|
+
env_content = f"AGENT_CONFIG_PATH={Path.home() / '.co'}\n"
|
|
302
|
+
env_content += "# Default model: co/gemini-2.5-pro (managed keys with free credits)\n\n"
|
|
303
|
+
env_content += '\n'.join(keys_to_add) + '\n'
|
|
304
|
+
env_path.write_text(env_content, encoding='utf-8')
|
|
305
|
+
console.print(f"[green]✓ Saved to {env_path}[/green]")
|
|
306
|
+
else:
|
|
307
|
+
# Fallback - should not happen now that we always auth
|
|
308
|
+
env_content = """# Add your LLM API key(s) below (uncomment one and set value)
|
|
309
|
+
# OPENAI_API_KEY=
|
|
310
|
+
# ANTHROPIC_API_KEY=
|
|
311
|
+
# GEMINI_API_KEY=
|
|
312
|
+
# GROQ_API_KEY=
|
|
313
|
+
|
|
314
|
+
# Optional: Override default model
|
|
315
|
+
# MODEL=gpt-4o-mini
|
|
316
|
+
"""
|
|
317
|
+
env_path.write_text(env_content, encoding='utf-8')
|
|
318
|
+
files_created.append(".env")
|
|
319
|
+
elif keys_to_add:
|
|
320
|
+
# Append missing keys to existing .env
|
|
321
|
+
with open(env_path, 'a', encoding='utf-8') as f:
|
|
322
|
+
if not existing_env_content.endswith('\n'):
|
|
323
|
+
f.write('\n')
|
|
324
|
+
f.write('\n# API Keys\n')
|
|
325
|
+
f.write('\n'.join(keys_to_add) + '\n')
|
|
326
|
+
console.print(f"[green]✓ Updated {env_path}[/green]")
|
|
327
|
+
files_created.append(".env (updated)")
|
|
328
|
+
else:
|
|
329
|
+
console.print("[green]✓ .env already contains all necessary keys[/green]")
|
|
330
|
+
|
|
331
|
+
# Create .co directory with metadata
|
|
332
|
+
co_dir = Path(current_dir) / ".co"
|
|
333
|
+
co_dir.mkdir(exist_ok=True)
|
|
334
|
+
|
|
335
|
+
# Create docs directory and copy documentation (always overwrite for latest version)
|
|
336
|
+
docs_dir = co_dir / "docs"
|
|
337
|
+
if docs_dir.exists():
|
|
338
|
+
shutil.rmtree(docs_dir)
|
|
339
|
+
docs_dir.mkdir(exist_ok=True)
|
|
340
|
+
|
|
341
|
+
# Copy ConnectOnion documentation
|
|
342
|
+
cli_dir = Path(__file__).parent.parent
|
|
343
|
+
|
|
344
|
+
# Always copy the vibe coding doc for all templates - it's the master reference doc
|
|
345
|
+
master_doc = cli_dir / "docs" / "co-vibecoding-principles-docs-contexts-all-in-one.md"
|
|
346
|
+
|
|
347
|
+
if master_doc.exists():
|
|
348
|
+
# Copy to .co/docs/ (project metadata)
|
|
349
|
+
target_doc = docs_dir / "co-vibecoding-principles-docs-contexts-all-in-one.md"
|
|
350
|
+
shutil.copy2(master_doc, target_doc)
|
|
351
|
+
files_created.append(".co/docs/co-vibecoding-principles-docs-contexts-all-in-one.md")
|
|
352
|
+
|
|
353
|
+
# ALSO copy to project root (always visible, easier to find)
|
|
354
|
+
root_doc = Path(current_dir) / "co-vibecoding-principles-docs-contexts-all-in-one.md"
|
|
355
|
+
shutil.copy2(master_doc, root_doc)
|
|
356
|
+
files_created.append("co-vibecoding-principles-docs-contexts-all-in-one.md")
|
|
357
|
+
else:
|
|
358
|
+
console.print(f"[yellow]⚠️ Warning: Vibe coding documentation not found at {master_doc}[/yellow]")
|
|
359
|
+
|
|
360
|
+
# Use global identity instead of generating project keys
|
|
361
|
+
# NO PROJECT KEYS - we use global address/email
|
|
362
|
+
# Reload global config to get updated email_active after authentication
|
|
363
|
+
global_config = toml.load(global_co_dir / "config.toml") if (global_co_dir / "config.toml").exists() else global_config
|
|
364
|
+
addr_data = global_config.get("agent", global_identity) # Use updated global identity
|
|
365
|
+
|
|
366
|
+
# Note: We're NOT creating project-specific keys anymore
|
|
367
|
+
# If user wants project-specific keys, they'll use 'co address' command
|
|
368
|
+
|
|
369
|
+
# Create config.toml (simplified - address/email now in .env)
|
|
370
|
+
config = {
|
|
371
|
+
"project": {
|
|
372
|
+
"name": os.path.basename(current_dir) or "connectonion-agent",
|
|
373
|
+
"created": datetime.now().isoformat(),
|
|
374
|
+
"framework_version": __version__,
|
|
375
|
+
"secrets": ".env", # Path to secrets file
|
|
376
|
+
},
|
|
377
|
+
"cli": {
|
|
378
|
+
"version": "1.0.0",
|
|
379
|
+
"command": "co init",
|
|
380
|
+
"template": template,
|
|
381
|
+
},
|
|
382
|
+
"agent": {
|
|
383
|
+
"algorithm": "ed25519",
|
|
384
|
+
"default_model": "co/gemini-2.5-pro", # Use managed model by default
|
|
385
|
+
"max_iterations": 10,
|
|
386
|
+
"created_at": datetime.now().isoformat(),
|
|
387
|
+
},
|
|
388
|
+
}
|
|
389
|
+
|
|
390
|
+
config_path = co_dir / "config.toml"
|
|
391
|
+
with open(config_path, "w", encoding='utf-8') as f:
|
|
392
|
+
toml.dump(config, f)
|
|
393
|
+
files_created.append(".co/config.toml")
|
|
394
|
+
|
|
395
|
+
# Handle .gitignore if in git repo
|
|
396
|
+
if (Path(current_dir) / ".git").exists():
|
|
397
|
+
gitignore_path = Path(current_dir) / ".gitignore"
|
|
398
|
+
gitignore_content = """
|
|
399
|
+
# ConnectOnion
|
|
400
|
+
.env
|
|
401
|
+
.co/keys/
|
|
402
|
+
.co/cache/
|
|
403
|
+
.co/logs/
|
|
404
|
+
.co/history/
|
|
405
|
+
co-vibecoding-principles-docs-contexts-all-in-one.md
|
|
406
|
+
*.py[cod]
|
|
407
|
+
__pycache__/
|
|
408
|
+
todo.md
|
|
409
|
+
"""
|
|
410
|
+
if gitignore_path.exists():
|
|
411
|
+
with open(gitignore_path, "a", encoding='utf-8') as f:
|
|
412
|
+
if "# ConnectOnion" not in gitignore_path.read_text(encoding='utf-8'):
|
|
413
|
+
f.write(gitignore_content)
|
|
414
|
+
files_created.append(".gitignore (updated)")
|
|
415
|
+
else:
|
|
416
|
+
gitignore_path.write_text(gitignore_content.lstrip(), encoding='utf-8')
|
|
417
|
+
files_created.append(".gitignore")
|
|
418
|
+
|
|
419
|
+
# Success message with Rich formatting
|
|
420
|
+
console.print()
|
|
421
|
+
console.print(f"[bold green]✅ Initialized ConnectOnion in {project_name}[/bold green]")
|
|
422
|
+
console.print()
|
|
423
|
+
|
|
424
|
+
# Show different message based on whether agent.py exists
|
|
425
|
+
if template != 'none' and (Path(current_dir) / "agent.py").exists():
|
|
426
|
+
# Command with syntax highlighting - compact design
|
|
427
|
+
command = "python agent.py"
|
|
428
|
+
syntax = Syntax(
|
|
429
|
+
command,
|
|
430
|
+
"bash",
|
|
431
|
+
theme="monokai",
|
|
432
|
+
background_color="#272822", # Monokai background color
|
|
433
|
+
padding=(0, 1) # Minimal padding for tight fit
|
|
434
|
+
)
|
|
435
|
+
console.print(syntax)
|
|
436
|
+
console.print()
|
|
437
|
+
|
|
438
|
+
# Vibe Coding hint - clean formatting with proper spacing
|
|
439
|
+
console.print("[bold yellow]💡 Vibe Coding:[/bold yellow] Use Claude/Cursor/Codex with")
|
|
440
|
+
console.print(f" [cyan]co-vibecoding-principles-docs-contexts-all-in-one.md[/cyan]")
|
|
441
|
+
else:
|
|
442
|
+
# Vibe Coding hint for building from scratch
|
|
443
|
+
console.print("[bold yellow]💡 Vibe Coding:[/bold yellow] Use Claude/Cursor/Codex with")
|
|
444
|
+
console.print(f" [cyan]co-vibecoding-principles-docs-contexts-all-in-one.md[/cyan]")
|
|
445
|
+
console.print(" to build your agent")
|
|
446
|
+
|
|
447
|
+
# Resources - clean format with arrows for better alignment
|
|
448
|
+
console.print()
|
|
449
|
+
console.print("[bold cyan]📚 Resources:[/bold cyan]")
|
|
450
|
+
console.print(f" Docs [dim]→[/dim] [link=https://docs.connectonion.com][blue]https://docs.connectonion.com[/blue][/link]")
|
|
451
|
+
console.print(f" Discord [dim]→[/dim] [link=https://discord.gg/4xfD9k8AUF][blue]https://discord.gg/4xfD9k8AUF[/blue][/link]")
|
|
452
|
+
console.print(f" GitHub [dim]→[/dim] [link=https://github.com/openonion/connectonion][blue]https://github.com/openonion/connectonion[/blue][/link] [dim](⭐ star us!)[/dim]")
|
|
453
|
+
console.print()
|
|
454
|
+
|
|
455
|
+
# Clean up temporary project directory if created for authentication
|
|
456
|
+
if temp_project_dir and temp_project_dir.exists():
|
|
457
|
+
# Copy the auth token to the current project
|
|
458
|
+
temp_config = temp_project_dir / ".co" / "config.toml"
|
|
459
|
+
current_config = Path(current_dir) / ".co" / "config.toml"
|
|
460
|
+
if temp_config.exists() and current_config.exists():
|
|
461
|
+
temp_data = toml.load(temp_config)
|
|
462
|
+
current_data = toml.load(current_config)
|
|
463
|
+
if "auth" in temp_data:
|
|
464
|
+
current_data["auth"] = temp_data["auth"]
|
|
465
|
+
with open(current_config, "w", encoding='utf-8') as f:
|
|
466
|
+
toml.dump(current_data, f)
|
|
467
|
+
|
|
468
|
+
# Remove the temp directory
|
|
469
|
+
shutil.rmtree(temp_project_dir)
|