Skip to content
Merged
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
2 changes: 1 addition & 1 deletion Cowork/cowork_session.py
Original file line number Diff line number Diff line change
Expand Up @@ -56,7 +56,7 @@ def _load_settings():
log = structlog.get_logger()

BANNER = """
╔═════════ Sicily Cowork v2.5.6 ════════════╦════════════════ What Sicily Can Do ════════════════╗
╔═════════ Sicily Cowork v2.5.7 ════════════╦════════════════ What Sicily Can Do ════════════════╗
║ ║ ║
║ ║ Sicily can search, inspect, read, organize, ║
║ Files are sandboxed to this directory. ║ and safely modify the contents of your ║
Expand Down
17 changes: 12 additions & 5 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -121,6 +121,14 @@ It walks backward through the message history to find a safe cut point — speci

---

### Sicily via Telegram

The Telegram interface where the agent lives day-to-day — chat naturally, and Sicily handles context, tools, and approvals behind the scenes.

<img src="Images/telegram_interface.png" alt="Sicily via Telegram" width="85%">

---

### Cowork — Local File Intelligence from the Terminal

`sicily start` launches Sicily as a local terminal assistant, sandboxed to whichever directory you run it from. No Telegram, no server, no scheduled tasks — just you and your files.
Expand All @@ -137,13 +145,14 @@ Sicily indexes your files at startup using a **hybrid RAG pipeline** (TF-IDF key
- Search across all your files by meaning, not just filenames
- Create new text files and directories
- Edit existing files line-by-line (with a dry-run preview before any change is applied)
- Copy, move, rename, and delete files (deletes go to a sandbox-local trash folder, never removed outright)
- Pin frequently referenced paths so they survive context summarisation

**What it will never do:**

- Overwrite or delete existing files
- Permanently delete a file without first moving it to trash
- Access paths outside the directory you started it in
- Apply edits without showing you a preview first
- Apply edits or destructive actions without showing you a preview first

The sandbox is enforced at the path level — every tool call resolves its target path against the root and rejects anything that would escape it, including symlink traversals.

Expand Down Expand Up @@ -206,8 +215,6 @@ sicily run

Starts the full agent: FastAPI backend, Telegram listener, session manager, and recurring task scheduler. Connect your Telegram bot and start chatting.

<img src="Images/telegram_interface.png" alt="Sicily via Telegram" width="85%">

#### As a Local Terminal Assistant (Cowork)

```bash
Expand Down Expand Up @@ -251,4 +258,4 @@ Type `exit` or `quit` to end the session.

**Scheduled tasks:** Edit `~/.sicily/Recurring_Tasks/recurring_tasks.yaml`. Set `enabled: false` to pause a task, or add new entries — no restart required on next run.

**Preferences:** Sicily builds these automatically over time. They live in `~/.sicily/Context/preferences.md` and can be edited manually if needed.
**Preferences:** Sicily builds these automatically over time. They live in `~/.sicily/Context/preferences.md` and can be edited manually if needed.
34 changes: 0 additions & 34 deletions Tests/test_connectors.py

This file was deleted.

42 changes: 42 additions & 0 deletions Tests/test_keys.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
import json
from pathlib import Path
# Import the single source of truth from your configuration script
from configuration import REQUIRED_KEYS


def test_settings_template_matches_required_keys():
"""
Validates that the keys defined in settings.example.json exactly match
the REQUIRED_KEYS listed in configuration.py.
"""
# Locating settings.example.json (one level up from the Tests/ directory)
test_dir = Path(__file__).resolve().parent
example_json_path = test_dir.parent / "settings.example.json"

# 1. Ensure the file actually exists where we expect it
assert example_json_path.exists(), f"Could not find settings.example.json at {example_json_path}"

# 2. Parse the keys from the template file
with open(example_json_path, "r") as f:
try:
example_data = json.load(f)
except json.JSONDecodeError:
assert False, f"settings.example.json is not valid JSON!"

template_keys = set(example_data.keys())
code_keys = set(REQUIRED_KEYS)

# 3. Find any discrepancies
missing_in_code = template_keys - code_keys
missing_in_template = code_keys - template_keys

# 4. Assertions with clear, helpful error messages
assert not missing_in_code, (
f"Discrepancy found! The following keys are in settings.example.json "
f"but are missing from REQUIRED_KEYS in configuration.py: {missing_in_code}"
)

assert not missing_in_template, (
f"Discrepancy found! The following keys are in REQUIRED_KEYS (configuration.py) "
f"but are missing from the settings.example.json template: {missing_in_template}"
)
4 changes: 3 additions & 1 deletion cli.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,8 @@
import subprocess
from pathlib import Path

from configuration import REQUIRED_KEYS

try:
from importlib.metadata import version as pkg_version
__version__ = pkg_version("sicily")
Expand All @@ -28,7 +30,7 @@ def ensure_initialized(required_keys=None):

# Default to all keys if none specified
if required_keys is None:
required_keys = ["OPENAI_API_KEY", "TELEGRAM_BOT_TOKEN", "TAVILY_API_KEY"]
required_keys = REQUIRED_KEYS

try:
with open(SETTINGS_PATH, "r") as f:
Expand Down
21 changes: 21 additions & 0 deletions connectors.py
Original file line number Diff line number Diff line change
Expand Up @@ -107,3 +107,24 @@ async def load_github_tools(tool_manager):
"tavily": load_tavily_tools,
"github": load_github_tools,
}

# Some connectors register more than one MCP server under the hood
# (e.g. "swiggy" spins up both "swiggy-food" and "swiggy-instamart").
# This maps a connector name -> the tool_manager server name(s) it owns,
# so /connect_*, /disconnect_*, and the "is this loaded?" check all stay
# correct without needing a special case anywhere else.
# Any connector not listed here is assumed to register a server with the
# same name as the connector itself (the common case).
CONNECTOR_SERVERS = {
"swiggy": ["swiggy-food", "swiggy-instamart"],
}


def get_connector_servers(name: str) -> list[str]:
"""Server name(s) a given connector registers with the tool_manager."""
return CONNECTOR_SERVERS.get(name, [name])


def is_connector_loaded(name: str, loaded_servers) -> bool:
"""True if any server belonging to this connector is currently loaded."""
return any(server in loaded_servers for server in get_connector_servers(name))
2 changes: 1 addition & 1 deletion pyproject.toml
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ build-backend = "hatchling.build"

[project]
name = "sicily"
version = "2.5.6"
version = "2.5.7"
description = "Add your description here"
readme = "README.md"
requires-python = ">=3.12"
Expand Down
Loading
Loading