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
7 changes: 6 additions & 1 deletion .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -42,4 +42,9 @@ tmp/
build/

commands.txt
gateway/.claude/
gateway/.claude/

# Local planning & Claude config — never commit
CONTEXT.md
.claudeignore
CLAUDE.md
136 changes: 109 additions & 27 deletions gateway/cmd/iq/main.go
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,9 @@ package main

import (
"context"
"crypto/rand"
"encoding/hex"
"errors"
"fmt"
"net"
"net/http"
Expand All @@ -21,35 +24,94 @@ import (

var version = "dev"

func generateToken() string {
b := make([]byte, 16)
if _, err := rand.Read(b); err != nil {
return fmt.Sprintf("iq-fallback-%d", time.Now().UnixNano())
}
return hex.EncodeToString(b)
}

func main() {
go checkForUpdate()

port, err := getFreePort()
if err != nil {
fmt.Fprintf(os.Stderr, "iq: failed to find free port: %v\n", err)
os.Exit(1)
if len(os.Args) < 2 {
// iq alone → run claude (backward compat, no subcommand)
runClaude([]string{}, false)
return
}

switch os.Args[1] {
case "claude":
devMode, claudeArgs := parseDevFlag(os.Args[2:])
runClaude(claudeArgs, devMode)
case "gemini":
fmt.Println(" iq gemini — coming soon")
case "codex":
fmt.Println(" iq codex — coming soon")
case "help", "--help", "-h":
printHelp()
default:
// Unknown subcommand → pass everything to claude (backward compat).
// Ensures `iq somefile.go` and `iq --resume` still work as before.
runClaude(os.Args[1:], false)
}
}

func parseDevFlag(args []string) (bool, []string) {
dev := false
filtered := make([]string, 0, len(args))
for _, a := range args {
if a == "--dev" {
dev = true
} else {
filtered = append(filtered, a)
}
}
return dev, filtered
}

func runClaude(args []string, devMode bool) {
if devMode {
os.Setenv("IQ_DEV_MODE", "1")
fmt.Fprintln(os.Stderr, " [iq] dev mode — relaxed guards, full telemetry")
}

// Generate a per-invocation session ID so the circuit breaker scopes
// similarity checks within this iq session, not across sessions.
os.Setenv("IQ_SESSION_ID", generateToken())

// Redirect gateway stderr to a log file so nothing leaks into the terminal.
logPath := filepath.Join(os.Getenv("HOME"), ".indexqube", "gateway.log")
os.MkdirAll(filepath.Dir(logPath), 0700) //nolint:errcheck
if lf, err := os.OpenFile(logPath, os.O_CREATE|os.O_APPEND|os.O_WRONLY, 0644); err == nil {
syscall.Dup2(int(lf.Fd()), int(os.Stderr.Fd())) //nolint:errcheck
ln, err := net.Listen("tcp", "127.0.0.1:0")
if err != nil {
fmt.Fprintf(os.Stderr, "iq: failed to reserve local port: %v\n", err)
os.Exit(1)
}
port := ln.Addr().(*net.TCPAddr).Port

ctx, cancel := context.WithCancel(context.Background())
defer cancel()

os.Setenv("INDEXQUBE_LOG_LEVEL", "error")
go startProxy(ctx, port)
os.Setenv("INDEXQUBE_LOG_LEVEL", "off")
go startProxy(ctx, ln)

if !waitForProxy(port) {
fmt.Fprintf(os.Stderr, "iq: proxy failed to start within 2s\n")
os.Exit(1)
}

args := append([]string{"claude"}, os.Args[1:]...)
cmd := exec.Command(args[0], args[1:]...)
claudePath, err := exec.LookPath("claude")
if err != nil {
fmt.Fprintf(os.Stderr, "iq: could not find 'claude' in PATH: %v\n", err)
os.Exit(1)
}

if !filepath.IsAbs(claudePath) {
fmt.Fprintf(os.Stderr, "iq: 'claude' path is not absolute: %s\n", claudePath)
os.Exit(1)
}

cmdArgs := append([]string{claudePath}, args...)
cmd := exec.Command(cmdArgs[0], cmdArgs[1:]...)
cmd.Env = append(os.Environ(),
fmt.Sprintf("ANTHROPIC_BASE_URL=http://127.0.0.1:%d", port),
)
Expand All @@ -59,21 +121,50 @@ func main() {

sigs := make(chan os.Signal, 1)
signal.Notify(sigs, syscall.SIGINT, syscall.SIGTERM)
defer signal.Stop(sigs)
go func() {
sig := <-sigs
if cmd.Process != nil {
cmd.Process.Signal(sig) //nolint:errcheck
}
}()

cmd.Run() //nolint:errcheck
if err := cmd.Run(); err != nil {
var exitErr *exec.ExitError
if errors.As(err, &exitErr) {
os.Exit(exitErr.ExitCode())
}
fmt.Fprintf(os.Stderr, "iq: failed to run claude: %v\n", err)
os.Exit(1)
}
}

func printHelp() {
fmt.Print(`
iq — IndexQube CLI

USAGE
iq Start Claude Code (default)
iq claude Start Claude Code via IndexQube
iq claude --dev Start Claude Code, dev mode (relaxed guards)
iq gemini Gemini via IndexQube (coming soon)
iq codex Codex via IndexQube (coming soon)
iq help Show this help

DEV MODE
--dev disables velocity guard, enables verbose logging.
Use when building IndexQube with IndexQube.

`)
}

const telemetryEndpoint = "https://dev-api.indexqube.com"

func startProxy(ctx context.Context, port int) {
os.Setenv("INDEXQUBE_BIND_ADDR", fmt.Sprintf("127.0.0.1:%d", port))
os.Setenv("INDEXQUBE_DEV_TOKEN", "iq-local")
func startProxy(ctx context.Context, ln net.Listener) {
os.Setenv("INDEXQUBE_BIND_ADDR", ln.Addr().String())
if os.Getenv("INDEXQUBE_DEV_TOKEN") == "" {
os.Setenv("INDEXQUBE_DEV_TOKEN", generateToken())
}
os.Setenv("INDEXQUBE_MODE", "optimize")
os.Setenv("INDEXQUBE_ENABLE_BLOCK_OPTIMIZER", "true")
// Route telemetry through the deployed gateway so Supabase credentials
Expand All @@ -85,16 +176,7 @@ func startProxy(ctx context.Context, port int) {
if os.Getenv("INDEXQUBE_LOG_LEVEL") == "" {
os.Setenv("INDEXQUBE_LOG_LEVEL", "warn")
}
server.Run(ctx) //nolint:errcheck
}

func getFreePort() (int, error) {
ln, err := net.Listen("tcp", "127.0.0.1:0")
if err != nil {
return 0, err
}
defer ln.Close()
return ln.Addr().(*net.TCPAddr).Port, nil
server.RunWithPublicListener(ctx, ln) //nolint:errcheck
}

func waitForProxy(port int) bool {
Expand Down
2 changes: 1 addition & 1 deletion gateway/cmd/iq/update.go
Original file line number Diff line number Diff line change
Expand Up @@ -31,7 +31,7 @@ func checkForUpdate() {

fmt.Fprintf(os.Stderr,
"\niq: new version available %s → %s\n"+
" Run: curl -sSL https://raw.githubusercontent.com/Revanth14/indexqube/main/install.sh | bash\n\n",
" Download the latest release from: https://github.com/Revanth14/indexqube/releases/latest\n\n",
version, release.TagName,
)
}
8 changes: 8 additions & 0 deletions gateway/deployments/gateway.env.example
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,14 @@ INDEXQUBE_ENABLE_BLOCK_OPTIMIZER=false
# Code retry bursts from hammering the provider with huge repeated payloads.
INDEXQUBE_RATE_LIMIT_COOLDOWN=30s

# --- Local runaway loop guard (circuit breaker for repeated large similar requests) ---
# IQ_ALLOW_RUNAWAY=1 bypasses this guard for the current session.
# INDEXQUBE_CIRCUIT_BREAKER_ENABLED=true
# INDEXQUBE_CIRCUIT_WINDOW_SECONDS=60
# INDEXQUBE_CIRCUIT_MAX_SIMILAR=15
# INDEXQUBE_CIRCUIT_MIN_TOKENS=50000
# INDEXQUBE_CIRCUIT_RETRY_AFTER_SECONDS=60

# --- AWS Bedrock backend (replaces direct Anthropic API calls for /v1/messages) ---
# Set INDEXQUBE_BEDROCK_ENABLED=true to route /v1/messages through Bedrock instead.
# Credentials come from the standard AWS SDK chain: env vars (AWS_ACCESS_KEY_ID /
Expand Down
8 changes: 8 additions & 0 deletions gateway/go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,7 @@ require (
github.com/beorn7/perks v1.0.1 // indirect
github.com/cenkalti/backoff/v5 v5.0.3 // indirect
github.com/cespare/xxhash/v2 v2.3.0 // indirect
github.com/dustin/go-humanize v1.0.1 // indirect
github.com/felixge/httpsnoop v1.0.4 // indirect
github.com/go-logr/logr v1.4.3 // indirect
github.com/go-logr/stdr v1.2.2 // indirect
Expand All @@ -41,10 +42,13 @@ require (
github.com/jackc/pgservicefile v0.0.0-20240606120523-5a60cdf6a761 // indirect
github.com/jackc/pgx/v5 v5.9.2 // indirect
github.com/jackc/puddle/v2 v2.2.2 // indirect
github.com/mattn/go-isatty v0.0.20 // indirect
github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822 // indirect
github.com/ncruces/go-strftime v1.0.0 // indirect
github.com/prometheus/client_model v0.6.2 // indirect
github.com/prometheus/common v0.66.1 // indirect
github.com/prometheus/procfs v0.16.1 // indirect
github.com/remyoudompheng/bigfft v0.0.0-20230129092748-24d4a6f8daec // indirect
go.opentelemetry.io/auto/sdk v1.2.1 // indirect
go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.43.0 // indirect
go.opentelemetry.io/otel/metric v1.43.0 // indirect
Expand All @@ -58,4 +62,8 @@ require (
google.golang.org/genproto/googleapis/rpc v0.0.0-20260401024825-9d38bb4040a9 // indirect
google.golang.org/grpc v1.80.0 // indirect
google.golang.org/protobuf v1.36.11 // indirect
modernc.org/libc v1.72.3 // indirect
modernc.org/mathutil v1.7.1 // indirect
modernc.org/memory v1.11.0 // indirect
modernc.org/sqlite v1.50.1 // indirect
)
17 changes: 17 additions & 0 deletions gateway/go.sum
Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,8 @@ github.com/cespare/xxhash/v2 v2.3.0/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XL
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/dustin/go-humanize v1.0.1 h1:GzkhY7T5VNhEkwH0PVJgjz+fX1rhBrR7pRT3mDkpeCY=
github.com/dustin/go-humanize v1.0.1/go.mod h1:Mu1zIs6XwVuF/gI1OepvI0qD18qycQx+mFykh5fBlto=
github.com/felixge/httpsnoop v1.0.4 h1:NFTV2Zj1bL4mc9sqWACXbQFVBBg2W3GPvqp8/ESS2Wg=
github.com/felixge/httpsnoop v1.0.4/go.mod h1:m8KPJKqk1gH5J9DgRY2ASl2lWCfGKXixSwevea8zH2U=
github.com/go-logr/logr v1.2.2/go.mod h1:jdQByPbusPIv2/zmleS9BjJVeZ6kBagPoEUsqbVz/1A=
Expand Down Expand Up @@ -72,8 +74,12 @@ github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY=
github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE=
github.com/kylelemons/godebug v1.1.0 h1:RPNrshWIDI6G2gRW9EHilWtl7Z6Sb1BR0xunSBf0SNc=
github.com/kylelemons/godebug v1.1.0/go.mod h1:9/0rRGxNHcop5bhtWyNeEfOS8JIWk580+fNqagV/RAw=
github.com/mattn/go-isatty v0.0.20 h1:xfD0iDuEKnDkl03q4limB+vH+GxLEtL/jb4xVJSWWEY=
github.com/mattn/go-isatty v0.0.20/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y=
github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822 h1:C3w9PqII01/Oq1c1nUAm88MOHcQC9l5mIlSMApZMrHA=
github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822/go.mod h1:+n7T8mK8HuQTcFwEeznm/DIxMOiR9yIdICNftLE1DvQ=
github.com/ncruces/go-strftime v1.0.0 h1:HMFp8mLCTPp341M/ZnA4qaf7ZlsbTc+miZjCLOFAw7w=
github.com/ncruces/go-strftime v1.0.0/go.mod h1:Fwc5htZGVVkseilnfgOVb9mKy6w1naJmn9CehxcKcls=
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
github.com/prometheus/client_golang v1.23.2 h1:Je96obch5RDVy3FDMndoUsjAhG5Edi49h0RJWRi/o0o=
Expand All @@ -84,6 +90,8 @@ github.com/prometheus/common v0.66.1 h1:h5E0h5/Y8niHc5DlaLlWLArTQI7tMrsfQjHV+d9Z
github.com/prometheus/common v0.66.1/go.mod h1:gcaUsgf3KfRSwHY4dIMXLPV0K/Wg1oZ8+SbZk/HH/dA=
github.com/prometheus/procfs v0.16.1 h1:hZ15bTNuirocR6u0JZ6BAHHmwS1p8B4P6MRqxtzMyRg=
github.com/prometheus/procfs v0.16.1/go.mod h1:teAbpZRB1iIAJYREa1LsoWUXykVXA1KlTmWl8x/U+Is=
github.com/remyoudompheng/bigfft v0.0.0-20230129092748-24d4a6f8daec h1:W09IVJc94icq4NjY3clb7Lk8O1qJ8BdBEF8z0ibU0rE=
github.com/remyoudompheng/bigfft v0.0.0-20230129092748-24d4a6f8daec/go.mod h1:qqbHyh8v60DhA7CoWK5oRCqLrMHRGoxYCSS9EjAz6Eo=
github.com/rogpeppe/go-internal v1.14.1 h1:UQB4HGPB6osV0SQTLymcB4TgvyWu6ZyliaW0tI/otEQ=
github.com/rogpeppe/go-internal v1.14.1/go.mod h1:MaRKkUm5W0goXpeCfT7UZI6fk/L7L7so1lCWt35ZSgc=
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
Expand Down Expand Up @@ -119,6 +127,7 @@ golang.org/x/net v0.52.0 h1:He/TN1l0e4mmR3QqHMT2Xab3Aj3L9qjbhRm78/6jrW0=
golang.org/x/net v0.52.0/go.mod h1:R1MAz7uMZxVMualyPXb+VaqGSa3LIaUqk0eEt3w36Sw=
golang.org/x/sync v0.20.0 h1:e0PTpb7pjO8GAtTs2dQ6jYa5BWYlMuX047Dco/pItO4=
golang.org/x/sync v0.20.0/go.mod h1:9xrNwdLfx4jkKbNva9FpL6vEN7evnE43NNNJQ2LF3+0=
golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.42.0 h1:omrd2nAlyT5ESRdCLYdm3+fMfNFE/+Rf4bDIQImRJeo=
golang.org/x/sys v0.42.0/go.mod h1:4GL1E5IUh+htKOUEOaiffhrAeqysfVGipDYzABqnCmw=
golang.org/x/text v0.35.0 h1:JOVx6vVDFokkpaq1AEptVzLTpDe9KGpj5tR4/X+ybL8=
Expand All @@ -139,3 +148,11 @@ gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c/go.mod h1:JHkPIbrfpd72SG/EV
gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
modernc.org/libc v1.72.3 h1:ZnDF4tXn4NBXFutMMQC4vtbTFSXhhKzR73fv0beZEAU=
modernc.org/libc v1.72.3/go.mod h1:dn0dZNnnn1clLyvRxLxYExxiKRZIRENOfqQ8XEeg4Qs=
modernc.org/mathutil v1.7.1 h1:GCZVGXdaN8gTqB1Mf/usp1Y/hSqgI2vAGGP4jZMCxOU=
modernc.org/mathutil v1.7.1/go.mod h1:4p5IwJITfppl0G4sUEDtCr4DthTaT47/N3aT6MhfgJg=
modernc.org/memory v1.11.0 h1:o4QC8aMQzmcwCK3t3Ux/ZHmwFPzE6hf2Y5LbkRs+hbI=
modernc.org/memory v1.11.0/go.mod h1:/JP4VbVC+K5sU2wZi9bHoq2MAkCnrt2r98UGeSK7Mjw=
modernc.org/sqlite v1.50.1 h1:l+cQvn0sd0zJJtfygGHuQJ5AjlrwXmWPw4KP3ZMwr9w=
modernc.org/sqlite v1.50.1/go.mod h1:tcNzv5p84E0skkmJn038y+hWJbLQXQqEnQfeh5r2JLM=
Loading
Loading