diff --git a/README.md b/README.md index 5881245..d8b4341 100644 --- a/README.md +++ b/README.md @@ -38,7 +38,7 @@ Add Semble to Claude Code (requires [uv](https://docs.astral.sh/uv/getting-start claude mcp add semble -s user -- uvx --from "semble[mcp]" semble ``` -Using Codex, OpenCode, or Cursor? See [MCP Server](#mcp-server) for setup instructions. +Using another agent harness? See [MCP Server](#mcp-server) below for per-agent setup. ### Bash / AGENTS.md @@ -83,7 +83,7 @@ If `semble` is not on `$PATH`, use `uvx --from "semble[mcp]" semble` in its plac -Once installed, run `semble savings` to see how many tokens Semble has saved you. Note that for sub-agent support in Claude Code or Codex, you need the full [Bash / AGENTS.md](#bash-agentsmd) setup below. +Note that sub-agents cannot call MCP tools directly, see [Bash / AGENTS.md](#bash-agentsmd) and [sub-agent setup](#sub-agent-setup) below for details.
Updating Semble @@ -102,7 +102,7 @@ uv cache clean semble # for MCP users (restart your MCP client after) - **Accurate**: NDCG@10 of 0.854 on our [benchmarks](#benchmarks), on par with code-specialized transformer models, at a fraction of the size and cost. - **Token-efficient**: returns only the relevant chunks, using [~98% fewer tokens than grep+read](#benchmarks). - **Zero setup**: runs on CPU with no API keys, GPU, or external services required. -- **MCP server**: works with Claude Code, Cursor, Codex, OpenCode, and any other MCP-compatible agent. +- **MCP server**: works with Claude Code, Cursor, Codex, OpenCode, VS Code, and any other MCP-compatible agent. - **Local and remote**: pass a local path or a git URL. ## MCP Server @@ -113,21 +113,51 @@ Semble can run as an MCP server so agents can search any codebase directly. Repo > Requires [uv](https://docs.astral.sh/uv/getting-started/installation/) to be installed. -#### Claude Code +
+Claude Code + ```bash claude mcp add semble -s user -- uvx --from "semble[mcp]" semble ``` -#### Codex +
+ +
+Cursor + +Add to `~/.cursor/mcp.json` (or `.cursor/mcp.json` in your project): + +```json +{ + "mcpServers": { + "semble": { + "command": "uvx", + "args": ["--from", "semble[mcp]", "semble"] + } + } +} +``` + +
+ +
+Codex + Add to `~/.codex/config.toml`: + ```toml [mcp_servers.semble] command = "uvx" args = ["--from", "semble[mcp]", "semble"] ``` -#### OpenCode +
+ +
+OpenCode + Add to `~/.opencode/config.json`: + ```json { "mcp": { @@ -139,8 +169,31 @@ Add to `~/.opencode/config.json`: } ``` -#### Cursor -Add to `~/.cursor/mcp.json` (or `.cursor/mcp.json` in your project): +
+ +
+VS Code + +Add to `.vscode/mcp.json` in your project (or your user profile's `mcp.json`): + +```json +{ + "servers": { + "semble": { + "command": "uvx", + "args": ["--from", "semble[mcp]", "semble"] + } + } +} +``` + +
+ +
+GitHub Copilot CLI + +Add to `~/.copilot/mcp-config.json`: + ```json { "mcpServers": { @@ -152,6 +205,81 @@ Add to `~/.cursor/mcp.json` (or `.cursor/mcp.json` in your project): } ``` +
+ +
+Windsurf + +Add to `~/.codeium/windsurf/mcp_config.json`: + +```json +{ + "mcpServers": { + "semble": { + "command": "uvx", + "args": ["--from", "semble[mcp]", "semble"] + } + } +} +``` + +
+ +
+Gemini CLI + +Add to `~/.gemini/settings.json`: + +```json +{ + "mcpServers": { + "semble": { + "command": "uvx", + "args": ["--from", "semble[mcp]", "semble"] + } + } +} +``` + +
+ +
+Kiro + +Add to `~/.kiro/settings/mcp.json` (or `.kiro/settings/mcp.json` in your project): + +```json +{ + "mcpServers": { + "semble": { + "command": "uvx", + "args": ["--from", "semble[mcp]", "semble"] + } + } +} +``` + +
+ +
+Zed + +Add to `~/.config/zed/settings.json` (or `.zed/settings.json` in your project): + +```json +{ + "context_servers": { + "semble": { + "command": "uvx", + "args": ["--from", "semble[mcp]", "semble"] + } + } +} +``` + +
+ + ### Tools | Tool | Description | @@ -164,9 +292,9 @@ Add to `~/.cursor/mcp.json` (or `.cursor/mcp.json` in your project): ## Bash / AGENTS.md -An alternative to MCP is to invoke Semble via Bash. For Claude Code and Codex CLI, this is the only option for sub-agents, which cannot call MCP tools directly, though it can also be used alongside MCP for the top-level agent. +An alternative to MCP is to invoke Semble via Bash. Sub-agents cannot call MCP tools directly, so this is the only option for sub-agent support; it can also be used alongside MCP for the top-level agent. -To add Bash support, append the following to your `AGENTS.md` or `CLAUDE.md`: +To add Bash support, append the following to your `AGENTS.md`, `CLAUDE.md`, `GEMINI.md`, or equivalent: ```markdown ## Code Search @@ -197,15 +325,20 @@ If `semble` is not on `$PATH`, use `uvx --from "semble[mcp]" semble` in its plac 4. Use grep only when you need exhaustive literal matches or quick confirmation of an exact string. ``` -**Claude Code sub-agent**: Claude Code also supports a dedicated sub-agent. Run this once in your project root: +### Sub-agent setup + +Claude Code, Gemini CLI, Cursor, OpenCode, GitHub Copilot CLI, and Kiro all support a dedicated semble search sub-agent. Run `semble init` once in your project root: ```bash -semble init -# or, if semble is not on $PATH: -uvx --from "semble[mcp]" semble init +semble init # Claude Code → .claude/agents/semble-search.md +semble init --agent gemini # Gemini CLI → .gemini/agents/semble-search.md +semble init --agent cursor # Cursor → .cursor/agents/semble-search.md +semble init --agent opencode # OpenCode → .opencode/agents/semble-search.md +semble init --agent copilot # Copilot CLI → .github/agents/semble-search.md +semble init --agent kiro # Kiro → .kiro/agents/semble-search.md ``` -This writes [`.claude/agents/semble-search.md`](src/semble/agents/semble-search.md). +If semble is not on `$PATH`, prefix the command with `uvx --from "semble[mcp]"`. ## CLI diff --git a/src/semble/cli.py b/src/semble/cli.py index 0f97adb..5d7f886 100644 --- a/src/semble/cli.py +++ b/src/semble/cli.py @@ -1,6 +1,7 @@ import argparse import asyncio import sys +from enum import Enum from importlib.resources import files from importlib.util import find_spec from pathlib import Path @@ -11,10 +12,26 @@ from semble.stats import format_savings_report from semble.utils import _format_results, _is_git_url, _resolve_chunk -_CLAUDE_FILE_PATH = Path(".claude") / "agents" / "semble-search.md" + +class Agent(str, Enum): + CLAUDE = "claude" + COPILOT = "copilot" + CURSOR = "cursor" + GEMINI = "gemini" + KIRO = "kiro" + OPENCODE = "opencode" + + +_DEFAULT_AGENT = Agent.CLAUDE _CLI_DISPATCH_ARGS = frozenset({"search", "find-related", "init", "savings", "-h", "--help"}) +def _agent_path(agent: Agent) -> Path: + """Return the project-relative path where the semble sub-agent file should be written.""" + base_dir = ".github" if agent is Agent.COPILOT else f".{agent.value}" + return Path(base_dir) / "agents" / "semble-search.md" + + def main() -> None: """Entry point for the semble command-line tool.""" if len(sys.argv) > 1 and sys.argv[1] in _CLI_DISPATCH_ARGS: @@ -49,9 +66,9 @@ def _mcp_main() -> None: asyncio.run(serve(args.path, ref=args.ref, include_text_files=args.include_text_files)) -def _run_init(*, force: bool = False) -> None: - """Write the Claude Code sub-agent file into the current project.""" - dest = _CLAUDE_FILE_PATH +def _run_init(*, agent: Agent = _DEFAULT_AGENT, force: bool = False) -> None: + """Write the semble sub-agent file for the given coding agent into the current project.""" + dest = _agent_path(agent) if dest.exists() and not force: print(f"{dest} already exists. Run with --force to overwrite.", file=sys.stderr) sys.exit(1) @@ -89,7 +106,14 @@ def _cli_main() -> None: help="Also index non-code text files (.md, .yaml, .json, etc.).", ) - init_p = sub.add_parser("init", help="Write .claude/agents/semble-search.md for Claude Code sub-agent support.") + init_p = sub.add_parser("init", help="Write a semble sub-agent file for your coding agent.") + init_p.add_argument( + "--agent", + "-a", + default=_DEFAULT_AGENT.value, + choices=[a.value for a in Agent], + help=f"Coding agent to set up (default: {_DEFAULT_AGENT.value}).", + ) init_p.add_argument("--force", action="store_true", help="Overwrite if the file already exists.") savings_p = sub.add_parser("savings", help="Show token savings and usage stats.") @@ -98,7 +122,7 @@ def _cli_main() -> None: args = parser.parse_args() if args.command == "init": - _run_init(force=args.force) + _run_init(agent=Agent(args.agent), force=args.force) return if args.command == "savings": diff --git a/src/semble/version.py b/src/semble/version.py index 0026b3b..70fb48f 100644 --- a/src/semble/version.py +++ b/src/semble/version.py @@ -1,2 +1,2 @@ -__version_triple__ = (0, 1, 9) +__version_triple__ = (0, 1, 10) __version__ = ".".join(map(str, __version_triple__)) diff --git a/tests/test_cli.py b/tests/test_cli.py index 0520b7a..76bc1d8 100644 --- a/tests/test_cli.py +++ b/tests/test_cli.py @@ -5,10 +5,12 @@ import pytest -from semble.cli import _CLAUDE_FILE_PATH, _cli_main, _run_init, main +from semble.cli import Agent, _agent_path, _cli_main, _run_init, main from semble.types import SearchMode, SearchResult from tests.conftest import make_chunk +_CLAUDE_FILE_PATH = _agent_path(Agent.CLAUDE) + _CLAUDE_AGENT_FILE = files("semble").joinpath("agents/semble-search.md").read_text(encoding="utf-8") diff --git a/uv.lock b/uv.lock index ef3a139..9ce6273 100644 --- a/uv.lock +++ b/uv.lock @@ -10,7 +10,7 @@ resolution-markers = [ [options] exclude-newer = "0001-01-01T00:00:00Z" # This has no effect and is included for backwards compatibility when using relative exclude-newer values. -exclude-newer-span = "P3D" +exclude-newer-span = "P1W" [[package]] name = "annotated-doc" @@ -3238,15 +3238,15 @@ wheels = [ [[package]] name = "sse-starlette" -version = "3.4.4" +version = "3.4.3" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "anyio" }, { name = "starlette" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/f7/2b/58abc2d1fd397e7dde08e947e05c884d8ef2f78d5e2588c17a12d42d6994/sse_starlette-3.4.4.tar.gz", hash = "sha256:07e0fa0460138baf25cdd5fb28683472c3995dc1642225191b3832d62526bcb0", size = 31819, upload-time = "2026-05-12T17:37:17.019Z" } +sdist = { url = "https://files.pythonhosted.org/packages/64/13/3cafb96bceb02949f265bbdf1cbcea2810271ae709e4aa35e980f90c07fd/sse_starlette-3.4.3.tar.gz", hash = "sha256:a7f6d87cf482cf38b911c31075811c7f8b4efbada8ac9d5199a8e239fed513c9", size = 35247, upload-time = "2026-05-11T17:23:41.987Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/dc/67/805710444ea8cc75fbf70b920ed431a560c4bf9c57f7d5a3117213189399/sse_starlette-3.4.4-py3-none-any.whl", hash = "sha256:3f4dd50d8aed2771a091f3a83000323fc3844541c16b4fe585ae2420cc6df973", size = 16514, upload-time = "2026-05-12T17:37:15.601Z" }, + { url = "https://files.pythonhosted.org/packages/fe/a4/c888212b19dd432110d4a78dbc5e6c1bc7476e5fff2f2df2ea9f298b0003/sse_starlette-3.4.3-py3-none-any.whl", hash = "sha256:bf8a90d76192062f01b55095593606bfc7edd0e3ad481339a6e16e7890bc9367", size = 16514, upload-time = "2026-05-11T17:23:40.352Z" }, ] [[package]]