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
53 changes: 53 additions & 0 deletions Formula/codetect.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,53 @@
# Homebrew formula for codetect
#
# This file is the reference formula. The canonical, published version lives in
# the homebrew tap repo: https://github.com/brian-lai/homebrew-tap
#
# To install:
# brew tap brian-lai/tap
# brew install codetect
#
# To update this formula after a new release, update `url`, `sha256`, and `version`.
# The release workflow (`.github/workflows/release.yml`) should automate this.

class Codetect < Formula
desc "Fast, token-efficient codebase search MCP server for Claude Code and any LLM"
homepage "https://github.com/brian-lai/codetect"
url "https://github.com/brian-lai/codetect/archive/refs/tags/v3.7.5.tar.gz"
sha256 "PLACEHOLDER_SHA256_UPDATE_ON_RELEASE" # TODO: update url, sha256, and version on each release
license "MIT"
version "3.7.5"

depends_on "go" => :build
depends_on "ripgrep"

def install
system "make", "build"
bin.install "dist/codetect" => "codetect-mcp"
bin.install "dist/codetect-index"
bin.install "dist/codetect-daemon"
bin.install "dist/codetect-eval"
bin.install "scripts/codetect-wrapper.sh" => "codetect"
(share/"codetect/templates").install Dir["templates/*"]
# Write VERSION file for non-git version reporting
(share/"codetect/VERSION").write(version.to_s + "\n")
# Copy the binary installer for `codetect update`
(share/"codetect").install "scripts/install-binary.sh"
end

def post_install
# ENV["HOME"] may not be the installing user's home in all sandbox contexts,
# but writing the marker is best-effort — never fail the install over it.
begin
config_dir = Pathname.new(ENV.fetch("HOME", Dir.home)) / ".config/codetect"
config_dir.mkpath
(config_dir / "install_method").write("brew\n")
rescue StandardError
# Non-fatal: codetect update will still work via brew upgrade
end
end

test do
assert_match version.to_s, shell_output("#{bin}/codetect version")
end
end
5 changes: 5 additions & 0 deletions Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -164,6 +164,11 @@ install: build
@chmod +x $(BIN_DIR)/codetect $(BIN_DIR)/codetect-mcp $(BIN_DIR)/codetect-index $(BIN_DIR)/codetect-daemon $(BIN_DIR)/codetect-eval $(BIN_DIR)/migrate-to-postgres
@codesign --sign - --force $(BIN_DIR)/codetect-mcp $(BIN_DIR)/codetect-index $(BIN_DIR)/codetect-daemon $(BIN_DIR)/codetect-eval $(BIN_DIR)/migrate-to-postgres 2>/dev/null || true
@cp templates/mcp.json $(SHARE_DIR)/templates/
@# Write VERSION file so non-git installs can report their version.
@# Use --abbrev=0 to get the nearest tag without the dirty commit suffix.
@git describe --tags --exact-match 2>/dev/null > $(SHARE_DIR)/VERSION || \
git describe --tags --abbrev=0 2>/dev/null > $(SHARE_DIR)/VERSION || \
echo "dev" > $(SHARE_DIR)/VERSION
@echo ""
@echo "✓ Installed to $(PREFIX)"
@echo ""
Expand Down
31 changes: 20 additions & 11 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -29,18 +29,27 @@ See [CHANGELOG.md](CHANGELOG.md) for version history and [Migration Guide](docs/

## Quick Start

**macOS (Homebrew):**
```bash
# Clone and run interactive installer
git clone https://github.com/brian-lai/codetect.git
cd codetect
./install.sh
brew tap brian-lai/tap
brew install codetect
```

The installer will:
- ✓ Check for required dependencies (Go, ripgrep)
- ✓ Guide you through Ollama setup for semantic search (with prominent warnings if missing)
- ✓ Build and install globally to `~/.local/bin`
- ✓ Configure your shell PATH automatically
**Linux / macOS (curl):**
```bash
curl -fsSL https://raw.githubusercontent.com/brian-lai/codetect/main/scripts/install-binary.sh | bash
```

**Debian / Ubuntu (.deb):**
```bash
curl -LO https://github.com/brian-lai/codetect/releases/latest/download/codetect_amd64.deb
sudo dpkg -i codetect_amd64.deb
```

**From source:**
```bash
git clone https://github.com/brian-lai/codetect.git && cd codetect && ./install.sh
```

Then in any project:

Expand All @@ -59,9 +68,9 @@ See [Installation Guide](docs/installation.md) for detailed setup instructions.

| Dependency | Required | Purpose |
|------------|----------|---------|
| Go 1.21+ | Yes | Building from source |
| [ripgrep](https://github.com/BurntSushi/ripgrep) | Yes | Keyword search |
| [ripgrep](https://github.com/BurntSushi/ripgrep) | Yes | Keyword search (auto-installed by Homebrew) |
| [Ollama](https://ollama.ai) | No | Semantic search (local embeddings) |
| Go 1.21+ | Build from source only | Compiling binaries |

**Note:** v3 uses ast-grep for symbol extraction. No external ctags dependency required.

Expand Down
2 changes: 2 additions & 0 deletions install.sh
Original file line number Diff line number Diff line change
Expand Up @@ -1199,6 +1199,8 @@ if [[ $INSTALL_GLOBAL =~ ^[Yy] ]]; then
CONFIG_DIR="$HOME/.config/codetect"
mkdir -p "$CONFIG_DIR"
CONFIG_FILE="$CONFIG_DIR/config.env"
# Record install method for codetect update
echo "git" > "$CONFIG_DIR/install_method"
INSTALLED_GLOBALLY=true
else
warn "Skipping global installation"
Expand Down
14 changes: 14 additions & 0 deletions packaging/deb/DEBIAN/control
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
Package: codetect
Version: VERSION_PLACEHOLDER
Architecture: ARCH_PLACEHOLDER
Maintainer: Brian Lai <brian@example.com>
Depends: ripgrep
Recommends: ollama
Section: devel
Priority: optional
Homepage: https://github.com/brian-lai/codetect
Description: Fast, token-efficient codebase search MCP server
codetect is a local MCP server that brings Cursor-like codebase intelligence
to Claude Code and any LLM tool. It provides fast keyword search (ripgrep),
symbol indexing (AST-based), and semantic search (Ollama embeddings) through
four focused MCP tools.
19 changes: 19 additions & 0 deletions packaging/deb/DEBIAN/postinst
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
#!/bin/bash
# Post-installation script for codetect .deb package
set -e

# Resolve the actual installing user's home directory.
# When run via "sudo dpkg -i", $HOME is /root but SUDO_USER is the real user.
if [[ -n "$SUDO_USER" ]]; then
USER_HOME=$(getent passwd "$SUDO_USER" | cut -d: -f6)
else
USER_HOME="$HOME"
fi

# Record install method for codetect update.
# Do not use $XDG_CONFIG_HOME here — under sudo it belongs to root, not $SUDO_USER.
CONFIG_DIR="$USER_HOME/.config/codetect"
mkdir -p "$CONFIG_DIR"
echo "binary" > "$CONFIG_DIR/install_method"

exit 0
74 changes: 74 additions & 0 deletions packaging/deb/build-deb.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,74 @@
#!/bin/bash
#
# Build a .deb package for codetect.
#
# Usage (from repo root):
# bash packaging/deb/build-deb.sh [version] [arch]
#
# Examples:
# bash packaging/deb/build-deb.sh v3.7.5 amd64
# bash packaging/deb/build-deb.sh v3.7.5 arm64
#
# Outputs: dist/codetect_<version>_<arch>.deb
#
# Prerequisites: dpkg-deb, built binaries in dist/

set -e

VERSION="${1:-$(git describe --tags --exact-match 2>/dev/null || git describe --tags --abbrev=0 2>/dev/null || echo dev)}"
VERSION_NUM="${VERSION#v}"
ARCH="${2:-amd64}"

SCRIPT_DIR="$(cd "$(dirname "$0")" && pwd)"
REPO_ROOT="$(cd "$SCRIPT_DIR/../.." && pwd)"

PKG_DIR="$REPO_ROOT/dist/deb/codetect_${VERSION_NUM}_${ARCH}"
DEB_OUT="$REPO_ROOT/dist/codetect_${VERSION_NUM}_${ARCH}.deb"

echo "Building codetect $VERSION_NUM ($ARCH) .deb..."

# Clean and create package tree
rm -rf "$PKG_DIR"
mkdir -p \
"$PKG_DIR/DEBIAN" \
"$PKG_DIR/usr/local/bin" \
"$PKG_DIR/usr/local/share/codetect/templates"

# Fill DEBIAN control files
sed -e "s/VERSION_PLACEHOLDER/$VERSION_NUM/" \
-e "s/ARCH_PLACEHOLDER/$ARCH/" \
"$SCRIPT_DIR/DEBIAN/control" > "$PKG_DIR/DEBIAN/control"

install -m 755 "$SCRIPT_DIR/DEBIAN/postinst" "$PKG_DIR/DEBIAN/postinst"

# Install binaries (must be pre-built)
for bin in codetect-mcp codetect-index codetect-daemon codetect-eval migrate-to-postgres; do
src="$REPO_ROOT/dist/$bin"
if [[ ! -f "$src" ]]; then
echo "Error: $src not found. Run 'make build' first." >&2
exit 1
fi
install -m 755 "$src" "$PKG_DIR/usr/local/bin/$bin"
done

# Wrapper script (main user-facing entry point)
install -m 755 "$REPO_ROOT/scripts/codetect-wrapper.sh" "$PKG_DIR/usr/local/bin/codetect"

# Templates
cp -r "$REPO_ROOT/templates/." "$PKG_DIR/usr/local/share/codetect/templates/"

# VERSION file
echo "$VERSION_NUM" > "$PKG_DIR/usr/local/share/codetect/VERSION"

# Copy binary installer for `codetect update`
install -m 755 "$REPO_ROOT/scripts/install-binary.sh" \
"$PKG_DIR/usr/local/share/codetect/install-binary.sh"

# Build the .deb
dpkg-deb --build "$PKG_DIR" "$DEB_OUT"

echo ""
echo "Built: $DEB_OUT"
echo ""
echo "Install with:"
echo " sudo dpkg -i $DEB_OUT"
96 changes: 88 additions & 8 deletions scripts/codetect-wrapper.sh
Original file line number Diff line number Diff line change
Expand Up @@ -1244,6 +1244,12 @@ cmd_version() {
fi
fi

# For brew/binary installs: read VERSION file written at install time
if [[ -f "$SHARE_DIR/VERSION" ]]; then
echo "codetect $(cat "$SHARE_DIR/VERSION")"
return 0
fi

# Fallback: ask codetect-index for its version
if [[ -x "$BIN_DIR/codetect-index" ]]; then
"$BIN_DIR/codetect-index" version
Expand All @@ -1254,16 +1260,56 @@ cmd_version() {
}

cmd_update() {
local source_dir="${CODETECT_SOURCE:-$HOME/dev/codetect}"

if [[ ! -f "$source_dir/scripts/update.sh" ]]; then
error "Update script not found"
info "Set CODETECT_SOURCE to the location of your codetect clone"
info "Default: $source_dir"
return 1
# Detect how codetect was installed
local method
method=$(cat "$CONFIG_DIR/install_method" 2>/dev/null) || method=""

# Detect go install if marker absent: binary lives under GOPATH/bin
if [[ -z "$method" ]]; then
local codetect_bin
codetect_bin=$(which codetect 2>/dev/null) || codetect_bin=""
local gopath
gopath=$(go env GOPATH 2>/dev/null) || gopath=""
if [[ -n "$gopath" && "$codetect_bin" == "$gopath/bin/"* ]]; then
method="go"
fi
fi

exec "$source_dir/scripts/update.sh" "$@"
case "$method" in
brew)
info "Installed via Homebrew. Running: brew upgrade codetect"
exec brew upgrade codetect
;;
go)
info "Installed via go install (updates codetect-mcp only)."
info "For a full update including the wrapper and indexer, use brew or the curl installer."
info "Running: go install github.com/brian-lai/codetect/cmd/codetect@latest"
exec go install github.com/brian-lai/codetect/cmd/codetect@latest
;;
binary)
local installer="$SHARE_DIR/install-binary.sh"
if [[ -x "$installer" ]]; then
exec "$installer" "$@"
else
info "Re-run the installer to update:"
info " curl -fsSL https://raw.githubusercontent.com/brian-lai/codetect/main/scripts/install-binary.sh | bash"
return 0
fi
;;
git|*)
# Legacy / git-clone path — delegate to scripts/update.sh
local source_dir="${CODETECT_SOURCE:-$HOME/dev/codetect}"
if [[ ! -f "$source_dir/scripts/update.sh" ]]; then
error "Update script not found and install method unknown."
info "Please reinstall codetect using one of:"
info " brew install brian-lai/tap/codetect"
info " curl -fsSL https://raw.githubusercontent.com/brian-lai/codetect/main/scripts/install-binary.sh | bash"
info " https://github.com/brian-lai/codetect#installation"
return 1
fi
exec "$source_dir/scripts/update.sh" "$@"
;;
esac
}

cmd_help() {
Expand Down Expand Up @@ -1321,13 +1367,47 @@ cmd_help() {
echo " claude # Start Claude Code"
}

#
# Version staleness check (throttled to once per 24 hours)
#
check_for_updates() {
local stamp_file="$CONFIG_DIR/last_update_check"
local now
now=$(date +%s 2>/dev/null) || return 0
local last
last=$(cat "$stamp_file" 2>/dev/null) || last=0
# Use >= to avoid (( expr == 0 )) returning exit code 1 under set -e
[[ $(( now - last )) -ge 86400 ]] || return 0
# Only write timestamp after a successful API response so a network failure
# doesn't silence the check for 24 hours
local latest
latest=$(curl -sf --max-time 3 \
"https://api.github.com/repos/brian-lai/codetect/releases/latest" \
2>/dev/null | grep '"tag_name"' | cut -d'"' -f4) || return 0
[[ -z "$latest" ]] && return 0
echo "$now" > "$stamp_file" 2>/dev/null || true
# Get current version tag (last word of cmd_version output, e.g. "v3.7.5")
local current
current=$(cmd_version 2>/dev/null | awk '{print $NF}') || return 0
# Skip nag for dev builds: their version looks like "(abc1234)" not "vX.Y.Z"
[[ "$current" == "("* ]] && return 0
if [[ -n "$current" && "$latest" != "$current" ]]; then
echo -e " ${CYAN}ℹ${NC} codetect $latest is available. Run: codetect update" >&2
fi
}

#
# Main
#
main() {
local cmd="${1:-help}"
shift || true

# Check for updates on every command except mcp (long-running server)
if [[ "$cmd" != "mcp" ]]; then
check_for_updates
fi

case "$cmd" in
mcp)
cmd_mcp "$@"
Expand Down
Loading