Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
22 changes: 22 additions & 0 deletions connectonion/cli/main.py
Original file line number Diff line number Diff line change
Expand Up @@ -85,7 +85,9 @@ def init(
):
"""Initialize project in current directory."""
from .commands.init import handle_init
from .tips import show_tip
handle_init(ai=None, key=key, template=template, description=description, yes=yes, force=force)
show_tip("init")


@app.command()
Expand All @@ -98,19 +100,24 @@ def create(
):
"""Create new project."""
from .commands.create import handle_create
from .tips import show_tip
handle_create(name=name, ai=None, key=key, template=template, description=description, yes=yes)
show_tip("create")


@app.command()
def deploy():
"""Deploy to ConnectOnion Cloud."""
from .commands.deploy_commands import handle_deploy
from .tips import show_tip
handle_deploy()
show_tip("deploy")


@app.command()
def auth(service: Optional[str] = typer.Argument(None, help="Service: google, microsoft")):
"""Authenticate with OpenOnion."""
from .tips import show_tip
if service == "google":
from .commands.auth_commands import handle_google_auth
handle_google_auth()
Expand All @@ -120,6 +127,7 @@ def auth(service: Optional[str] = typer.Argument(None, help="Service: google, mi
else:
from .commands.auth_commands import handle_auth
handle_auth()
show_tip("auth")


@app.command()
Expand All @@ -128,14 +136,18 @@ def keys(
):
"""Show agent keys and credentials."""
from .commands.keys_commands import handle_keys
from .tips import show_tip
handle_keys(reveal=reveal)
show_tip("keys")


@app.command()
def status():
"""Check account status."""
from .commands.status_commands import handle_status
from .tips import show_tip
handle_status()
show_tip("status")


@app.command()
Expand All @@ -149,14 +161,18 @@ def reset():
def doctor():
"""Diagnose installation."""
from .commands.doctor_commands import handle_doctor
from .tips import show_tip
handle_doctor()
show_tip("doctor")


@app.command()
def browser(command: str = typer.Argument(..., help="Browser command")):
"""Browser automation."""
from .commands.browser_commands import handle_browser
from .tips import show_tip
handle_browser(command)
show_tip("browser")


@app.command()
Expand All @@ -168,7 +184,9 @@ def ai(
):
"""Start AI coding agent or run one-shot prompt."""
from .commands.ai_commands import handle_ai
from .tips import show_tip
handle_ai(prompt=prompt, port=port, model=model, max_iterations=max_iterations)
show_tip("ai")


@app.command()
Expand All @@ -180,7 +198,9 @@ def copy(
):
"""Copy built-in tools/plugins to customize."""
from .commands.copy_commands import handle_copy
from .tips import show_tip
handle_copy(names=names or [], list_all=list_all, path=path, force=force)
show_tip("copy")


@app.command()
Expand All @@ -190,7 +210,9 @@ def eval(
):
"""Run evals and show results."""
from .commands.eval_commands import handle_eval
from .tips import show_tip
handle_eval(name=name, agent_file=agent)
show_tip("eval")


# Trust command group
Expand Down
166 changes: 166 additions & 0 deletions connectonion/cli/tips.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,166 @@
import json
import random
from pathlib import Path
from rich.console import Console

console = Console()

TIPS = [
{
"text": "Use @xray decorator on any tool to pause and inspect agent state during execution.",
"link": "docs.connectonion.com/xray",
"context": ["create", "init"],
},
{
"text": 'Try model="co/gemini-2.5-pro" for free managed LLM access — no API key needed.',
"link": "docs.connectonion.com/models",
"context": ["auth", "status"],
},
{
"text": "Add type hints to tool functions for better LLM schema generation.\n def search(query: str, limit: int = 10) -> list: ...",
"link": "docs.connectonion.com/tools",
"context": ["create", "init"],
},
{
"text": "Use host() and connect() to build multi-agent systems that collaborate.\n from connectonion.network import host, connect",
"link": "docs.connectonion.com/network",
"context": ["create", "init", "deploy"],
},
{
"text": "The event system lets you hook into agent lifecycle: after_llm, after_tools, on_complete.\n agent = Agent('name', on_events=[after_tools(my_handler)])",
"link": "docs.connectonion.com/events",
"context": ["create", "init"],
},
{
"text": "Plugins bundle event handlers together — try the built-in reflection plugin.\n from connectonion.useful_plugins import reflection",
"link": "docs.connectonion.com/plugins",
"context": ["create", "init"],
},
{
"text": "co browser launches a browser automation agent. Try: co -b 'find flights to Tokyo'",
"link": "docs.connectonion.com/browser",
"context": ["create", "status", "auth"],
},
{
"text": "Use co/ prefix models for managed keys: model='co/gemini-2.5-flash' for fast, cheap tasks.",
"link": "docs.connectonion.com/models",
"context": ["status", "auth", "keys"],
},
{
"text": "Join our Discord for help, tips, and to share your agents.\n discord.gg/4xfD9k8AUF",
"link": "discord.gg/4xfD9k8AUF",
"context": [],
},
{
"text": "Add connect.py integrations for Gmail/Calendar with: co auth google",
"link": "docs.connectonion.com/email",
"context": ["auth", "create", "init"],
},
{
"text": "Use quiet=True to suppress console output for background agents.\n agent = Agent('name', quiet=True)",
"link": "docs.connectonion.com/logging",
"context": ["create", "init"],
},
{
"text": "llm_do() runs a one-shot LLM call without creating a full agent.\n from connectonion import llm_do",
"link": "docs.connectonion.com/llm-do",
"context": ["create", "init"],
},
{
"text": "Use agent.receive_all() to iterate over streaming events from a remote agent.",
"link": "docs.connectonion.com/network",
"context": ["create", "deploy"],
},
{
"text": "co copy <tool> copies built-in tool source to your project for customization.\n co copy send_email",
"link": "docs.connectonion.com/tools",
"context": ["create", "init", "copy"],
},
{
"text": "Set max_iterations on your agent to control how long it runs.\n agent = Agent('name', max_iterations=50)",
"link": "docs.connectonion.com/agent",
"context": ["create", "init"],
},
{
"text": "Use structured_complete() to get typed Pydantic output from LLMs.\n llm.structured_complete(messages, MySchema)",
"link": "docs.connectonion.com/llm",
"context": ["create", "init"],
},
{
"text": "Sessions are saved to .co/evals/ as YAML for debugging and replaying.",
"link": "docs.connectonion.com/sessions",
"context": ["eval", "status"],
},
{
"text": "co eval runs your eval files and shows pass/fail results.",
"link": "docs.connectonion.com/eval",
"context": ["eval"],
},
{
"text": "Use trust='careful' in host() to require signature verification from remote agents.",
"link": "docs.connectonion.com/trust",
"context": ["deploy", "create"],
},
{
"text": "Full docs at docs.connectonion.com — includes tutorials, API reference, and examples.",
"link": "docs.connectonion.com",
"context": [],
},
]

_TIPS_SEEN_FILE = Path.home() / ".co" / "tips_seen.json"
_CONFIG_FILE = Path.home() / ".co" / "config.toml"


def _tips_enabled() -> bool:
if not _CONFIG_FILE.exists():
return True
import toml
config = toml.load(_CONFIG_FILE)
return config.get("cli", {}).get("tips", True)


def _load_seen() -> list:
if not _TIPS_SEEN_FILE.exists():
return []
return json.loads(_TIPS_SEEN_FILE.read_text())


def _save_seen(seen: list):
_TIPS_SEEN_FILE.parent.mkdir(parents=True, exist_ok=True)
_TIPS_SEEN_FILE.write_text(json.dumps(seen))


def show_tip(command: str):
if not _tips_enabled():
return

seen = _load_seen()

# Find tips matching this command context, preferring unseen ones
contextual = [t for t in TIPS if command in t["context"]]
universal = [t for t in TIPS if not t["context"]]
candidates = contextual + universal

# Filter unseen; if all seen, reset rotation
unseen = [t for t in candidates if t["text"] not in seen]
if not unseen:
# All seen - reset and show any
seen = []
unseen = candidates

if not unseen:
return

tip = random.choice(unseen)
seen.append(tip["text"])

# Keep seen list bounded
if len(seen) > len(TIPS):
seen = seen[-len(TIPS):]

_save_seen(seen)

console.print()
console.print(f"[bold yellow]💡 Tip:[/bold yellow] {tip['text']}")
console.print(f" [dim]Learn more: {tip['link']}[/dim]")