connectonion 0.5.7__tar.gz → 0.5.9__tar.gz
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-0.5.7 → connectonion-0.5.9}/PKG-INFO +1 -1
- {connectonion-0.5.7 → connectonion-0.5.9}/connectonion/__init__.py +3 -1
- connectonion-0.5.9/connectonion/cli/commands/copy_commands.py +116 -0
- {connectonion-0.5.7 → connectonion-0.5.9}/connectonion/cli/main.py +14 -1
- connectonion-0.5.9/connectonion/connect.py +272 -0
- {connectonion-0.5.7 → connectonion-0.5.9}/connectonion/host.py +8 -3
- {connectonion-0.5.7 → connectonion-0.5.9}/connectonion/static/docs.html +97 -0
- connectonion-0.5.9/connectonion/transcribe.py +245 -0
- {connectonion-0.5.7 → connectonion-0.5.9}/docs/cli/README.md +47 -0
- {connectonion-0.5.7 → connectonion-0.5.9}/docs/useful_plugins/README.md +24 -0
- {connectonion-0.5.7 → connectonion-0.5.9}/docs/useful_tools/README.md +24 -0
- {connectonion-0.5.7 → connectonion-0.5.9}/pyproject.toml +1 -1
- connectonion-0.5.7/connectonion/connect.py +0 -128
- {connectonion-0.5.7 → connectonion-0.5.9}/.gitignore +0 -0
- {connectonion-0.5.7 → connectonion-0.5.9}/README.md +0 -0
- {connectonion-0.5.7 → connectonion-0.5.9}/connectonion/address.py +0 -0
- {connectonion-0.5.7 → connectonion-0.5.9}/connectonion/agent.py +0 -0
- {connectonion-0.5.7 → connectonion-0.5.9}/connectonion/announce.py +0 -0
- {connectonion-0.5.7 → connectonion-0.5.9}/connectonion/asgi.py +0 -0
- {connectonion-0.5.7 → connectonion-0.5.9}/connectonion/auto_debug_exception.py +0 -0
- {connectonion-0.5.7 → connectonion-0.5.9}/connectonion/cli/__init__.py +0 -0
- {connectonion-0.5.7 → connectonion-0.5.9}/connectonion/cli/browser_agent/__init__.py +0 -0
- {connectonion-0.5.7 → connectonion-0.5.9}/connectonion/cli/browser_agent/browser.py +0 -0
- {connectonion-0.5.7 → connectonion-0.5.9}/connectonion/cli/browser_agent/prompt.md +0 -0
- {connectonion-0.5.7 → connectonion-0.5.9}/connectonion/cli/commands/__init__.py +0 -0
- {connectonion-0.5.7 → connectonion-0.5.9}/connectonion/cli/commands/auth_commands.py +0 -0
- {connectonion-0.5.7 → connectonion-0.5.9}/connectonion/cli/commands/browser_commands.py +0 -0
- {connectonion-0.5.7 → connectonion-0.5.9}/connectonion/cli/commands/create.py +0 -0
- {connectonion-0.5.7 → connectonion-0.5.9}/connectonion/cli/commands/deploy_commands.py +0 -0
- {connectonion-0.5.7 → connectonion-0.5.9}/connectonion/cli/commands/doctor_commands.py +0 -0
- {connectonion-0.5.7 → connectonion-0.5.9}/connectonion/cli/commands/init.py +0 -0
- {connectonion-0.5.7 → connectonion-0.5.9}/connectonion/cli/commands/project_cmd_lib.py +0 -0
- {connectonion-0.5.7 → connectonion-0.5.9}/connectonion/cli/commands/reset_commands.py +0 -0
- {connectonion-0.5.7 → connectonion-0.5.9}/connectonion/cli/commands/status_commands.py +0 -0
- {connectonion-0.5.7 → connectonion-0.5.9}/connectonion/cli/docs/co-vibecoding-principles-docs-contexts-all-in-one.md +0 -0
- {connectonion-0.5.7 → connectonion-0.5.9}/connectonion/cli/docs/connectonion.md +0 -0
- {connectonion-0.5.7 → connectonion-0.5.9}/connectonion/cli/docs.md +0 -0
- {connectonion-0.5.7 → connectonion-0.5.9}/connectonion/cli/templates/meta-agent/README.md +0 -0
- {connectonion-0.5.7 → connectonion-0.5.9}/connectonion/cli/templates/meta-agent/agent.py +0 -0
- {connectonion-0.5.7 → connectonion-0.5.9}/connectonion/cli/templates/meta-agent/prompts/answer_prompt.md +0 -0
- {connectonion-0.5.7 → connectonion-0.5.9}/connectonion/cli/templates/meta-agent/prompts/docs_retrieve_prompt.md +0 -0
- {connectonion-0.5.7 → connectonion-0.5.9}/connectonion/cli/templates/meta-agent/prompts/metagent.md +0 -0
- {connectonion-0.5.7 → connectonion-0.5.9}/connectonion/cli/templates/meta-agent/prompts/think_prompt.md +0 -0
- {connectonion-0.5.7 → connectonion-0.5.9}/connectonion/cli/templates/minimal/README.md +0 -0
- {connectonion-0.5.7 → connectonion-0.5.9}/connectonion/cli/templates/minimal/agent.py +0 -0
- {connectonion-0.5.7 → connectonion-0.5.9}/connectonion/cli/templates/playwright/README.md +0 -0
- {connectonion-0.5.7 → connectonion-0.5.9}/connectonion/cli/templates/playwright/agent.py +0 -0
- {connectonion-0.5.7 → connectonion-0.5.9}/connectonion/cli/templates/playwright/prompt.md +0 -0
- {connectonion-0.5.7 → connectonion-0.5.9}/connectonion/cli/templates/playwright/requirements.txt +0 -0
- {connectonion-0.5.7 → connectonion-0.5.9}/connectonion/cli/templates/web-research/agent.py +0 -0
- {connectonion-0.5.7 → connectonion-0.5.9}/connectonion/console.py +0 -0
- {connectonion-0.5.7 → connectonion-0.5.9}/connectonion/debug_agent/__init__.py +0 -0
- {connectonion-0.5.7 → connectonion-0.5.9}/connectonion/debug_agent/agent.py +0 -0
- {connectonion-0.5.7 → connectonion-0.5.9}/connectonion/debug_agent/prompts/debug_assistant.md +0 -0
- {connectonion-0.5.7 → connectonion-0.5.9}/connectonion/debug_agent/runtime_inspector.py +0 -0
- {connectonion-0.5.7 → connectonion-0.5.9}/connectonion/debug_explainer/__init__.py +0 -0
- {connectonion-0.5.7 → connectonion-0.5.9}/connectonion/debug_explainer/explain_agent.py +0 -0
- {connectonion-0.5.7 → connectonion-0.5.9}/connectonion/debug_explainer/explain_context.py +0 -0
- {connectonion-0.5.7 → connectonion-0.5.9}/connectonion/debug_explainer/explainer_prompt.md +0 -0
- {connectonion-0.5.7 → connectonion-0.5.9}/connectonion/debug_explainer/root_cause_analysis_prompt.md +0 -0
- {connectonion-0.5.7 → connectonion-0.5.9}/connectonion/debugger_ui.py +0 -0
- {connectonion-0.5.7 → connectonion-0.5.9}/connectonion/decorators.py +0 -0
- {connectonion-0.5.7 → connectonion-0.5.9}/connectonion/events.py +0 -0
- {connectonion-0.5.7 → connectonion-0.5.9}/connectonion/execution_analyzer/__init__.py +0 -0
- {connectonion-0.5.7 → connectonion-0.5.9}/connectonion/execution_analyzer/execution_analysis.py +0 -0
- {connectonion-0.5.7 → connectonion-0.5.9}/connectonion/execution_analyzer/execution_analysis_prompt.md +0 -0
- {connectonion-0.5.7 → connectonion-0.5.9}/connectonion/interactive_debugger.py +0 -0
- {connectonion-0.5.7 → connectonion-0.5.9}/connectonion/llm.py +0 -0
- {connectonion-0.5.7 → connectonion-0.5.9}/connectonion/llm_do.py +0 -0
- {connectonion-0.5.7 → connectonion-0.5.9}/connectonion/logger.py +0 -0
- {connectonion-0.5.7 → connectonion-0.5.9}/connectonion/prompt_files/__init__.py +0 -0
- {connectonion-0.5.7 → connectonion-0.5.9}/connectonion/prompt_files/analyze_contact.md +0 -0
- {connectonion-0.5.7 → connectonion-0.5.9}/connectonion/prompt_files/eval_expected.md +0 -0
- {connectonion-0.5.7 → connectonion-0.5.9}/connectonion/prompt_files/react_evaluate.md +0 -0
- {connectonion-0.5.7 → connectonion-0.5.9}/connectonion/prompt_files/react_plan.md +0 -0
- {connectonion-0.5.7 → connectonion-0.5.9}/connectonion/prompt_files/reflect.md +0 -0
- {connectonion-0.5.7 → connectonion-0.5.9}/connectonion/prompts.py +0 -0
- {connectonion-0.5.7 → connectonion-0.5.9}/connectonion/relay.py +0 -0
- {connectonion-0.5.7 → connectonion-0.5.9}/connectonion/tool_executor.py +0 -0
- {connectonion-0.5.7 → connectonion-0.5.9}/connectonion/tool_factory.py +0 -0
- {connectonion-0.5.7 → connectonion-0.5.9}/connectonion/tool_registry.py +0 -0
- {connectonion-0.5.7 → connectonion-0.5.9}/connectonion/trust.py +0 -0
- {connectonion-0.5.7 → connectonion-0.5.9}/connectonion/trust_agents.py +0 -0
- {connectonion-0.5.7 → connectonion-0.5.9}/connectonion/trust_functions.py +0 -0
- {connectonion-0.5.7 → connectonion-0.5.9}/connectonion/tui/__init__.py +0 -0
- {connectonion-0.5.7 → connectonion-0.5.9}/connectonion/tui/divider.py +0 -0
- {connectonion-0.5.7 → connectonion-0.5.9}/connectonion/tui/dropdown.py +0 -0
- {connectonion-0.5.7 → connectonion-0.5.9}/connectonion/tui/footer.py +0 -0
- {connectonion-0.5.7 → connectonion-0.5.9}/connectonion/tui/fuzzy.py +0 -0
- {connectonion-0.5.7 → connectonion-0.5.9}/connectonion/tui/input.py +0 -0
- {connectonion-0.5.7 → connectonion-0.5.9}/connectonion/tui/keys.py +0 -0
- {connectonion-0.5.7 → connectonion-0.5.9}/connectonion/tui/pick.py +0 -0
- {connectonion-0.5.7 → connectonion-0.5.9}/connectonion/tui/providers.py +0 -0
- {connectonion-0.5.7 → connectonion-0.5.9}/connectonion/tui/status_bar.py +0 -0
- {connectonion-0.5.7 → connectonion-0.5.9}/connectonion/usage.py +0 -0
- {connectonion-0.5.7 → connectonion-0.5.9}/connectonion/useful_events_handlers/__init__.py +0 -0
- {connectonion-0.5.7 → connectonion-0.5.9}/connectonion/useful_events_handlers/reflect.py +0 -0
- {connectonion-0.5.7 → connectonion-0.5.9}/connectonion/useful_plugins/__init__.py +0 -0
- {connectonion-0.5.7 → connectonion-0.5.9}/connectonion/useful_plugins/calendar_plugin.py +0 -0
- {connectonion-0.5.7 → connectonion-0.5.9}/connectonion/useful_plugins/eval.py +0 -0
- {connectonion-0.5.7 → connectonion-0.5.9}/connectonion/useful_plugins/gmail_plugin.py +0 -0
- {connectonion-0.5.7 → connectonion-0.5.9}/connectonion/useful_plugins/image_result_formatter.py +0 -0
- {connectonion-0.5.7 → connectonion-0.5.9}/connectonion/useful_plugins/re_act.py +0 -0
- {connectonion-0.5.7 → connectonion-0.5.9}/connectonion/useful_plugins/shell_approval.py +0 -0
- {connectonion-0.5.7 → connectonion-0.5.9}/connectonion/useful_tools/__init__.py +0 -0
- {connectonion-0.5.7 → connectonion-0.5.9}/connectonion/useful_tools/diff_writer.py +0 -0
- {connectonion-0.5.7 → connectonion-0.5.9}/connectonion/useful_tools/get_emails.py +0 -0
- {connectonion-0.5.7 → connectonion-0.5.9}/connectonion/useful_tools/gmail.py +0 -0
- {connectonion-0.5.7 → connectonion-0.5.9}/connectonion/useful_tools/google_calendar.py +0 -0
- {connectonion-0.5.7 → connectonion-0.5.9}/connectonion/useful_tools/memory.py +0 -0
- {connectonion-0.5.7 → connectonion-0.5.9}/connectonion/useful_tools/microsoft_calendar.py +0 -0
- {connectonion-0.5.7 → connectonion-0.5.9}/connectonion/useful_tools/outlook.py +0 -0
- {connectonion-0.5.7 → connectonion-0.5.9}/connectonion/useful_tools/send_email.py +0 -0
- {connectonion-0.5.7 → connectonion-0.5.9}/connectonion/useful_tools/shell.py +0 -0
- {connectonion-0.5.7 → connectonion-0.5.9}/connectonion/useful_tools/slash_command.py +0 -0
- {connectonion-0.5.7 → connectonion-0.5.9}/connectonion/useful_tools/terminal.py +0 -0
- {connectonion-0.5.7 → connectonion-0.5.9}/connectonion/useful_tools/todo_list.py +0 -0
- {connectonion-0.5.7 → connectonion-0.5.9}/connectonion/useful_tools/web_fetch.py +0 -0
- {connectonion-0.5.7 → connectonion-0.5.9}/connectonion/xray.py +0 -0
- {connectonion-0.5.7 → connectonion-0.5.9}/docs/README.md +0 -0
- {connectonion-0.5.7 → connectonion-0.5.9}/docs/debug/README.md +0 -0
- {connectonion-0.5.7 → connectonion-0.5.9}/docs/integrations/README.md +0 -0
- {connectonion-0.5.7 → connectonion-0.5.9}/docs/network/README.md +0 -0
- {connectonion-0.5.7 → connectonion-0.5.9}/docs/templates/README.md +0 -0
- {connectonion-0.5.7 → connectonion-0.5.9}/docs/tui/README.md +0 -0
- {connectonion-0.5.7 → connectonion-0.5.9}/examples/README.md +0 -0
- {connectonion-0.5.7 → connectonion-0.5.9}/examples/browser-agent/README.md +0 -0
- {connectonion-0.5.7 → connectonion-0.5.9}/examples/email-agent/README.md +0 -0
- {connectonion-0.5.7 → connectonion-0.5.9}/examples/simple-agent/README.md +0 -0
- {connectonion-0.5.7 → connectonion-0.5.9}/prompts/README.md +0 -0
- {connectonion-0.5.7 → connectonion-0.5.9}/prompts/formats/README.md +0 -0
- {connectonion-0.5.7 → connectonion-0.5.9}/tests/README.md +0 -0
- {connectonion-0.5.7 → connectonion-0.5.9}/tests/cli/README.md +0 -0
- {connectonion-0.5.7 → connectonion-0.5.9}/tests/cli/aws/README.md +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: connectonion
|
|
3
|
-
Version: 0.5.
|
|
3
|
+
Version: 0.5.9
|
|
4
4
|
Summary: A simple Python framework for creating AI agents with behavior tracking
|
|
5
5
|
Project-URL: Homepage, https://github.com/openonion/connectonion
|
|
6
6
|
Project-URL: Documentation, https://docs.connectonion.com
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
"""ConnectOnion - A simple agent framework with behavior tracking."""
|
|
2
2
|
|
|
3
|
-
__version__ = "0.5.
|
|
3
|
+
__version__ = "0.5.9"
|
|
4
4
|
|
|
5
5
|
# Auto-load .env files for the entire framework
|
|
6
6
|
from dotenv import load_dotenv
|
|
@@ -15,6 +15,7 @@ from .tool_factory import create_tool_from_function
|
|
|
15
15
|
from .llm import LLM
|
|
16
16
|
from .logger import Logger
|
|
17
17
|
from .llm_do import llm_do
|
|
18
|
+
from .transcribe import transcribe
|
|
18
19
|
from .prompts import load_system_prompt
|
|
19
20
|
from .xray import xray
|
|
20
21
|
from .decorators import replay, xray_replay
|
|
@@ -40,6 +41,7 @@ __all__ = [
|
|
|
40
41
|
"Logger",
|
|
41
42
|
"create_tool_from_function",
|
|
42
43
|
"llm_do",
|
|
44
|
+
"transcribe",
|
|
43
45
|
"load_system_prompt",
|
|
44
46
|
"xray",
|
|
45
47
|
"replay",
|
|
@@ -0,0 +1,116 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Purpose: CLI command to copy built-in tools and plugins to user's project for customization
|
|
3
|
+
LLM-Note:
|
|
4
|
+
Dependencies: imports from [shutil, pathlib, typing, rich] | imported by [cli/main.py via handle_copy()]
|
|
5
|
+
Data flow: user runs `co copy <name>` → looks up name in TOOLS/PLUGINS registry → finds source via module.__file__ → copies to ./tools/ or ./plugins/
|
|
6
|
+
State/Effects: creates tools/ or plugins/ directory if needed | copies .py files from installed package to user's project
|
|
7
|
+
Integration: exposes handle_copy() for CLI | uses Python import system to find installed package location (cross-platform)
|
|
8
|
+
"""
|
|
9
|
+
|
|
10
|
+
import shutil
|
|
11
|
+
from pathlib import Path
|
|
12
|
+
from typing import Optional, List
|
|
13
|
+
from rich.console import Console
|
|
14
|
+
from rich.table import Table
|
|
15
|
+
|
|
16
|
+
console = Console()
|
|
17
|
+
|
|
18
|
+
# Registry of copyable tools
|
|
19
|
+
TOOLS = {
|
|
20
|
+
"gmail": "gmail.py",
|
|
21
|
+
"outlook": "outlook.py",
|
|
22
|
+
"google_calendar": "google_calendar.py",
|
|
23
|
+
"microsoft_calendar": "microsoft_calendar.py",
|
|
24
|
+
"memory": "memory.py",
|
|
25
|
+
"web_fetch": "web_fetch.py",
|
|
26
|
+
"shell": "shell.py",
|
|
27
|
+
"diff_writer": "diff_writer.py",
|
|
28
|
+
"todo_list": "todo_list.py",
|
|
29
|
+
"slash_command": "slash_command.py",
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
# Registry of copyable plugins
|
|
33
|
+
PLUGINS = {
|
|
34
|
+
"re_act": "re_act.py",
|
|
35
|
+
"eval": "eval.py",
|
|
36
|
+
"image_result_formatter": "image_result_formatter.py",
|
|
37
|
+
"shell_approval": "shell_approval.py",
|
|
38
|
+
"gmail_plugin": "gmail_plugin.py",
|
|
39
|
+
"calendar_plugin": "calendar_plugin.py",
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
|
|
43
|
+
def handle_copy(
|
|
44
|
+
names: List[str],
|
|
45
|
+
list_all: bool = False,
|
|
46
|
+
path: Optional[str] = None,
|
|
47
|
+
force: bool = False
|
|
48
|
+
):
|
|
49
|
+
"""Copy built-in tools and plugins to user's project."""
|
|
50
|
+
|
|
51
|
+
# Show list if requested or no names provided
|
|
52
|
+
if list_all or not names:
|
|
53
|
+
show_available_items()
|
|
54
|
+
return
|
|
55
|
+
|
|
56
|
+
# Get source directories using import system (works for installed packages)
|
|
57
|
+
import connectonion.useful_tools as tools_module
|
|
58
|
+
import connectonion.useful_plugins as plugins_module
|
|
59
|
+
|
|
60
|
+
useful_tools_dir = Path(tools_module.__file__).parent
|
|
61
|
+
useful_plugins_dir = Path(plugins_module.__file__).parent
|
|
62
|
+
|
|
63
|
+
current_dir = Path.cwd()
|
|
64
|
+
|
|
65
|
+
for name in names:
|
|
66
|
+
name_lower = name.lower()
|
|
67
|
+
|
|
68
|
+
# Check if it's a tool
|
|
69
|
+
if name_lower in TOOLS:
|
|
70
|
+
source = useful_tools_dir / TOOLS[name_lower]
|
|
71
|
+
dest_dir = Path(path) if path else current_dir / "tools"
|
|
72
|
+
copy_file(source, dest_dir, force)
|
|
73
|
+
|
|
74
|
+
# Check if it's a plugin
|
|
75
|
+
elif name_lower in PLUGINS:
|
|
76
|
+
source = useful_plugins_dir / PLUGINS[name_lower]
|
|
77
|
+
dest_dir = Path(path) if path else current_dir / "plugins"
|
|
78
|
+
copy_file(source, dest_dir, force)
|
|
79
|
+
|
|
80
|
+
else:
|
|
81
|
+
console.print(f"[red]Unknown: {name}[/red]")
|
|
82
|
+
console.print("Use [cyan]co copy --list[/cyan] to see available items")
|
|
83
|
+
|
|
84
|
+
|
|
85
|
+
def copy_file(source: Path, dest_dir: Path, force: bool):
|
|
86
|
+
"""Copy a single file to destination."""
|
|
87
|
+
if not source.exists():
|
|
88
|
+
console.print(f"[red]Source not found: {source}[/red]")
|
|
89
|
+
return
|
|
90
|
+
|
|
91
|
+
dest_dir.mkdir(parents=True, exist_ok=True)
|
|
92
|
+
dest = dest_dir / source.name
|
|
93
|
+
|
|
94
|
+
if dest.exists() and not force:
|
|
95
|
+
console.print(f"[yellow]Skipped: {dest} (exists, use --force)[/yellow]")
|
|
96
|
+
return
|
|
97
|
+
|
|
98
|
+
shutil.copy2(source, dest)
|
|
99
|
+
console.print(f"[green]✓ Copied: {dest}[/green]")
|
|
100
|
+
|
|
101
|
+
|
|
102
|
+
def show_available_items():
|
|
103
|
+
"""Display available tools and plugins."""
|
|
104
|
+
table = Table(title="Available Items to Copy")
|
|
105
|
+
table.add_column("Name", style="cyan")
|
|
106
|
+
table.add_column("Type", style="green")
|
|
107
|
+
table.add_column("File")
|
|
108
|
+
|
|
109
|
+
for name, file in sorted(TOOLS.items()):
|
|
110
|
+
table.add_row(name, "tool", file)
|
|
111
|
+
|
|
112
|
+
for name, file in sorted(PLUGINS.items()):
|
|
113
|
+
table.add_row(name, "plugin", file)
|
|
114
|
+
|
|
115
|
+
console.print(table)
|
|
116
|
+
console.print("\n[dim]Usage: co copy <name> [--path ./custom/][/dim]")
|
|
@@ -11,7 +11,7 @@ LLM-Note:
|
|
|
11
11
|
|
|
12
12
|
import typer
|
|
13
13
|
from rich.console import Console
|
|
14
|
-
from typing import Optional
|
|
14
|
+
from typing import Optional, List
|
|
15
15
|
|
|
16
16
|
from .. import __version__
|
|
17
17
|
|
|
@@ -54,6 +54,7 @@ def _show_help():
|
|
|
54
54
|
console.print("[bold]Commands:[/bold]")
|
|
55
55
|
console.print(" [green]create[/green] <name> Create new project")
|
|
56
56
|
console.print(" [green]init[/green] Initialize in current directory")
|
|
57
|
+
console.print(" [green]copy[/green] <name> Copy tool/plugin source to project")
|
|
57
58
|
console.print(" [green]deploy[/green] Deploy to ConnectOnion Cloud")
|
|
58
59
|
console.print(" [green]auth[/green] Authenticate for managed keys")
|
|
59
60
|
console.print(" [green]status[/green] Check account balance")
|
|
@@ -139,6 +140,18 @@ def browser(command: str = typer.Argument(..., help="Browser command")):
|
|
|
139
140
|
handle_browser(command)
|
|
140
141
|
|
|
141
142
|
|
|
143
|
+
@app.command()
|
|
144
|
+
def copy(
|
|
145
|
+
names: List[str] = typer.Argument(None, help="Tool or plugin names to copy"),
|
|
146
|
+
list_all: bool = typer.Option(False, "--list", "-l", help="List available items"),
|
|
147
|
+
path: Optional[str] = typer.Option(None, "--path", "-p", help="Custom destination path"),
|
|
148
|
+
force: bool = typer.Option(False, "--force", "-f", help="Overwrite existing files"),
|
|
149
|
+
):
|
|
150
|
+
"""Copy built-in tools/plugins to customize."""
|
|
151
|
+
from .commands.copy_commands import handle_copy
|
|
152
|
+
handle_copy(names=names or [], list_all=list_all, path=path, force=force)
|
|
153
|
+
|
|
154
|
+
|
|
142
155
|
def cli():
|
|
143
156
|
"""Entry point."""
|
|
144
157
|
app()
|
|
@@ -0,0 +1,272 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Purpose: Client interface for connecting to remote agents via HTTP or relay network
|
|
3
|
+
LLM-Note:
|
|
4
|
+
Dependencies: imports from [asyncio, json, uuid, time, aiohttp, websockets, address] | imported by [__init__.py, tests/test_connect.py, examples/] | tested by [tests/test_connect.py]
|
|
5
|
+
Data flow: connect(address, keys) → RemoteAgent → input() → discover endpoints → try HTTP first → fallback to relay → return result
|
|
6
|
+
State/Effects: caches discovered endpoint for reuse | optional signing with keys parameter
|
|
7
|
+
Integration: exposes connect(address, keys, relay_url), RemoteAgent class with .input(), .input_async()
|
|
8
|
+
Performance: discovery cached per RemoteAgent instance | HTTPS tried first (direct), relay as fallback
|
|
9
|
+
|
|
10
|
+
Connect to remote agents on the network.
|
|
11
|
+
|
|
12
|
+
Smart discovery: tries HTTP endpoints first, falls back to relay.
|
|
13
|
+
Always signs requests when keys are provided.
|
|
14
|
+
"""
|
|
15
|
+
|
|
16
|
+
import asyncio
|
|
17
|
+
import json
|
|
18
|
+
import time
|
|
19
|
+
import uuid
|
|
20
|
+
from typing import Any, Dict, List, Optional
|
|
21
|
+
|
|
22
|
+
from . import address as addr
|
|
23
|
+
|
|
24
|
+
|
|
25
|
+
class RemoteAgent:
|
|
26
|
+
"""
|
|
27
|
+
Interface to a remote agent.
|
|
28
|
+
|
|
29
|
+
Supports:
|
|
30
|
+
- Discovery via relay API
|
|
31
|
+
- Direct HTTP POST to agent /input endpoint
|
|
32
|
+
- WebSocket relay fallback
|
|
33
|
+
- Signed requests when keys provided
|
|
34
|
+
- Multi-turn conversations via session management
|
|
35
|
+
|
|
36
|
+
Usage:
|
|
37
|
+
# Standard Python scripts
|
|
38
|
+
agent = connect("0x...")
|
|
39
|
+
result = agent.input("Hello")
|
|
40
|
+
|
|
41
|
+
# Jupyter notebooks or async code
|
|
42
|
+
agent = connect("0x...")
|
|
43
|
+
result = await agent.input_async("Hello")
|
|
44
|
+
"""
|
|
45
|
+
|
|
46
|
+
def __init__(
|
|
47
|
+
self,
|
|
48
|
+
agent_address: str,
|
|
49
|
+
*,
|
|
50
|
+
keys: Optional[Dict[str, Any]] = None,
|
|
51
|
+
relay_url: str = "wss://oo.openonion.ai/ws/announce"
|
|
52
|
+
):
|
|
53
|
+
self.address = agent_address
|
|
54
|
+
self._keys = keys
|
|
55
|
+
self._relay_url = relay_url
|
|
56
|
+
self._cached_endpoint: Optional[str] = None
|
|
57
|
+
self._session: Optional[Dict[str, Any]] = None # Multi-turn conversation state
|
|
58
|
+
|
|
59
|
+
def input(self, prompt: str, timeout: float = 30.0) -> str:
|
|
60
|
+
"""
|
|
61
|
+
Send task to remote agent and get response (sync version).
|
|
62
|
+
|
|
63
|
+
Automatically maintains conversation context across calls.
|
|
64
|
+
|
|
65
|
+
Note:
|
|
66
|
+
This method cannot be used inside an async context (e.g., Jupyter notebooks,
|
|
67
|
+
async functions). Use input_async() instead in those environments.
|
|
68
|
+
|
|
69
|
+
Args:
|
|
70
|
+
prompt: Task/prompt to send
|
|
71
|
+
timeout: Seconds to wait for response (default 30)
|
|
72
|
+
|
|
73
|
+
Returns:
|
|
74
|
+
Agent's response string
|
|
75
|
+
|
|
76
|
+
Raises:
|
|
77
|
+
RuntimeError: If called from within a running event loop
|
|
78
|
+
|
|
79
|
+
Example:
|
|
80
|
+
>>> translator = connect("0x3d40...")
|
|
81
|
+
>>> result = translator.input("Translate 'hello' to Spanish")
|
|
82
|
+
>>> # Continue conversation
|
|
83
|
+
>>> result2 = translator.input("Now translate it to French")
|
|
84
|
+
"""
|
|
85
|
+
try:
|
|
86
|
+
asyncio.get_running_loop()
|
|
87
|
+
raise RuntimeError(
|
|
88
|
+
"input() cannot be used inside async context (e.g., Jupyter notebooks). "
|
|
89
|
+
"Use 'await agent.input_async()' instead."
|
|
90
|
+
)
|
|
91
|
+
except RuntimeError as e:
|
|
92
|
+
if "input() cannot be used" in str(e):
|
|
93
|
+
raise
|
|
94
|
+
# No running loop - safe to proceed
|
|
95
|
+
return asyncio.run(self._send_task(prompt, timeout))
|
|
96
|
+
|
|
97
|
+
async def input_async(self, prompt: str, timeout: float = 30.0) -> str:
|
|
98
|
+
"""
|
|
99
|
+
Send task to remote agent and get response (async version).
|
|
100
|
+
|
|
101
|
+
Automatically maintains conversation context across calls.
|
|
102
|
+
|
|
103
|
+
Args:
|
|
104
|
+
prompt: Task/prompt to send
|
|
105
|
+
timeout: Seconds to wait for response (default 30)
|
|
106
|
+
|
|
107
|
+
Returns:
|
|
108
|
+
Agent's response string
|
|
109
|
+
"""
|
|
110
|
+
return await self._send_task(prompt, timeout)
|
|
111
|
+
|
|
112
|
+
def reset_conversation(self):
|
|
113
|
+
"""Clear conversation history and start fresh."""
|
|
114
|
+
self._session = None
|
|
115
|
+
|
|
116
|
+
def _sign_payload(self, payload: Dict[str, Any]) -> Dict[str, Any]:
|
|
117
|
+
"""Sign a payload if keys are available."""
|
|
118
|
+
if not self._keys:
|
|
119
|
+
return {"prompt": payload.get("prompt", "")}
|
|
120
|
+
|
|
121
|
+
canonical = json.dumps(payload, sort_keys=True, separators=(',', ':'))
|
|
122
|
+
signature = addr.sign(self._keys, canonical.encode())
|
|
123
|
+
return {
|
|
124
|
+
"payload": payload,
|
|
125
|
+
"from": self._keys["address"],
|
|
126
|
+
"signature": signature.hex()
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
async def _discover_endpoints(self) -> List[str]:
|
|
130
|
+
"""Query relay API for agent endpoints."""
|
|
131
|
+
import aiohttp
|
|
132
|
+
|
|
133
|
+
# Convert wss://oo.openonion.ai/ws/announce to https://oo.openonion.ai
|
|
134
|
+
base_url = self._relay_url.replace("wss://", "https://").replace("ws://", "http://")
|
|
135
|
+
base_url = base_url.replace("/ws/announce", "")
|
|
136
|
+
|
|
137
|
+
async with aiohttp.ClientSession() as session:
|
|
138
|
+
async with session.get(f"{base_url}/api/relay/agents/{self.address}") as resp:
|
|
139
|
+
if resp.status == 200:
|
|
140
|
+
data = await resp.json()
|
|
141
|
+
if data.get("online"):
|
|
142
|
+
return data.get("endpoints", [])
|
|
143
|
+
return []
|
|
144
|
+
|
|
145
|
+
def _create_signed_body(self, prompt: str) -> Dict[str, Any]:
|
|
146
|
+
"""Create signed request body for agent /input endpoint."""
|
|
147
|
+
payload = {"prompt": prompt, "to": self.address, "timestamp": int(time.time())}
|
|
148
|
+
body = self._sign_payload(payload)
|
|
149
|
+
if self._session:
|
|
150
|
+
body["session"] = self._session
|
|
151
|
+
return body
|
|
152
|
+
|
|
153
|
+
async def _send_http(self, endpoint: str, prompt: str, timeout: float) -> str:
|
|
154
|
+
"""Send request via direct HTTP POST to agent /input endpoint."""
|
|
155
|
+
import aiohttp
|
|
156
|
+
|
|
157
|
+
body = self._create_signed_body(prompt)
|
|
158
|
+
|
|
159
|
+
async with aiohttp.ClientSession() as http_session:
|
|
160
|
+
async with http_session.post(
|
|
161
|
+
f"{endpoint}/input",
|
|
162
|
+
json=body,
|
|
163
|
+
timeout=aiohttp.ClientTimeout(total=timeout)
|
|
164
|
+
) as resp:
|
|
165
|
+
data = await resp.json()
|
|
166
|
+
if not resp.ok:
|
|
167
|
+
raise ConnectionError(data.get("error", f"HTTP {resp.status}"))
|
|
168
|
+
# Save session for conversation continuation
|
|
169
|
+
if "session" in data:
|
|
170
|
+
self._session = data["session"]
|
|
171
|
+
return data.get("result", "")
|
|
172
|
+
|
|
173
|
+
async def _send_relay(self, prompt: str, timeout: float) -> str:
|
|
174
|
+
"""Send request via WebSocket relay."""
|
|
175
|
+
import websockets
|
|
176
|
+
|
|
177
|
+
input_id = str(uuid.uuid4())
|
|
178
|
+
relay_input_url = self._relay_url.replace("/ws/announce", "/ws/input")
|
|
179
|
+
|
|
180
|
+
async with websockets.connect(relay_input_url) as ws:
|
|
181
|
+
payload = {"prompt": prompt, "to": self.address, "timestamp": int(time.time())}
|
|
182
|
+
signed = self._sign_payload(payload)
|
|
183
|
+
|
|
184
|
+
input_message = {
|
|
185
|
+
"type": "INPUT",
|
|
186
|
+
"input_id": input_id,
|
|
187
|
+
"to": self.address,
|
|
188
|
+
**signed
|
|
189
|
+
}
|
|
190
|
+
|
|
191
|
+
await ws.send(json.dumps(input_message))
|
|
192
|
+
|
|
193
|
+
response_data = await asyncio.wait_for(ws.recv(), timeout=timeout)
|
|
194
|
+
response = json.loads(response_data)
|
|
195
|
+
|
|
196
|
+
if response.get("type") == "OUTPUT" and response.get("input_id") == input_id:
|
|
197
|
+
return response.get("result", "")
|
|
198
|
+
elif response.get("type") == "ERROR":
|
|
199
|
+
raise ConnectionError(f"Agent error: {response.get('error')}")
|
|
200
|
+
else:
|
|
201
|
+
raise ConnectionError(f"Unexpected response: {response}")
|
|
202
|
+
|
|
203
|
+
async def _send_task(self, prompt: str, timeout: float) -> str:
|
|
204
|
+
"""
|
|
205
|
+
Send task using best available connection method.
|
|
206
|
+
|
|
207
|
+
Priority:
|
|
208
|
+
1. Cached endpoint (if previously successful)
|
|
209
|
+
2. Discovered HTTPS endpoints
|
|
210
|
+
3. Discovered HTTP endpoints
|
|
211
|
+
4. Relay fallback
|
|
212
|
+
"""
|
|
213
|
+
# Try cached endpoint first
|
|
214
|
+
if self._cached_endpoint:
|
|
215
|
+
try:
|
|
216
|
+
return await self._send_http(self._cached_endpoint, prompt, timeout)
|
|
217
|
+
except Exception:
|
|
218
|
+
self._cached_endpoint = None # Clear failed cache
|
|
219
|
+
|
|
220
|
+
# Discover endpoints
|
|
221
|
+
endpoints = await self._discover_endpoints()
|
|
222
|
+
|
|
223
|
+
# Sort: HTTPS first, then HTTP
|
|
224
|
+
endpoints.sort(key=lambda e: (0 if e.startswith("https://") else 1))
|
|
225
|
+
|
|
226
|
+
# Try each endpoint
|
|
227
|
+
for endpoint in endpoints:
|
|
228
|
+
try:
|
|
229
|
+
result = await self._send_http(endpoint, prompt, timeout)
|
|
230
|
+
self._cached_endpoint = endpoint # Cache successful endpoint
|
|
231
|
+
return result
|
|
232
|
+
except Exception:
|
|
233
|
+
continue
|
|
234
|
+
|
|
235
|
+
# Fallback to relay
|
|
236
|
+
return await self._send_relay(prompt, timeout)
|
|
237
|
+
|
|
238
|
+
def __repr__(self):
|
|
239
|
+
short = self.address[:12] + "..." if len(self.address) > 12 else self.address
|
|
240
|
+
return f"RemoteAgent({short})"
|
|
241
|
+
|
|
242
|
+
|
|
243
|
+
def connect(
|
|
244
|
+
address: str,
|
|
245
|
+
*,
|
|
246
|
+
keys: Optional[Dict[str, Any]] = None,
|
|
247
|
+
relay_url: str = "wss://oo.openonion.ai/ws/announce"
|
|
248
|
+
) -> RemoteAgent:
|
|
249
|
+
"""
|
|
250
|
+
Connect to a remote agent.
|
|
251
|
+
|
|
252
|
+
Args:
|
|
253
|
+
address: Agent's public key address (0x...)
|
|
254
|
+
keys: Signing keys from address.load() - required for strict trust agents
|
|
255
|
+
relay_url: Relay server URL (default: production)
|
|
256
|
+
|
|
257
|
+
Returns:
|
|
258
|
+
RemoteAgent interface
|
|
259
|
+
|
|
260
|
+
Example:
|
|
261
|
+
>>> from connectonion import connect, address
|
|
262
|
+
>>>
|
|
263
|
+
>>> # Simple (unsigned)
|
|
264
|
+
>>> agent = connect("0x3d4017c3...")
|
|
265
|
+
>>> result = agent.input("Hello")
|
|
266
|
+
>>>
|
|
267
|
+
>>> # With signing (for strict trust agents)
|
|
268
|
+
>>> keys = address.load(Path(".co"))
|
|
269
|
+
>>> agent = connect("0x3d4017c3...", keys=keys)
|
|
270
|
+
>>> result = agent.input("Hello")
|
|
271
|
+
"""
|
|
272
|
+
return RemoteAgent(address, keys=keys, relay_url=relay_url)
|
|
@@ -368,7 +368,12 @@ def admin_logs_handler(agent_name: str) -> dict:
|
|
|
368
368
|
|
|
369
369
|
|
|
370
370
|
def admin_sessions_handler() -> dict:
|
|
371
|
-
"""GET /admin/sessions - return
|
|
371
|
+
"""GET /admin/sessions - return raw session YAML files as JSON.
|
|
372
|
+
|
|
373
|
+
Returns session files as-is (converted from YAML to JSON). Each session
|
|
374
|
+
contains: name, created, updated, total_cost, total_tokens, turns array.
|
|
375
|
+
Frontend handles the display logic.
|
|
376
|
+
"""
|
|
372
377
|
import yaml
|
|
373
378
|
sessions_dir = Path(".co/sessions")
|
|
374
379
|
if not sessions_dir.exists():
|
|
@@ -381,8 +386,8 @@ def admin_sessions_handler() -> dict:
|
|
|
381
386
|
if session_data:
|
|
382
387
|
sessions.append(session_data)
|
|
383
388
|
|
|
384
|
-
# Sort by
|
|
385
|
-
sessions.sort(key=lambda s: s.get("created", ""), reverse=True)
|
|
389
|
+
# Sort by updated date descending (newest first)
|
|
390
|
+
sessions.sort(key=lambda s: s.get("updated", s.get("created", "")), reverse=True)
|
|
386
391
|
return {"sessions": sessions}
|
|
387
392
|
|
|
388
393
|
|
|
@@ -366,6 +366,70 @@
|
|
|
366
366
|
</div>
|
|
367
367
|
</details>
|
|
368
368
|
|
|
369
|
+
<!-- Admin Endpoints Header -->
|
|
370
|
+
<div style="margin:24px 0 12px;padding:12px 16px;background:var(--panel);border:1px solid var(--panel-border);border-radius:4px;">
|
|
371
|
+
<div class="row between">
|
|
372
|
+
<div>
|
|
373
|
+
<h3 style="margin:0;font-size:14px;">Admin Endpoints</h3>
|
|
374
|
+
<p style="margin:4px 0 0;font-size:12px;color:var(--muted);">Requires OPENONION_API_KEY authorization</p>
|
|
375
|
+
</div>
|
|
376
|
+
<div class="row" style="gap:8px;">
|
|
377
|
+
<label for="api-key" style="font-size:12px;margin:0;">API Key:</label>
|
|
378
|
+
<input type="password" id="api-key" placeholder="Enter API key..." style="width:200px;height:36px;font-size:12px;" />
|
|
379
|
+
</div>
|
|
380
|
+
</div>
|
|
381
|
+
</div>
|
|
382
|
+
|
|
383
|
+
<!-- GET /admin/logs -->
|
|
384
|
+
<details class="endpoint">
|
|
385
|
+
<summary class="endpoint-header">
|
|
386
|
+
<span class="method get">GET</span>
|
|
387
|
+
<span class="endpoint-path">/admin/logs</span>
|
|
388
|
+
<span class="endpoint-desc">Get agent activity logs (auth required)</span>
|
|
389
|
+
<span class="endpoint-expand">▾</span>
|
|
390
|
+
</summary>
|
|
391
|
+
<div class="endpoint-body">
|
|
392
|
+
<button class="btn execute" onclick="fetchAdminLogs()">Execute</button>
|
|
393
|
+
<div class="endpoint-section" style="margin-top:12px;">
|
|
394
|
+
<h4>Curl</h4>
|
|
395
|
+
<div class="curl-box">
|
|
396
|
+
<pre id="admin-logs-curl">curl -X GET "http://localhost:8000/admin/logs" \
|
|
397
|
+
-H "Authorization: Bearer YOUR_API_KEY"</pre>
|
|
398
|
+
<button class="copy-btn" onclick="copyCurl('admin-logs-curl')">Copy</button>
|
|
399
|
+
</div>
|
|
400
|
+
</div>
|
|
401
|
+
<div class="endpoint-section">
|
|
402
|
+
<h4>Response</h4>
|
|
403
|
+
<pre id="admin-logs-result" style="max-height:400px;">{ }</pre>
|
|
404
|
+
</div>
|
|
405
|
+
</div>
|
|
406
|
+
</details>
|
|
407
|
+
|
|
408
|
+
<!-- GET /admin/sessions -->
|
|
409
|
+
<details class="endpoint">
|
|
410
|
+
<summary class="endpoint-header">
|
|
411
|
+
<span class="method get">GET</span>
|
|
412
|
+
<span class="endpoint-path">/admin/sessions</span>
|
|
413
|
+
<span class="endpoint-desc">Get all activity sessions (auth required)</span>
|
|
414
|
+
<span class="endpoint-expand">▾</span>
|
|
415
|
+
</summary>
|
|
416
|
+
<div class="endpoint-body">
|
|
417
|
+
<button class="btn execute" onclick="fetchAdminSessions()">Execute</button>
|
|
418
|
+
<div class="endpoint-section" style="margin-top:12px;">
|
|
419
|
+
<h4>Curl</h4>
|
|
420
|
+
<div class="curl-box">
|
|
421
|
+
<pre id="admin-sessions-curl">curl -X GET "http://localhost:8000/admin/sessions" \
|
|
422
|
+
-H "Authorization: Bearer YOUR_API_KEY"</pre>
|
|
423
|
+
<button class="copy-btn" onclick="copyCurl('admin-sessions-curl')">Copy</button>
|
|
424
|
+
</div>
|
|
425
|
+
</div>
|
|
426
|
+
<div class="endpoint-section">
|
|
427
|
+
<h4>Response</h4>
|
|
428
|
+
<pre id="admin-sessions-result" style="max-height:400px;">{ }</pre>
|
|
429
|
+
</div>
|
|
430
|
+
</div>
|
|
431
|
+
</details>
|
|
432
|
+
|
|
369
433
|
<!-- WebSocket /ws -->
|
|
370
434
|
<details class="endpoint">
|
|
371
435
|
<summary class="endpoint-header">
|
|
@@ -580,6 +644,39 @@
|
|
|
580
644
|
loadInfo(); refreshSessions();
|
|
581
645
|
}
|
|
582
646
|
|
|
647
|
+
async function fetchAdminLogs(){
|
|
648
|
+
const apiKey = $('api-key').value.trim();
|
|
649
|
+
if (!apiKey) {
|
|
650
|
+
$('admin-logs-result').textContent = '{"error": "Please enter API key"}';
|
|
651
|
+
return;
|
|
652
|
+
}
|
|
653
|
+
$('admin-logs-result').textContent = 'Loading...';
|
|
654
|
+
const res = await fetch(buildUrl('/admin/logs'), {
|
|
655
|
+
headers: { 'Authorization': 'Bearer ' + apiKey }
|
|
656
|
+
});
|
|
657
|
+
if (res.ok) {
|
|
658
|
+
const text = await res.text();
|
|
659
|
+
$('admin-logs-result').textContent = text || '(empty)';
|
|
660
|
+
} else {
|
|
661
|
+
const data = await res.json().catch(() => ({ error: res.statusText }));
|
|
662
|
+
$('admin-logs-result').textContent = JSON.stringify(data, null, 2);
|
|
663
|
+
}
|
|
664
|
+
}
|
|
665
|
+
|
|
666
|
+
async function fetchAdminSessions(){
|
|
667
|
+
const apiKey = $('api-key').value.trim();
|
|
668
|
+
if (!apiKey) {
|
|
669
|
+
$('admin-sessions-result').textContent = '{"error": "Please enter API key"}';
|
|
670
|
+
return;
|
|
671
|
+
}
|
|
672
|
+
$('admin-sessions-result').textContent = 'Loading...';
|
|
673
|
+
const res = await fetch(buildUrl('/admin/sessions'), {
|
|
674
|
+
headers: { 'Authorization': 'Bearer ' + apiKey }
|
|
675
|
+
});
|
|
676
|
+
const data = await res.json();
|
|
677
|
+
$('admin-sessions-result').textContent = JSON.stringify(data, null, 2);
|
|
678
|
+
}
|
|
679
|
+
|
|
583
680
|
$('prompt').addEventListener('input', updatePayloadPreview);
|
|
584
681
|
$('from').addEventListener('input', updateCurlCommands);
|
|
585
682
|
$('signature').addEventListener('input', updateCurlCommands);
|