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.
Files changed (113) hide show
  1. connectonion/__init__.py +78 -0
  2. connectonion/address.py +320 -0
  3. connectonion/agent.py +450 -0
  4. connectonion/announce.py +84 -0
  5. connectonion/asgi.py +287 -0
  6. connectonion/auto_debug_exception.py +181 -0
  7. connectonion/cli/__init__.py +3 -0
  8. connectonion/cli/browser_agent/__init__.py +5 -0
  9. connectonion/cli/browser_agent/browser.py +243 -0
  10. connectonion/cli/browser_agent/prompt.md +107 -0
  11. connectonion/cli/commands/__init__.py +1 -0
  12. connectonion/cli/commands/auth_commands.py +527 -0
  13. connectonion/cli/commands/browser_commands.py +27 -0
  14. connectonion/cli/commands/create.py +511 -0
  15. connectonion/cli/commands/deploy_commands.py +220 -0
  16. connectonion/cli/commands/doctor_commands.py +173 -0
  17. connectonion/cli/commands/init.py +469 -0
  18. connectonion/cli/commands/project_cmd_lib.py +828 -0
  19. connectonion/cli/commands/reset_commands.py +149 -0
  20. connectonion/cli/commands/status_commands.py +168 -0
  21. connectonion/cli/docs/co-vibecoding-principles-docs-contexts-all-in-one.md +2010 -0
  22. connectonion/cli/docs/connectonion.md +1256 -0
  23. connectonion/cli/docs.md +123 -0
  24. connectonion/cli/main.py +148 -0
  25. connectonion/cli/templates/meta-agent/README.md +287 -0
  26. connectonion/cli/templates/meta-agent/agent.py +196 -0
  27. connectonion/cli/templates/meta-agent/prompts/answer_prompt.md +9 -0
  28. connectonion/cli/templates/meta-agent/prompts/docs_retrieve_prompt.md +15 -0
  29. connectonion/cli/templates/meta-agent/prompts/metagent.md +71 -0
  30. connectonion/cli/templates/meta-agent/prompts/think_prompt.md +18 -0
  31. connectonion/cli/templates/minimal/README.md +56 -0
  32. connectonion/cli/templates/minimal/agent.py +40 -0
  33. connectonion/cli/templates/playwright/README.md +118 -0
  34. connectonion/cli/templates/playwright/agent.py +336 -0
  35. connectonion/cli/templates/playwright/prompt.md +102 -0
  36. connectonion/cli/templates/playwright/requirements.txt +3 -0
  37. connectonion/cli/templates/web-research/agent.py +122 -0
  38. connectonion/connect.py +128 -0
  39. connectonion/console.py +539 -0
  40. connectonion/debug_agent/__init__.py +13 -0
  41. connectonion/debug_agent/agent.py +45 -0
  42. connectonion/debug_agent/prompts/debug_assistant.md +72 -0
  43. connectonion/debug_agent/runtime_inspector.py +406 -0
  44. connectonion/debug_explainer/__init__.py +10 -0
  45. connectonion/debug_explainer/explain_agent.py +114 -0
  46. connectonion/debug_explainer/explain_context.py +263 -0
  47. connectonion/debug_explainer/explainer_prompt.md +29 -0
  48. connectonion/debug_explainer/root_cause_analysis_prompt.md +43 -0
  49. connectonion/debugger_ui.py +1039 -0
  50. connectonion/decorators.py +208 -0
  51. connectonion/events.py +248 -0
  52. connectonion/execution_analyzer/__init__.py +9 -0
  53. connectonion/execution_analyzer/execution_analysis.py +93 -0
  54. connectonion/execution_analyzer/execution_analysis_prompt.md +47 -0
  55. connectonion/host.py +579 -0
  56. connectonion/interactive_debugger.py +342 -0
  57. connectonion/llm.py +801 -0
  58. connectonion/llm_do.py +307 -0
  59. connectonion/logger.py +300 -0
  60. connectonion/prompt_files/__init__.py +1 -0
  61. connectonion/prompt_files/analyze_contact.md +62 -0
  62. connectonion/prompt_files/eval_expected.md +12 -0
  63. connectonion/prompt_files/react_evaluate.md +11 -0
  64. connectonion/prompt_files/react_plan.md +16 -0
  65. connectonion/prompt_files/reflect.md +22 -0
  66. connectonion/prompts.py +144 -0
  67. connectonion/relay.py +200 -0
  68. connectonion/static/docs.html +688 -0
  69. connectonion/tool_executor.py +279 -0
  70. connectonion/tool_factory.py +186 -0
  71. connectonion/tool_registry.py +105 -0
  72. connectonion/trust.py +166 -0
  73. connectonion/trust_agents.py +71 -0
  74. connectonion/trust_functions.py +88 -0
  75. connectonion/tui/__init__.py +57 -0
  76. connectonion/tui/divider.py +39 -0
  77. connectonion/tui/dropdown.py +251 -0
  78. connectonion/tui/footer.py +31 -0
  79. connectonion/tui/fuzzy.py +56 -0
  80. connectonion/tui/input.py +278 -0
  81. connectonion/tui/keys.py +35 -0
  82. connectonion/tui/pick.py +130 -0
  83. connectonion/tui/providers.py +155 -0
  84. connectonion/tui/status_bar.py +163 -0
  85. connectonion/usage.py +161 -0
  86. connectonion/useful_events_handlers/__init__.py +16 -0
  87. connectonion/useful_events_handlers/reflect.py +116 -0
  88. connectonion/useful_plugins/__init__.py +20 -0
  89. connectonion/useful_plugins/calendar_plugin.py +163 -0
  90. connectonion/useful_plugins/eval.py +139 -0
  91. connectonion/useful_plugins/gmail_plugin.py +162 -0
  92. connectonion/useful_plugins/image_result_formatter.py +127 -0
  93. connectonion/useful_plugins/re_act.py +78 -0
  94. connectonion/useful_plugins/shell_approval.py +159 -0
  95. connectonion/useful_tools/__init__.py +44 -0
  96. connectonion/useful_tools/diff_writer.py +192 -0
  97. connectonion/useful_tools/get_emails.py +183 -0
  98. connectonion/useful_tools/gmail.py +1596 -0
  99. connectonion/useful_tools/google_calendar.py +613 -0
  100. connectonion/useful_tools/memory.py +380 -0
  101. connectonion/useful_tools/microsoft_calendar.py +604 -0
  102. connectonion/useful_tools/outlook.py +488 -0
  103. connectonion/useful_tools/send_email.py +205 -0
  104. connectonion/useful_tools/shell.py +97 -0
  105. connectonion/useful_tools/slash_command.py +201 -0
  106. connectonion/useful_tools/terminal.py +285 -0
  107. connectonion/useful_tools/todo_list.py +241 -0
  108. connectonion/useful_tools/web_fetch.py +216 -0
  109. connectonion/xray.py +467 -0
  110. connectonion-0.5.8.dist-info/METADATA +741 -0
  111. connectonion-0.5.8.dist-info/RECORD +113 -0
  112. connectonion-0.5.8.dist-info/WHEEL +4 -0
  113. 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()