Phillip Tarrant 5b5c3098bb fix: remove cat from allowed shell commands and fix stale display tests
Remove cat from allowed_commands to close shell redirect bypass vector
(read_file provides safer alternative). Update display tests to match
render_user_message returning Text instead of Panel, and shell test to
use head instead of cat.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-11 21:14:21 -05:00
2026-03-11 07:21:21 -05:00
2026-03-11 12:40:59 -05:00

SneakyCode

A privacy-first, locally-running Python coding agent that uses a local LLM (via Ollama) to perform autonomous coding tasks inside a project directory.

SneakyCode accepts natural language tasks and executes them using a defined toolset for filesystem operations, shell execution, code search, and file manipulation. It runs a ReAct-style tool-call loop: send conversation history to the LLM, receive tool calls, execute them with permission checks, and feed results back until the task is complete.

Prerequisites

  • Python 3.11+
  • Ollama running locally with a model that supports function calling (e.g., qwen3.5, llama3.1, mistral-nemo)
  • uv (recommended) or pip

Installation

# Clone the repository
git clone <repo-url>
cd SneakyCode

# Install dependencies
uv sync --dev

# Or with pip
pip install -e ".[dev]"

Configuration

Edit config/config.yaml to configure the agent. The full configuration reference:

llm:
  model: "qwen3.5:latest"              # Ollama model name
  endpoint: "http://localhost:11434"    # Ollama endpoint
  api_path: "/v1/chat/completions"     # API endpoint path
  temperature: 0.1                     # Sampling temperature
  max_tokens: 4096                     # Maximum tokens in LLM response
  timeout: 120                         # Request timeout in seconds
  max_retries: 3                       # Retry attempts on transient errors
  retry_backoff_base: 1.0              # Exponential backoff base (seconds)
  retry_backoff_max: 30.0              # Maximum backoff seconds

agent:
  max_iterations: 25                   # Max tool-call iterations per turn
  max_conversation_tokens: 32000       # Token budget for conversation
  workspace_root: "."                  # Project directory for file operations
  truncation_keep_recent: 10           # Messages preserved during truncation
  truncation_threshold: 0.85           # Budget fraction that triggers truncation

session:
  session_dir: ".sneakycode/sessions"  # Directory for session files
  auto_save: true                      # Save session after each turn
  max_session_age_hours: 72            # Auto-cleanup old sessions
  offer_resume: true                   # Offer to resume on startup

permissions:
  auto_approve: [read_file, list_dir, grep_files, find_files, finish]
  prompt_user: [write_file, delete_file, run_command, str_replace, patch_apply, make_dir]
  deny: []

tools:
  shell:
    allowed_commands:                   # Commands the LLM may run
      - git
      - python
      - pip
      - pytest
      - ruff
      - ls
      - cat
      - head
      - tail
      - wc
      - diff
      - grep
      - find
      - echo
    denied_commands:                    # Blocked commands
      - rm -rf /
      - sudo
      - curl
      - wget
    max_output_bytes: 65536            # Max captured output size (bytes)
  filesystem:
    max_file_size_bytes: 1048576       # 1 MB — max file size for read/write
    binary_detection: true             # Detect and reject binary files

display:
  show_tool_calls: true                # Show tool call details in output
  show_token_usage: true               # Show token usage stats
  stream_output: true                  # Stream LLM output to terminal

skills:
  enabled: true                        # Enable the skills system
  directories:                         # Directories to scan for skill files
    - ".sneakycode/skills"

debug:
  enabled: false                       # Enable debug logging
  log_dir: ".sneakycode/logs"          # Debug log directory
  max_files: 10                        # Max debug log files to retain

Environment variable SNEAKYCODE_CONFIG can override the config file path.

Usage

# Start the interactive TUI
sneakycode

# Open a specific project directory
sneakycode /path/to/project

# Or run directly
python -m app.main

# With options
sneakycode --config path/to/config.yaml --verbose --log-file sneakycode.log

CLI Options

Option Description
DIRECTORY Project directory to use as workspace root
--config PATH Path to config YAML file (default: config/config.yaml)
-v, --verbose Enable verbose (DEBUG) logging
--log-file PATH Path to log file for persistent logging

REPL Commands

Command Description
/help Show available commands
/quit Save session and exit (also /exit, /bye)
/history Show conversation history
/clear Clear conversation history
/save Manually save session
/session Show session info (messages, tokens, start time)
/models List available Ollama models
/models <name> Switch to a different model
/skills List available skills

Session Persistence

Sessions are automatically saved after each agent turn and on exit. On startup, SneakyCode offers to resume the most recent session for the current workspace.

Session files are stored in .sneakycode/sessions/ within the workspace root (configurable via session.session_dir).

Available Tools

SneakyCode provides tools across 6 categories. See docs/tools.md for the full reference.

Category Tools Permission
Read read_file, list_dir Auto-approved
Search grep_files, find_files Auto-approved
Write write_file, make_dir, delete_file User confirm
Edit str_replace, patch_apply User confirm
Shell run_command User confirm
Control finish Auto-approved
Skills load_skill Auto-approved

The load_skill tool is available when skills.enabled is true in the config. It allows the LLM to load skill instructions from the configured skill directories.

Skills

SneakyCode includes a skills system that lets you provide reusable instruction sets to the LLM. Skills are markdown files placed in .sneakycode/skills/ (or any directory listed in skills.directories).

Skills are auto-discovered on startup. The LLM can load them via the load_skill tool, and you can list available skills with the /skills command.

To create a skill, add a .md file to your skills directory with a descriptive filename (e.g., refactoring.md). The file content is injected into the conversation when the skill is loaded.

Development

# Run tests
.venv/bin/python -m pytest tests/ -v

# Run with coverage
.venv/bin/python -m pytest tests/ --cov=app

# Lint
.venv/bin/ruff check app/ tests/

# Format
.venv/bin/ruff format app/ tests/

Project Structure

app/
├── agent/           # Agent loop and session context
├── models/          # Pydantic config and message schemas
├── services/        # LLM client, streaming, permissions, session persistence
├── tools/           # Tool implementations (one file per group)
├── ui/              # Textual TUI application and widgets
└── utils/           # Logging, display, file helpers, token counter
config/
└── config.yaml      # Application configuration
tests/
├── unit/            # Unit tests for individual components
└── integration/     # End-to-end workflow tests with mocked LLM

Architecture

SneakyCode follows a ReAct-style agent pattern:

  1. User provides a task in natural language
  2. Agent sends conversation history + tool schemas to the LLM
  3. LLM responds with either text (task complete) or tool calls
  4. Agent executes tool calls with permission checks
  5. Results are fed back to the LLM for the next iteration
  6. Loop continues until the LLM produces a plain-text response or calls finish

The LLM client is abstracted behind an OpenAI-compatible interface, so any endpoint implementing the /v1/chat/completions SSE streaming protocol works as a backend.

License

MIT

Description
No description provided
Readme 465 KiB
Languages
Python 100%