# Claude Code Composer / DeepSeek Large-File Read Setup

This guide explains how to copy a Claude Code setup where large file reads are redirected to a cheaper or external model helper instead of being loaded directly into Claude's context.

The setup has three parts:

1. `ask-deepseek`: a shell command for asking questions about one or more files.
2. `deepseek-read-hook.sh`: a Claude Code `PreToolUse` hook that checks `Read` calls.
3. `~/.claude/settings.json`: Claude Code configuration that connects the hook to the `Read` tool.

In this version, `ask-deepseek` is a symlink to a `composer` script:

- Primary backend: `grok-composer-2.5-fast` through a local CLIProxy-compatible gateway.
- Fallback backend: DeepSeek direct API.
- Large-file cutoff: 200 lines.

Small reads still work normally. Full reads of files over 200 lines are blocked with a message telling Claude to use `ask-deepseek`.

## 1. Install Requirements

Install `jq` and `curl`:

```bash
sudo apt-get update
sudo apt-get install -y jq curl
```

Create the local script directories:

```bash
mkdir -p "$HOME/.local/bin" "$HOME/.claude" "$HOME/.deepseek"
```

Make sure `~/.local/bin` is in your shell path:

```bash
echo 'export PATH="$HOME/.local/bin:$PATH"' >> "$HOME/.bashrc"
echo 'export PATH="$HOME/.local/bin:$PATH"' >> "$HOME/.zshrc"
export PATH="$HOME/.local/bin:$PATH"
```

## 2. Configure API Keys

### Option A: Composer / CLIProxy

If you have a local CLIProxy-compatible gateway, export its API key:

```bash
export CLIPROXY_API_KEY="your_clipproxy_key_here"
```

To make it persistent:

```bash
echo 'export CLIPROXY_API_KEY="your_clipproxy_key_here"' >> "$HOME/.zshrc"
```

The default gateway URL used by the helper is:

```text
http://localhost:8317/v1/chat/completions
```

You can override it with:

```bash
export ASK_GATEWAY_URL="http://localhost:8317/v1/chat/completions"
```

### Option B: DeepSeek Direct Fallback

Create a DeepSeek environment file:

```bash
nano "$HOME/.deepseek/.env"
```

Add your own key:

```bash
DEEPSEEK_API_KEY="your_deepseek_key_here"
DEEPSEEK_MODEL="deepseek-v4-pro"
DEEPSEEK_MAX_TOKENS=16384
DEEPSEEK_TEMPERATURE=0.2
DEEPSEEK_TOP_P=0.9
DEEPSEEK_THINKING=true
DEEPSEEK_REASONING_EFFORT="max"
```

Secure the file:

```bash
chmod 600 "$HOME/.deepseek/.env"
```

Do not commit this file to git.

## 3. Create The Composer Helper

Create the helper script:

```bash
nano "$HOME/.local/bin/composer"
```

Paste this:

```bash
#!/usr/bin/env bash
set -euo pipefail

# composer - read-only file Q&A helper.
#
# Usage:
#   ask-deepseek "What does this file do?" path/to/file
#   ask-deepseek "Find likely bugs" file1 file2
#
# Env overrides:
#   ASK_BACKEND=composer|deepseek
#   ASK_COMPOSER_MODEL=grok-composer-2.5-fast
#   ASK_COMPOSER_EFFORT=low|medium|high
#   ASK_GATEWAY_URL=http://localhost:8317/v1/chat/completions
#   ASK_MAX_TOKENS=16384
#   ASK_TIMEOUT=300

BACKEND="${ASK_BACKEND:-composer}"

if [[ $# -lt 1 ]]; then
  echo "Usage: ask-deepseek \"question\" [file1 file2 ...]" >&2
  exit 1
fi

PROMPT="$1"
shift || true

CONTENT_FILE=$(mktemp)
PAYLOAD_FILE=$(mktemp)
RESP_FILE=$(mktemp)
trap 'rm -f "$CONTENT_FILE" "$PAYLOAD_FILE" "$RESP_FILE"' EXIT

for file in "$@"; do
  if [[ -f "$file" ]]; then
    echo "=== $file ===" >> "$CONTENT_FILE"
    cat "$file" >> "$CONTENT_FILE"
    echo "" >> "$CONTENT_FILE"
  fi
done

if [[ ! -t 0 ]]; then
  echo "=== stdin ===" >> "$CONTENT_FILE"
  cat >> "$CONTENT_FILE"
fi

SYSTEM_PROMPT="You are a code analyst. Be thorough, precise, and structured in your analysis."

do_request() {
  local code
  code=$(curl -sS -m "${ASK_TIMEOUT:-300}" -o "$RESP_FILE" -w "%{http_code}" \
    "$1" \
    -H "Authorization: Bearer $2" \
    -H "Content-Type: application/json" \
    -d @"$PAYLOAD_FILE" 2>/dev/null) || true
  echo "${code:-000}"
}

extract_answer() {
  local content
  content=$(jq -r '.choices[0].message.content // empty' "$RESP_FILE" 2>/dev/null)
  [[ -n "$content" ]] || return 1
  printf '%s\n' "$content"
}

run_composer() {
  local key="${CLIPROXY_API_KEY:-}"
  [[ -n "$key" ]] || { echo "composer: CLIPROXY_API_KEY not set" >&2; return 1; }

  local effort="${ASK_COMPOSER_EFFORT:-high}"
  case "$effort" in low|medium|high) ;; *) effort="high" ;; esac

  jq -n \
    --arg model "${ASK_COMPOSER_MODEL:-grok-composer-2.5-fast}" \
    --arg system "$SYSTEM_PROMPT" \
    --arg prompt "$PROMPT" \
    --rawfile content "$CONTENT_FILE" \
    --arg effort "$effort" \
    --argjson max_tokens "${ASK_MAX_TOKENS:-16384}" \
    '{
      model: $model,
      max_tokens: $max_tokens,
      temperature: 0.2,
      reasoning_effort: $effort,
      messages: [
        {role: "system", content: $system},
        {role: "user", content: ($content + "\n\n---\n\nQuestion: " + $prompt)}
      ]
    }' > "$PAYLOAD_FILE"

  local status
  status=$(do_request "${ASK_GATEWAY_URL:-http://localhost:8317/v1/chat/completions}" "$key")
  if [[ "$status" != "200" ]]; then
    echo "composer: composer backend failed HTTP $status" >&2
    return 1
  fi

  extract_answer
}

run_deepseek() {
  [[ -f "$HOME/.deepseek/.env" ]] || { echo "composer: ~/.deepseek/.env missing" >&2; return 1; }
  source "$HOME/.deepseek/.env"

  jq -n \
    --arg model "${DEEPSEEK_MODEL:-deepseek-v4-pro}" \
    --arg system "$SYSTEM_PROMPT" \
    --arg prompt "$PROMPT" \
    --rawfile content "$CONTENT_FILE" \
    --argjson max_tokens "${DEEPSEEK_MAX_TOKENS:-16384}" \
    --argjson temperature "${DEEPSEEK_TEMPERATURE:-0.2}" \
    '{
      model: $model,
      max_tokens: $max_tokens,
      temperature: $temperature,
      messages: [
        {role: "system", content: $system},
        {role: "user", content: ($content + "\n\n---\n\nQuestion: " + $prompt)}
      ]
    }' > "$PAYLOAD_FILE"

  local status
  status=$(do_request "https://api.deepseek.com/chat/completions" "$DEEPSEEK_API_KEY")
  if [[ "$status" != "200" ]]; then
    echo "composer: deepseek backend failed HTTP $status" >&2
    return 1
  fi

  extract_answer
}

case "$BACKEND" in
  composer)
    if ! run_composer; then
      echo "composer: falling back to deepseek-direct" >&2
      run_deepseek
    fi
    ;;
  deepseek)
    run_deepseek
    ;;
  *)
    echo "composer: unknown ASK_BACKEND '$BACKEND' use composer|deepseek" >&2
    exit 1
    ;;
esac
```

Make it executable and create the compatibility alias:

```bash
chmod +x "$HOME/.local/bin/composer"
ln -sf "$HOME/.local/bin/composer" "$HOME/.local/bin/ask-deepseek"
```

Test it:

```bash
ask-deepseek "Summarize this file" "$HOME/.zshrc"
```

Force the DeepSeek backend:

```bash
ASK_BACKEND=deepseek ask-deepseek "Summarize this file" "$HOME/.zshrc"
```

## 4. Create The Claude Read Hook

Create the hook:

```bash
nano "$HOME/.local/bin/deepseek-read-hook.sh"
```

Paste this:

```bash
#!/usr/bin/env bash
set -euo pipefail

LOG_FILE="/tmp/deepseek-read-hook.log"

INPUT=$(cat)
echo "$(date -Iseconds) Input: $INPUT" >> "$LOG_FILE"

FILE_PATH=$(echo "$INPUT" | jq -r '.tool_input.file_path // empty')
LIMIT=$(echo "$INPUT" | jq -r '.tool_input.limit // empty')
OFFSET=$(echo "$INPUT" | jq -r '.tool_input.offset // empty')
PAGES=$(echo "$INPUT" | jq -r '.tool_input.pages // empty')

allow() {
  exit 0
}

deny() {
  local reason="$1"
  jq -n --arg reason "$reason" '{
    hookSpecificOutput: {
      hookEventName: "PreToolUse",
      permissionDecision: "deny",
      permissionDecisionReason: $reason
    }
  }'
  exit 0
}

[[ -z "$FILE_PATH" ]] && allow
[[ ! -f "$FILE_PATH" ]] && allow

if [[ -n "$PAGES" && "$PAGES" != "null" ]]; then
  allow
fi

if [[ -n "$OFFSET" && "$OFFSET" != "null" && "$OFFSET" != "0" ]]; then
  allow
fi

if [[ -n "$LIMIT" && "$LIMIT" != "null" ]]; then
  if [[ "$LIMIT" -le 200 ]] 2>/dev/null; then
    allow
  fi
fi

EXT="${FILE_PATH##*.}"
case "$EXT" in
  png|jpg|jpeg|gif|webp|ico|svg|bmp|tiff|heic|avif) allow ;;
  pdf) allow ;;
  zip|tar|gz|bz2|xz|7z|rar|dmg|iso) allow ;;
  bin|exe|dll|so|dylib|wasm|pyc|pyo|class|o|a|lib) allow ;;
  mp3|mp4|wav|flac|ogg|webm|mkv|avi|mov) allow ;;
  sqlite|db|sqlite3) allow ;;
esac

LINES_IN_FIRST_201=$(head -n 201 "$FILE_PATH" 2>/dev/null | wc -l || echo "0")
LINES_IN_FIRST_201=$(echo "$LINES_IN_FIRST_201" | tr -d '[:space:]')

if [[ "$LINES_IN_FIRST_201" -gt 200 ]] 2>/dev/null; then
  BASENAME=$(basename "$FILE_PATH")
  deny "File '$BASENAME' has >200 lines. Use: ask-deepseek \"your question\" \"$FILE_PATH\""
fi

allow
```

Make it executable:

```bash
chmod +x "$HOME/.local/bin/deepseek-read-hook.sh"
```

## 5. Connect The Hook To Claude Code

Open Claude Code settings:

```bash
nano "$HOME/.claude/settings.json"
```

If the file does not exist, create:

```json
{
  "permissions": {
    "defaultMode": "dontAsk"
  },
  "hooks": {
    "PreToolUse": [
      {
        "matcher": "Read",
        "hooks": [
          {
            "type": "command",
            "command": "/home/YOUR_USER/.local/bin/deepseek-read-hook.sh"
          }
        ]
      }
    ]
  }
}
```

Replace `/home/YOUR_USER` with your real home path.

If `settings.json` already exists, merge the `PreToolUse` hook into the existing `"hooks"` object rather than deleting the current settings.

Example using `$HOME` expanded to `/home/adeel`:

```json
{
  "hooks": {
    "PreToolUse": [
      {
        "matcher": "Read",
        "hooks": [
          {
            "type": "command",
            "command": "/home/adeel/.local/bin/deepseek-read-hook.sh"
          }
        ]
      }
    ]
  }
}
```

Restart Claude Code after editing settings.

## 6. Expected Behavior

Small file reads work normally.

Large full-file reads are blocked when the file has more than 200 lines. Claude sees a message like:

```text
File 'main.rs' has >200 lines. Use: ask-deepseek "your question" "/path/to/main.rs"
```

Then Claude can run:

```bash
ask-deepseek "Explain the routing and identify likely bugs" src/main.rs
```

Multiple files:

```bash
ask-deepseek "Compare these files and explain how data flows between them" src/main.rs src/routes.rs src/db.rs
```

Piped input:

```bash
rg "TODO|panic|unwrap" src | ask-deepseek "Which of these are risky?"
```

## 7. Optional DeepSeek Code Generator

This optional helper generates code from a prompt and optional context files. It is manual and separate from Claude's `Edit` tool.

Create:

```bash
nano "$HOME/.local/bin/deepseek-write"
```

Paste:

```bash
#!/usr/bin/env bash
set -euo pipefail

source "$HOME/.deepseek/.env"

usage() {
  echo "Usage: deepseek-write --spec 'description' [--context file1 file2 ...] [--target output.py]"
  exit 1
}

SPEC=""
TARGET=""
CONTEXT_FILES=()
CONTEXT=""

while [[ $# -gt 0 ]]; do
  case "$1" in
    --spec)
      SPEC="$2"
      shift 2
      ;;
    --context)
      shift
      while [[ $# -gt 0 && ! "$1" =~ ^-- ]]; do
        CONTEXT_FILES+=("$1")
        shift
      done
      ;;
    --target)
      TARGET="$2"
      shift 2
      ;;
    -h|--help)
      usage
      ;;
    *)
      echo "Unknown option: $1"
      usage
      ;;
  esac
done

[[ -z "$SPEC" ]] && usage

for file in "${CONTEXT_FILES[@]}"; do
  if [[ -f "$file" ]]; then
    CONTEXT+="=== $file ===
$(cat "$file")

"
  fi
done

RESULT=$(curl -s https://api.deepseek.com/chat/completions \
  -H "Authorization: Bearer $DEEPSEEK_API_KEY" \
  -H "Content-Type: application/json" \
  -d "$(jq -n \
    --arg model "${DEEPSEEK_MODEL:-deepseek-v4-pro}" \
    --arg spec "$SPEC" \
    --arg context "$CONTEXT" \
    --argjson max_tokens "${DEEPSEEK_WRITE_MAX_TOKENS:-16384}" \
    --argjson temperature "${DEEPSEEK_TEMPERATURE:-0.2}" \
    '{
      model: $model,
      max_tokens: $max_tokens,
      temperature: $temperature,
      messages: [
        {
          role: "system",
          content: "You are a code generator. Output ONLY the requested code, no explanations or markdown fences. Match the style and patterns of any reference files provided."
        },
        {
          role: "user",
          content: ("Generate: " + $spec + (if $context != "" then "\n\nReference files for style:\n" + $context else "" end))
        }
      ]
    }')" \
  | jq -r '.choices[0].message.content')

if [[ -n "$TARGET" ]]; then
  printf '%s\n' "$RESULT" > "$TARGET"
  echo "Written to $TARGET"
else
  printf '%s\n' "$RESULT"
fi
```

Make it executable:

```bash
chmod +x "$HOME/.local/bin/deepseek-write"
```

Example:

```bash
deepseek-write \
  --spec "Create a Rust helper that validates email addresses" \
  --context src/main.rs \
  --target src/email.rs
```

## 8. Verify The Hook

Create a large test file:

```bash
seq 1 250 > /tmp/large-test.txt
```

Start Claude Code and ask:

```text
Read /tmp/large-test.txt
```

Expected result: Claude's normal `Read` gets denied and the message tells it to use `ask-deepseek`.

Then test manually:

```bash
ask-deepseek "What is in this file?" /tmp/large-test.txt
```

## 9. Suggested Claude Instruction

After installing the hook, tell Claude Code:

```text
When a file is large or Read is blocked, use:
ask-deepseek "specific question" "path/to/file"

Use normal Read with limit/offset for small sections, and use ask-deepseek for broad analysis of big files.
```

## Troubleshooting

Check that the hook is executable:

```bash
ls -l "$HOME/.local/bin/deepseek-read-hook.sh"
```

Check the hook log:

```bash
tail -50 /tmp/deepseek-read-hook.log
```

Check that `ask-deepseek` is available:

```bash
command -v ask-deepseek
ask-deepseek "Say hello" < /dev/null
```

If Composer is not available, force DeepSeek:

```bash
ASK_BACKEND=deepseek ask-deepseek "Summarize this file" path/to/file
```

If Claude settings fail to parse, validate JSON:

```bash
jq . "$HOME/.claude/settings.json"
```

## Summary

The hook acts as a guardrail for Claude Code reads. It allows small and targeted reads, blocks large full-file reads, and nudges Claude toward `ask-deepseek` for broad analysis of large files. This helps keep Claude's context cleaner while still giving it a way to inspect big files.
