Skip to content
Open
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
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -125,4 +125,5 @@ docs/.yarn/*

uv.lock

tmp/
*.csv
26 changes: 17 additions & 9 deletions Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -63,6 +63,12 @@ codeai: # codeai
AWS_DEFAULT_REGION=${DATALAYER_BEDROCK_AWS_DEFAULT_REGION} \
codeai --eggs

codeai-simple: # codeai-simple
@AWS_ACCESS_KEY_ID=${DATALAYER_BEDROCK_AWS_ACCESS_KEY_ID} \
AWS_SECRET_ACCESS_KEY=${DATALAYER_BEDROCK_AWS_SECRET_ACCESS_KEY} \
AWS_DEFAULT_REGION=${DATALAYER_BEDROCK_AWS_DEFAULT_REGION} \
codeai --eggs --agentspec-id codeai/simple

codeai-data-acquisition: # codeai-data-acquisition KAGGLE_TOKEN and TAVILY_API_KEY must be set in env
@AWS_ACCESS_KEY_ID=${DATALAYER_BEDROCK_AWS_ACCESS_KEY_ID} \
AWS_SECRET_ACCESS_KEY=${DATALAYER_BEDROCK_AWS_SECRET_ACCESS_KEY} \
Expand All @@ -75,23 +81,25 @@ codeai-financial: # codeai-financial ALPHA_VANTAGE_API_KEY must be set in env
AWS_DEFAULT_REGION=${DATALAYER_BEDROCK_AWS_DEFAULT_REGION} \
codeai --eggs --agentspec-id datalayer-ai/financial

###########
# Prompts #
###########
# List files located in the sales-data folder of my Google Drive account (eric@datalayer.io).
# Aggregate all CSV files located in the sales-data folder of my Google Drive account (eric@datalayer.io) into a single file named sales_21-25.csv, and save this aggregated file in the sales-data directory of the echarles/openteams-codemode-demo repository.
codeai-openteams-demo: # codeai-openteams-demo
codeai-demo: # codeai-demo
@AWS_ACCESS_KEY_ID=${DATALAYER_BEDROCK_AWS_ACCESS_KEY_ID} \
AWS_SECRET_ACCESS_KEY=${DATALAYER_BEDROCK_AWS_SECRET_ACCESS_KEY} \
AWS_DEFAULT_REGION=${DATALAYER_BEDROCK_AWS_DEFAULT_REGION} \
GOOGLE_OAUTH_CLIENT_ID=${OPENTEAMS_DEMO_GOOGLE_CLIENT_ID} \
GOOGLE_OAUTH_CLIENT_SECRET=${OPENTEAMS_DEMO_GOOGLE_CLIENT_SECRET} \
codeai --eggs --agentspec-id codemode-paper/information-routing
codeai \
--eggs \
--suggestions "List files located in the sales-data folder of my Google Drive account (eric@datalayer.io),Aggregate all CSV files located in the sales-data folder of my Google Drive account (eric@datalayer.io) into a single file named sales_21-25.csv and save this aggregated file in the sales-data directory of the echarles/openteams-codemode-demo repository." \
--agentspec-id codemode-paper/information-routing

codeai-openteams-demo-nocodemode: # codeai-openteams-demo-codemode
codeai-demo-nocodemode: # codeai-demo-nocodemode
@AWS_ACCESS_KEY_ID=${DATALAYER_BEDROCK_AWS_ACCESS_KEY_ID} \
AWS_SECRET_ACCESS_KEY=${DATALAYER_BEDROCK_AWS_SECRET_ACCESS_KEY} \
AWS_DEFAULT_REGION=${DATALAYER_BEDROCK_AWS_DEFAULT_REGION} \
GOOGLE_OAUTH_CLIENT_ID=${OPENTEAMS_DEMO_GOOGLE_CLIENT_ID} \
GOOGLE_OAUTH_CLIENT_SECRET=${OPENTEAMS_DEMO_GOOGLE_CLIENT_SECRET} \
codeai --eggs --agentspec-id codemode-paper/information-routing --no-codemode
codeai \
--eggs \
--agentspec-id codemode-paper/information-routing \
--suggestions "List files located in the sales-data folder of my Google Drive account (eric@datalayer.io),Aggregate all CSV files located in the sales-data folder of my Google Drive account (eric@datalayer.io) into a single file named sales_21-25.csv and save this aggregated file in the sales-data directory of the echarles/openteams-codemode-demo repository." \
--no-codemode
2 changes: 1 addition & 1 deletion codeai/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@
- CLI interface for AI-powered code assistance
- ACP (Agent Communication Protocol) client for remote agent connections
- Interactive chat mode with local and remote agents
- Terminal UX (TUX) with Claude Code-style interface
- Terminal UX (TUX)
"""

from codeai.cli import agent, main
Expand Down
9 changes: 8 additions & 1 deletion codeai/cli.py
Original file line number Diff line number Diff line change
Expand Up @@ -587,6 +587,12 @@ def main_callback(
"--no-codemode",
help="Disable codemode (MCP tools as programmatic tools)"
),
suggestions: Optional[str] = typer.Option(
None,
"--suggestions",
"-s",
help="Extra suggestions to add (comma-separated), e.g. 'Search for X,Summarize Y'"
),
eggs: bool = typer.Option(
False,
"--eggs",
Expand Down Expand Up @@ -739,7 +745,8 @@ def main_callback(
try:
# Use Rich-based TUX
from .tux import run_tux
asyncio.run(run_tux(url, server_url, agent_id="codeai", eggs=eggs, jupyter_url=jupyter_url))
extra_suggestions = [s.strip() for s in suggestions.split(",") if s.strip()] if suggestions else []
asyncio.run(run_tux(url, server_url, agent_id="codeai", eggs=eggs, jupyter_url=jupyter_url, extra_suggestions=extra_suggestions))
finally:
_cleanup_subprocess()
else:
Expand Down
124 changes: 124 additions & 0 deletions codeai/commands/__init__.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,124 @@
# Copyright (c) 2025-2026 Datalayer, Inc.
#
# BSD 3-Clause License

"""Commands package - one file per slash command.

Each command module exports:
NAME: str - primary command name
ALIASES: list[str] - alternative names
DESCRIPTION: str - help text
SHORTCUT: Optional[str] - keyboard shortcut (e.g., "escape x")
execute(tux) -> Optional[str] - async handler, returns optional next prompt
"""

from __future__ import annotations

from dataclasses import dataclass, field
from typing import Callable, Optional, TYPE_CHECKING

if TYPE_CHECKING:
from ..tux import CodeAITux


@dataclass
class SlashCommand:
"""Definition of a slash command."""
name: str
aliases: list[str] = field(default_factory=list)
description: str = ""
handler: Optional[Callable] = None
shortcut: Optional[str] = None # e.g., "escape x" for Esc, X


def build_commands(
tux: "CodeAITux",
eggs: bool = False,
jupyter_url: Optional[str] = None,
) -> dict[str, SlashCommand]:
"""Build all slash commands, binding handlers to the tux instance.

Args:
tux: The CodeAITux instance.
eggs: Enable Easter egg commands.
jupyter_url: Jupyter URL (enables /jupyter command when set).

Returns:
Dict mapping command names (including aliases) to SlashCommand instances.
"""
from . import (
context,
clear,
help,
status,
exit,
agents,
tools,
mcp_servers,
skills,
codemode_toggle,
context_export,
tools_last,
cls,
browser,
browser_notebook,
browser_lexical,
suggestions,
)

# Core commands always registered
modules = [
context,
clear,
help,
status,
exit,
agents,
tools,
mcp_servers,
skills,
codemode_toggle,
context_export,
tools_last,
cls,
browser,
browser_notebook,
browser_lexical,
suggestions,
]

# Conditionally add egg commands
if eggs:
from . import rain, about, gif
modules.extend([rain, about, gif])

# Conditionally add jupyter command
if jupyter_url:
from . import jupyter
modules.append(jupyter)

commands: dict[str, SlashCommand] = {}

for mod in modules:
# Create handler closure that captures tux
handler = _make_handler(mod.execute, tux)

cmd = SlashCommand(
name=mod.NAME,
aliases=getattr(mod, "ALIASES", []),
description=getattr(mod, "DESCRIPTION", ""),
handler=handler,
shortcut=getattr(mod, "SHORTCUT", None),
)
commands[cmd.name] = cmd
for alias in cmd.aliases:
commands[alias] = cmd

return commands


def _make_handler(execute_fn: Callable, tux: "CodeAITux") -> Callable:
"""Create a handler closure that passes tux to the execute function."""
async def handler():
return await execute_fn(tux)
return handler
24 changes: 24 additions & 0 deletions codeai/commands/about.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
# Copyright (c) 2025-2026 Datalayer, Inc.
#
# BSD 3-Clause License

"""Slash command: /about - About Datalayer animation (Easter egg)."""

from __future__ import annotations

from typing import Optional, TYPE_CHECKING

if TYPE_CHECKING:
from ..tux import CodeAITux

NAME = "about"
ALIASES: list[str] = []
DESCRIPTION = "About Datalayer"
SHORTCUT = "escape l"
Copy link

Copilot AI Feb 17, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Keyboard shortcut conflict: Both /about (line 17 in about.py) and /tools-last (line 18 in tools_last.py) are assigned the same shortcut "escape l". This causes only one command to be reachable via the keyboard shortcut. The shortcut registration logic in tux.py (line 314-319) uses a dictionary where shortcuts are keys, so the second command with the same shortcut will overwrite the first. Consider changing one of these shortcuts to a different key combination.

Suggested change
SHORTCUT = "escape l"
SHORTCUT = "escape a"

Copilot uses AI. Check for mistakes.


async def execute(tux: "CodeAITux") -> Optional[str]:
"""Display About Datalayer animation."""
from ..animations import about_animation
await about_animation(tux.console)
return None
101 changes: 101 additions & 0 deletions codeai/commands/agents.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,101 @@
# Copyright (c) 2025-2026 Datalayer, Inc.
#
# BSD 3-Clause License

"""Slash command: /agents - List available agents."""

from __future__ import annotations

from typing import Optional, TYPE_CHECKING

import httpx

if TYPE_CHECKING:
from ..tux import CodeAITux

NAME = "agents"
ALIASES: list[str] = []
DESCRIPTION = "List available agents on the server"
SHORTCUT = "escape a"


async def execute(tux: "CodeAITux") -> Optional[str]:
"""List available agents with detailed information."""
from ..tux import STYLE_PRIMARY, STYLE_ACCENT, STYLE_MUTED

try:
async with httpx.AsyncClient() as client:
url = f"{tux.server_url}/api/v1/agents"
response = await client.get(url, timeout=10.0)
response.raise_for_status()
data = response.json()
except Exception as e:
tux.console.print(f"[red]Error fetching agents: {e}[/red]")
return None

agents_list = data.get("agents", [])

if not agents_list:
tux.console.print("No agents available", style=STYLE_MUTED)
return None

tux.console.print()
tux.console.print(f"● Available Agents ({len(agents_list)}):", style=STYLE_PRIMARY)
tux.console.print()

for agent in agents_list:
agent_id = agent.get("id", "unknown")
name = agent.get("name", "Unknown")
description = agent.get("description", "")
model = agent.get("model", "unknown")
status = agent.get("status", "unknown")
toolsets = agent.get("toolsets", {})

# Status indicator
status_icon = "[green]●[/green]" if status == "running" else "[red]○[/red]"
tux.console.print(f" {status_icon} {name} ({agent_id})", style=STYLE_ACCENT)

# Description
if description:
desc = description[:60] + "..." if len(description) > 60 else description
tux.console.print(f" {desc}", style=STYLE_MUTED)

# Model
tux.console.print(f" Model: {model}", style=STYLE_MUTED)

# Codemode
codemode = toolsets.get("codemode", False)
codemode_text = "enabled" if codemode else "disabled"
codemode_style = STYLE_ACCENT if codemode else STYLE_MUTED
tux.console.print(f" Codemode: ", style=STYLE_MUTED, end="")
tux.console.print(codemode_text, style=codemode_style)

# MCP Servers
mcp_servers = toolsets.get("mcp_servers", [])
if mcp_servers:
mcp_text = ", ".join(mcp_servers[:5])
if len(mcp_servers) > 5:
mcp_text += f" (+{len(mcp_servers) - 5} more)"
tux.console.print(f" MCP Servers: {mcp_text}", style=STYLE_MUTED)

# Tools count
tools_count = toolsets.get("tools_count", 0)
if tools_count > 0:
tux.console.print(f" Tools: {tools_count}", style=STYLE_MUTED)

# Skills
skills = toolsets.get("skills", [])
if skills:
skill_names = []
for s in skills[:3]:
if isinstance(s, dict):
skill_names.append(s.get("name", "?"))
else:
skill_names.append(str(s))
skills_text = ", ".join(skill_names)
if len(skills) > 3:
skills_text += f" (+{len(skills) - 3} more)"
tux.console.print(f" Skills: {skills_text}", style=STYLE_MUTED)

tux.console.print()
return None
26 changes: 26 additions & 0 deletions codeai/commands/browser.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
# Copyright (c) 2025-2026 Datalayer, Inc.
#
# BSD 3-Clause License

"""Slash command: /browser - Open the Agent chat UI in the browser."""

from __future__ import annotations

import webbrowser
from typing import Optional, TYPE_CHECKING

if TYPE_CHECKING:
from ..tux import CodeAITux

NAME = "browser"
ALIASES: list[str] = []
DESCRIPTION = "Open the Agent chat UI in your browser"
SHORTCUT = "escape w"


async def execute(tux: "CodeAITux") -> Optional[str]:
"""Open the Agent chat web UI in the default browser."""
url = f"{tux.server_url}/static/agent.html?agentId={tux.agent_id}"
tux.console.print(f" Opening [bold cyan]{url}[/bold cyan]")
webbrowser.open(url)
return None
Loading