diff --git a/.github/ISSUE_TEMPLATE/bug_report.yml b/.github/ISSUE_TEMPLATE/bug_report.yml
new file mode 100644
index 0000000..0e59476
--- /dev/null
+++ b/.github/ISSUE_TEMPLATE/bug_report.yml
@@ -0,0 +1,54 @@
+name: Bug report
+description: Report a problem with enowx-rag
+labels: ["bug"]
+body:
+ - type: markdown
+ attributes:
+ value: Thanks for taking the time to file a bug report!
+ - type: textarea
+ id: what-happened
+ attributes:
+ label: What happened?
+ description: A clear description of the bug, and what you expected instead.
+ placeholder: When I run ... I expected ... but got ...
+ validations:
+ required: true
+ - type: textarea
+ id: repro
+ attributes:
+ label: Steps to reproduce
+ placeholder: |
+ 1. Configure with ...
+ 2. Run `rag_index` / `--serve` ...
+ 3. See error ...
+ validations:
+ required: true
+ - type: dropdown
+ id: vector-store
+ attributes:
+ label: Vector store
+ options:
+ - qdrant
+ - chroma
+ - pgvector
+ - other / not sure
+ validations:
+ required: true
+ - type: dropdown
+ id: embedder
+ attributes:
+ label: Embedder
+ options:
+ - voyage
+ - tei
+ - other / not sure
+ validations:
+ required: true
+ - type: textarea
+ id: environment
+ attributes:
+ label: Environment
+ description: OS, Go version, how you run the server (MCP stdio vs `--serve`), and relevant logs.
+ placeholder: macOS 15, Go 1.26, run via MCP stdio ...
+ validations:
+ required: false
diff --git a/.github/ISSUE_TEMPLATE/config.yml b/.github/ISSUE_TEMPLATE/config.yml
new file mode 100644
index 0000000..b176a57
--- /dev/null
+++ b/.github/ISSUE_TEMPLATE/config.yml
@@ -0,0 +1,8 @@
+blank_issues_enabled: false
+contact_links:
+ - name: Security vulnerability
+ url: https://github.com/enowdev/enowx-rag/security/advisories/new
+ about: Please report security issues privately, not as public issues (see SECURITY.md).
+ - name: Question / discussion
+ url: https://github.com/enowdev/enowx-rag/discussions
+ about: Ask questions and discuss ideas with the community.
diff --git a/.github/ISSUE_TEMPLATE/feature_request.yml b/.github/ISSUE_TEMPLATE/feature_request.yml
new file mode 100644
index 0000000..c3959ef
--- /dev/null
+++ b/.github/ISSUE_TEMPLATE/feature_request.yml
@@ -0,0 +1,24 @@
+name: Feature request
+description: Suggest an idea or improvement for enowx-rag
+labels: ["enhancement"]
+body:
+ - type: textarea
+ id: problem
+ attributes:
+ label: What problem does this solve?
+ description: Describe the use case or pain point before the proposed solution.
+ validations:
+ required: true
+ - type: textarea
+ id: proposal
+ attributes:
+ label: Proposed solution
+ description: What would you like to happen? Include API/config shape if relevant.
+ validations:
+ required: true
+ - type: textarea
+ id: alternatives
+ attributes:
+ label: Alternatives considered
+ validations:
+ required: false
diff --git a/.github/pull_request_template.md b/.github/pull_request_template.md
new file mode 100644
index 0000000..bf4bd49
--- /dev/null
+++ b/.github/pull_request_template.md
@@ -0,0 +1,23 @@
+## Summary
+
+
+
+## Changes
+
+
+
+-
+
+## Testing
+
+
+
+- [ ] `go build ./...` passes (under `mcp-server/`)
+- [ ] `go test ./...` passes
+- [ ] `go vet ./...` passes
+- [ ] `npm run build` passes (if the SPA was touched)
+- [ ] Added/updated tests for the new behavior
+
+## Notes for reviewers
+
+
diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml
new file mode 100644
index 0000000..4918534
--- /dev/null
+++ b/.github/workflows/ci.yml
@@ -0,0 +1,64 @@
+name: CI
+
+on:
+ push:
+ branches: [main]
+ pull_request:
+ branches: [main]
+
+permissions:
+ contents: read
+
+jobs:
+ go:
+ name: Go build & test
+ runs-on: ubuntu-latest
+ defaults:
+ run:
+ working-directory: mcp-server
+ steps:
+ - uses: actions/checkout@v4
+
+ - name: Set up Go
+ uses: actions/setup-go@v5
+ with:
+ go-version: "1.26"
+ cache-dependency-path: mcp-server/go.sum
+
+ # The Go binary embeds web/dist via embed.FS, so a placeholder must
+ # exist for `go build`/`go test` to compile without a full SPA build.
+ - name: Create web/dist placeholder
+ run: |
+ mkdir -p web/dist
+ echo '
enowx-rag' > web/dist/index.html
+
+ - name: go vet
+ run: go vet ./...
+
+ - name: go build
+ run: go build ./...
+
+ - name: go test
+ run: go test ./... -count=1
+
+ web:
+ name: Frontend build
+ runs-on: ubuntu-latest
+ defaults:
+ run:
+ working-directory: mcp-server/web
+ steps:
+ - uses: actions/checkout@v4
+
+ - name: Set up Node
+ uses: actions/setup-node@v4
+ with:
+ node-version: "20"
+ cache: npm
+ cache-dependency-path: mcp-server/web/package-lock.json
+
+ - name: Install dependencies
+ run: npm ci
+
+ - name: Build SPA
+ run: npm run build
diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml
new file mode 100644
index 0000000..02fdc17
--- /dev/null
+++ b/.github/workflows/release.yml
@@ -0,0 +1,59 @@
+name: Release
+
+on:
+ push:
+ tags:
+ - "v*"
+
+permissions:
+ contents: write # create the GitHub Release and upload assets
+
+jobs:
+ goreleaser:
+ name: Build & publish binaries
+ runs-on: ubuntu-latest
+ steps:
+ - uses: actions/checkout@v4
+ with:
+ fetch-depth: 0 # GoReleaser needs full history + tags
+
+ - name: Set up Go
+ uses: actions/setup-go@v5
+ with:
+ go-version: "1.26"
+ cache-dependency-path: mcp-server/go.sum
+
+ - name: Run GoReleaser
+ uses: goreleaser/goreleaser-action@v6
+ with:
+ version: "~> v2"
+ args: release --clean
+ env:
+ GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
+ # Set this secret to publish the Homebrew formula to enowdev/homebrew-tap.
+ # If it's absent the brew step fails; remove the `brews:` block in
+ # .goreleaser.yaml (or add the secret) to make the release green.
+ HOMEBREW_TAP_GITHUB_TOKEN: ${{ secrets.HOMEBREW_TAP_GITHUB_TOKEN }}
+
+ npm:
+ name: Publish npm wrapper
+ runs-on: ubuntu-latest
+ needs: goreleaser # binaries must exist on the Release before npm postinstall can fetch them
+ steps:
+ - uses: actions/checkout@v4
+
+ - name: Set up Node
+ uses: actions/setup-node@v4
+ with:
+ node-version: "20"
+ registry-url: "https://registry.npmjs.org"
+
+ - name: Sync package version to the tag
+ working-directory: npm
+ run: npm version "${GITHUB_REF_NAME#v}" --no-git-tag-version --allow-same-version
+
+ - name: Publish to npm
+ working-directory: npm
+ run: npm publish --access public
+ env:
+ NODE_AUTH_TOKEN: ${{ secrets.NPM_TOKEN }}
diff --git a/.gitignore b/.gitignore
index b09a64e..d952991 100644
--- a/.gitignore
+++ b/.gitignore
@@ -1,7 +1,17 @@
# Binaries
mcp-server/mcp-server
+mcp-server/enowx-rag
+enowx-rag
mcp-server/*.exe
+# GoReleaser output
+/dist/
+
+# npm wrapper: the native binary is downloaded on postinstall, never committed
+npm/bin/enowx-rag
+npm/bin/enowx-rag.exe
+npm/node_modules/
+
# Go
*.log
*.test
diff --git a/.goreleaser.yaml b/.goreleaser.yaml
new file mode 100644
index 0000000..6cd390b
--- /dev/null
+++ b/.goreleaser.yaml
@@ -0,0 +1,101 @@
+# GoReleaser config for enowx-rag.
+# Docs: https://goreleaser.com
+#
+# The Go module lives in ./mcp-server (its go.mod is there), and the web
+# dashboard is embedded from mcp-server/web/dist (committed to the repo), so no
+# npm build is needed here — GoReleaser just cross-compiles the CGO-free binary.
+#
+# Release: git tag v0.1.0 && git push origin v0.1.0
+# The .github/workflows/release.yml workflow runs `goreleaser release` on tags.
+version: 2
+
+project_name: enowx-rag
+
+before:
+ hooks:
+ # Ensure modules are tidy/available before the build.
+ - go -C mcp-server mod download
+
+builds:
+ - id: enowx-rag
+ binary: enowx-rag
+ dir: mcp-server
+ main: ./cmd/mcp-server
+ env:
+ - CGO_ENABLED=0
+ flags:
+ - -trimpath
+ ldflags:
+ - -s -w -X main.version={{ .Version }}
+ goos:
+ - linux
+ - darwin
+ - windows
+ goarch:
+ - amd64
+ - arm64
+ ignore:
+ # Windows on ARM64 is uncommon for a server daemon; skip it.
+ - goos: windows
+ goarch: arm64
+
+archives:
+ - id: default
+ ids:
+ - enowx-rag
+ # v0.1.0_darwin_arm64.tar.gz etc. install.sh depends on this name format.
+ name_template: >-
+ {{ .ProjectName }}_{{ .Version }}_{{ .Os }}_{{ .Arch }}
+ formats:
+ - tar.gz
+ format_overrides:
+ - goos: windows
+ formats:
+ - zip
+ files:
+ - README.md
+ - LICENSE
+ - CHANGELOG.md
+
+checksum:
+ name_template: "checksums.txt"
+
+snapshot:
+ version_template: "{{ incpatch .Version }}-dev"
+
+changelog:
+ # We maintain CHANGELOG.md by hand; keep the release notes from the tag/body.
+ disable: true
+
+release:
+ github:
+ owner: enowdev
+ name: enowx-rag
+ # Draft the release so you can review binaries before publishing.
+ draft: false
+ prerelease: auto
+ name_template: "{{ .Tag }}"
+
+# --- Homebrew tap -----------------------------------------------------------
+# Requires a repo github.com/enowdev/homebrew-tap and a token in the
+# HOMEBREW_TAP_GITHUB_TOKEN secret (a PAT with `repo` scope, or a fine-grained
+# token with contents:write on homebrew-tap). Then: brew install enowdev/tap/enowx-rag
+brews:
+ - name: enowx-rag
+ ids:
+ - default
+ repository:
+ owner: enowdev
+ name: homebrew-tap
+ token: "{{ .Env.HOMEBREW_TAP_GITHUB_TOKEN }}"
+ homepage: "https://github.com/enowdev/enowx-rag"
+ description: "Per-project RAG memory MCP server for AI coding agents"
+ license: "Apache-2.0"
+ commit_author:
+ name: goreleaser-bot
+ email: goreleaser@enowx-rag.local
+ directory: Formula
+ test: |
+ system "#{bin}/enowx-rag", "version"
+ install: |
+ bin.install "enowx-rag"
diff --git a/CHANGELOG.md b/CHANGELOG.md
new file mode 100644
index 0000000..9b4cbf5
--- /dev/null
+++ b/CHANGELOG.md
@@ -0,0 +1,123 @@
+# Changelog
+
+All notable changes to this project are documented in this file.
+
+The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/),
+and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
+
+## [Unreleased]
+
+### Added
+- **One-line install + prebuilt binaries**: releases now ship cross-compiled,
+ CGO-free binaries for macOS/Linux (amd64/arm64) and Windows (amd64) via
+ GoReleaser (`.github/workflows/release.yml` on tag `v*`). Install without a
+ toolchain through the `install.sh` script (`curl -fsSL …/install.sh | sh`),
+ Homebrew (`brew install enowdev/tap/enowx-rag`), npm (`npm install -g
+ enowx-rag`, a thin wrapper that fetches the native binary), `go install
+ …/cmd/mcp-server@latest` (the dashboard is embedded from a committed build),
+ or by downloading a release archive directly. Adds `enowx-rag version`
+ (`--version`), with the version stamped at build time via ldflags.
+- **Settings page** to manage API keys and the admin token from the dashboard:
+ view keys masked (`GET /api/setup/config`), reveal full keys
+ (`/config/reveal`, localhost or admin token), update a key
+ (`POST /api/setup/config`, merged into config.yaml 0600), and generate an
+ admin token (`POST /api/setup/gen-token`, shown once, saved to config). The
+ admin token now takes effect from either `RAG_ADMIN_TOKEN` (precedence) or the
+ saved config, read per-request so a generated token works without a restart.
+- **More MCP tools** (6 → 11): `rag_list_projects` (discover projects + chunk
+ counts), `rag_project_exists`, `rag_list_points` (inspect indexed chunks),
+ `rag_delete_points` (remove stale chunks without a full re-index), and
+ `rag_stats` (projects/chunks/embed-model/latency/tokens). Thin wrappers over
+ existing core.Service methods; available over stdio and remote HTTP.
+- **MCP over HTTP (remote daemon)**: `enowx-rag --serve` now also exposes the MCP
+ server at `/mcp` (Streamable HTTP transport, stateless), so agents can use
+ enowx-rag as a centralized remote daemon — e.g. on a VPS — instead of a local
+ stdio process. It's gated by the same `RAG_ADMIN_TOKEN` bearer as `/api`.
+ Connect a client with `{ "url": ".../mcp", "headers": { "Authorization":
+ "Bearer " } }`. Local stdio mode is unchanged.
+- **Agent setup**: point an AI agent at `GET /api/docs/setup` and it configures
+ enowx-rag for a project, idempotently. `GET /api/setup/probe` reports what's
+ already installed (MCP per client, skill, AGENTS.md block) so finished steps
+ are skipped; `POST /api/setup/write-agents-md` merges an enowx-rag section into
+ the project's AGENTS.md via `` markers (create /
+ append / update, preserving the user's own content). The Install step shows a
+ short copy-paste prompt.
+- **Migration** page + engine: re-embed a project's stored text into a new
+ destination to change embedding model/dimension or move between vector stores
+ (Qdrant/pgvector/Chroma). Raw vectors aren't copied (they're model-specific);
+ the text — stored alongside every chunk — is re-embedded by the destination.
+ `POST /api/migrate` runs asynchronously with live SSE progress; the UI shows a
+ progress bar and offers to delete the source after success.
+- **Cloud import**: pull from an external vector DB and re-embed. Qdrant Cloud is
+ verified (reuses the tested Qdrant provider); Pinecone, Weaviate, and Chroma
+ Cloud connectors are **experimental** (built from vendor docs, mock-tested
+ only — not verified against a live account) and labelled as such in the UI.
+- OpenAI-compatible embedder (`RAG_EMBEDDER=openai`): works with any
+ `/v1/embeddings` API — OpenAI, Together, Jina, Mistral, a local Ollama,
+ LiteLLM, etc. — via `RAG_OPENAI_BASE_URL` / `RAG_OPENAI_MODEL` /
+ `RAG_OPENAI_API_KEY` / `RAG_OPENAI_DIM`. Surfaced in the wizard's Embedding
+ step, which also clarifies that TEI serves any local model.
+- Query metrics: latency (avg/p50/p95), Voyage token usage, and dense/lexical
+ retrieval breakdown, exposed at `GET /api/metrics` and shown on the dashboard.
+ Persisted durably to `~/.enowx-rag/metrics.db` (pure-Go SQLite, no cgo), so
+ metrics survive restarts on any backend.
+- `compress` search option: deterministic near-duplicate result dedup.
+- `enowx-rag setup [--run]` CLI subcommand to generate/run the backend
+ docker-compose (never over HTTP).
+- Efficient project chunk counts (`points/count` / `COUNT(*)` / `/count`),
+ making `/api/projects` and `/api/stats` fast on large Qdrant/pgvector.
+- Qdrant & Chroma now implement project listing, so the dashboard works on all
+ backends, not only pgvector.
+
+### Changed
+- MCP `rag_semantic_search` / `rag_retrieve_context` now accept and apply
+ `hybrid`/`rerank`/`recall`/`compress` (hybrid & rerank default on).
+- Dashboard shows only real, backend-sourced data — removed all mock/hardcoded
+ metrics, dead controls, and the fake auto-setup simulation.
+
+### Security
+- `/api/setup/apply` and `/api/setup/test` require localhost or a valid admin
+ token; `/api/setup/apply` no longer echoes the saved config (API key) back.
+- Removed the deprecated `middleware.RealIP` (X-Forwarded-For spoofing risk).
+
+### Notes
+- The Chroma provider is **experimental** (legacy `/api/v1`, mock-tested only);
+ use Qdrant or pgvector for a supported setup.
+
+## [0.1.0] - 2026-07-12
+
+First tagged release. A per-project RAG memory skill and MCP server for AI
+coding agents, distributed as a single self-contained binary.
+
+### Added
+
+- **MCP server** (stdio) exposing per-project RAG tools: create/delete project,
+ index, retrieve context, semantic search.
+- **Vector store providers**: Qdrant, Chroma, and pgvector, behind a common
+ `rag.Provider` interface (one project = one collection).
+- **Embedding backends**: Voyage AI (with Matryoshka `output_dimension` and
+ `input_type=query`/`document` split) and self-hosted TEI.
+- **Reranking**: Voyage `rerank-2.5` integrated into search (retrieve → rerank →
+ top-k), disable-able and with graceful fallback.
+- **Hybrid search** on pgvector: dense + lexical full-text fused with
+ Reciprocal Rank Fusion (RRF, k=60) over a GIN `tsvector` index.
+- **HTTP + web UI** (`--serve`): chi-based REST API, Server-Sent Events activity
+ stream, and an embedded React dashboard (Overview, Playground, Chunks) served
+ from the single binary via `embed.FS`.
+- **Onboarding wizard**: guided setup for vector store, embedder, and connection
+ testing.
+- **Incremental indexing**: `content_hash` / `chunk_version` metadata so
+ unchanged chunks are skipped on re-index; `embed_model`/`embed_dim` recorded.
+- **Auth**: optional `RAG_ADMIN_TOKEN` protecting `/api/*` with constant-time
+ comparison.
+- **Packaging**: Makefile build pipeline and an all-in-one Docker Compose.
+
+### Security
+
+- SSE event stream (`/api/events`) is same-origin only by default; cross-origin
+ access must be explicitly enabled via `RAG_CORS_ORIGIN`.
+- Reranked result sets are defensively truncated to `k` regardless of the
+ rerank API response.
+
+[Unreleased]: https://github.com/enowdev/enowx-rag/compare/v0.1.0...HEAD
+[0.1.0]: https://github.com/enowdev/enowx-rag/releases/tag/v0.1.0
diff --git a/CODE_OF_CONDUCT.md b/CODE_OF_CONDUCT.md
new file mode 100644
index 0000000..8752c54
--- /dev/null
+++ b/CODE_OF_CONDUCT.md
@@ -0,0 +1,132 @@
+# Contributor Covenant Code of Conduct
+
+## Our Pledge
+
+We as members, contributors, and leaders pledge to make participation in our
+community a harassment-free experience for everyone, regardless of age, body
+size, visible or invisible disability, ethnicity, sex characteristics, gender
+identity and expression, level of experience, education, socio-economic status,
+nationality, personal appearance, race, caste, color, religion, or sexual
+identity and orientation.
+
+We pledge to act and interact in ways that contribute to an open, welcoming,
+diverse, inclusive, and healthy community.
+
+## Our Standards
+
+Examples of behavior that contributes to a positive environment for our
+community include:
+
+* Demonstrating empathy and kindness toward other people
+* Being respectful of differing opinions, viewpoints, and experiences
+* Giving and gracefully accepting constructive feedback
+* Accepting responsibility and apologizing to those affected by our mistakes,
+ and learning from the experience
+* Focusing on what is best not just for us as individuals, but for the overall
+ community
+
+Examples of unacceptable behavior include:
+
+* The use of sexualized language or imagery, and sexual attention or advances of
+ any kind
+* Trolling, insulting or derogatory comments, and personal or political attacks
+* Public or private harassment
+* Publishing others' private information, such as a physical or email address,
+ without their explicit permission
+* Other conduct which could reasonably be considered inappropriate in a
+ professional setting
+
+## Enforcement Responsibilities
+
+Community leaders are responsible for clarifying and enforcing our standards of
+acceptable behavior and will take appropriate and fair corrective action in
+response to any behavior that they deem inappropriate, threatening, offensive,
+or harmful.
+
+Community leaders have the right and responsibility to remove, edit, or reject
+comments, commits, code, wiki edits, issues, and other contributions that are
+not aligned to this Code of Conduct, and will communicate reasons for moderation
+decisions when appropriate.
+
+## Scope
+
+This Code of Conduct applies within all community spaces, and also applies when
+an individual is officially representing the community in public spaces.
+Examples of representing our community include using an official email address,
+posting via an official social media account, or acting as an appointed
+representative at an online or offline event.
+
+## Enforcement
+
+Instances of abusive, harassing, or otherwise unacceptable behavior may be
+reported to the community leaders responsible for enforcement via the contact
+listed on the [maintainer's GitHub profile](https://github.com/enowdev).
+All complaints will be reviewed and investigated promptly and fairly.
+
+All community leaders are obligated to respect the privacy and security of the
+reporter of any incident.
+
+## Enforcement Guidelines
+
+Community leaders will follow these Community Impact Guidelines in determining
+the consequences for any action they deem in violation of this Code of Conduct:
+
+### 1. Correction
+
+**Community Impact**: Use of inappropriate language or other behavior deemed
+unprofessional or unwelcome in the community.
+
+**Consequence**: A private, written warning from community leaders, providing
+clarity around the nature of the violation and an explanation of why the
+behavior was inappropriate. A public apology may be requested.
+
+### 2. Warning
+
+**Community Impact**: A violation through a single incident or series of
+actions.
+
+**Consequence**: A warning with consequences for continued behavior. No
+interaction with the people involved, including unsolicited interaction with
+those enforcing the Code of Conduct, for a specified period of time. This
+includes avoiding interactions in community spaces as well as external channels
+like social media. Violating these terms may lead to a temporary or permanent
+ban.
+
+### 3. Temporary Ban
+
+**Community Impact**: A serious violation of community standards, including
+sustained inappropriate behavior.
+
+**Consequence**: A temporary ban from any sort of interaction or public
+communication with the community for a specified period of time. No public or
+private interaction with the people involved, including unsolicited interaction
+with those enforcing the Code of Conduct, is allowed during this period.
+Violating these terms may lead to a permanent ban.
+
+### 4. Permanent Ban
+
+**Community Impact**: Demonstrating a pattern of violation of community
+standards, including sustained inappropriate behavior, harassment of an
+individual, or aggression toward or disparagement of classes of individuals.
+
+**Consequence**: A permanent ban from any sort of public interaction within the
+community.
+
+## Attribution
+
+This Code of Conduct is adapted from the [Contributor Covenant][homepage],
+version 2.1, available at
+[https://www.contributor-covenant.org/version/2/1/code_of_conduct.html][v2.1].
+
+Community Impact Guidelines were inspired by
+[Mozilla's code of conduct enforcement ladder][Mozilla CoC].
+
+For answers to common questions about this code of conduct, see the FAQ at
+[https://www.contributor-covenant.org/faq][FAQ]. Translations are available at
+[https://www.contributor-covenant.org/translations][translations].
+
+[homepage]: https://www.contributor-covenant.org
+[v2.1]: https://www.contributor-covenant.org/version/2/1/code_of_conduct.html
+[Mozilla CoC]: https://github.com/mozilla/diversity
+[FAQ]: https://www.contributor-covenant.org/faq
+[translations]: https://www.contributor-covenant.org/translations
diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md
new file mode 100644
index 0000000..bd5727b
--- /dev/null
+++ b/CONTRIBUTING.md
@@ -0,0 +1,83 @@
+# Contributing to enowx-rag
+
+Thanks for your interest in improving **enowx-rag** — a per-project RAG memory
+skill and MCP server for AI coding agents. Contributions of all sizes are
+welcome, from typo fixes to new vector-store providers.
+
+## Ways to contribute
+
+- **Report a bug** — open an issue with steps to reproduce, expected vs actual
+ behavior, and your config (vector store, embedder, OS).
+- **Suggest a feature** — open an issue describing the use case before writing
+ code, so we can agree on the approach.
+- **Pick up a `good first issue`** — issues labeled this way are scoped for
+ newcomers and have context in the description.
+- **Improve docs** — the README, skill guide, and `docs/` are all fair game.
+
+## Development setup
+
+The server is a Go module under `mcp-server/` with an embedded React SPA under
+`mcp-server/web/`.
+
+```bash
+# Prerequisites: Go 1.26+, Node 20+, (optional) Docker for local Qdrant/TEI
+
+git clone https://github.com/enowdev/enowx-rag.git
+cd enowx-rag/mcp-server
+
+# Build everything (frontend + Go binary)
+make build # from repo root: builds web/dist then the binary
+
+# Run the test suite
+go test ./...
+```
+
+To run the server with the web dashboard locally:
+
+```bash
+# Local backend (no API key needed)
+docker compose up -d qdrant tei-embedding
+RAG_VECTOR_STORE=qdrant RAG_EMBEDDER=tei \
+ RAG_QDRANT_URL=http://localhost:6333 RAG_TEI_URL=http://localhost:8081 \
+ ./enowx-rag --serve
+
+# then open http://localhost:7777
+```
+
+See the [README](README.md) for the full list of environment variables.
+
+## Pull request checklist
+
+Before opening a PR, please make sure:
+
+- [ ] `go build ./...` and `go test ./...` pass under `mcp-server/`
+- [ ] `npm run build` passes under `mcp-server/web/` (if you touched the SPA)
+- [ ] New behavior has a test that locks it in
+- [ ] Commit messages are descriptive (we follow `type: summary`, e.g.
+ `fix:`, `feat:`, `docs:`, `test:`)
+- [ ] You've run `go vet ./...`
+
+Keep PRs focused — one logical change per PR is much easier to review.
+
+## Code style
+
+- **Go**: standard `gofmt`; match the surrounding code's naming and comment
+ density. The `rag.Provider` interface is the contract for vector stores —
+ new backends implement it (and optionally `HybridSearcher`, `Reranker`).
+- **TypeScript/React**: follow the existing component structure under
+ `web/src/`. Keep the true-black flat design tokens in `styles/tokens.css`.
+
+## Reporting security issues
+
+Please **do not** open a public issue for security vulnerabilities. See
+[SECURITY.md](SECURITY.md) for how to report them privately.
+
+## Code of Conduct
+
+This project follows the [Contributor Covenant](CODE_OF_CONDUCT.md). By
+participating, you agree to uphold it.
+
+## License
+
+By contributing, you agree that your contributions will be licensed under the
+[Apache License 2.0](LICENSE) that covers this project.
diff --git a/LICENSE b/LICENSE
new file mode 100644
index 0000000..d645695
--- /dev/null
+++ b/LICENSE
@@ -0,0 +1,202 @@
+
+ Apache License
+ Version 2.0, January 2004
+ http://www.apache.org/licenses/
+
+ TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
+
+ 1. Definitions.
+
+ "License" shall mean the terms and conditions for use, reproduction,
+ and distribution as defined by Sections 1 through 9 of this document.
+
+ "Licensor" shall mean the copyright owner or entity authorized by
+ the copyright owner that is granting the License.
+
+ "Legal Entity" shall mean the union of the acting entity and all
+ other entities that control, are controlled by, or are under common
+ control with that entity. For the purposes of this definition,
+ "control" means (i) the power, direct or indirect, to cause the
+ direction or management of such entity, whether by contract or
+ otherwise, or (ii) ownership of fifty percent (50%) or more of the
+ outstanding shares, or (iii) beneficial ownership of such entity.
+
+ "You" (or "Your") shall mean an individual or Legal Entity
+ exercising permissions granted by this License.
+
+ "Source" form shall mean the preferred form for making modifications,
+ including but not limited to software source code, documentation
+ source, and configuration files.
+
+ "Object" form shall mean any form resulting from mechanical
+ transformation or translation of a Source form, including but
+ not limited to compiled object code, generated documentation,
+ and conversions to other media types.
+
+ "Work" shall mean the work of authorship, whether in Source or
+ Object form, made available under the License, as indicated by a
+ copyright notice that is included in or attached to the work
+ (an example is provided in the Appendix below).
+
+ "Derivative Works" shall mean any work, whether in Source or Object
+ form, that is based on (or derived from) the Work and for which the
+ editorial revisions, annotations, elaborations, or other modifications
+ represent, as a whole, an original work of authorship. For the purposes
+ of this License, Derivative Works shall not include works that remain
+ separable from, or merely link (or bind by name) to the interfaces of,
+ the Work and Derivative Works thereof.
+
+ "Contribution" shall mean any work of authorship, including
+ the original version of the Work and any modifications or additions
+ to that Work or Derivative Works thereof, that is intentionally
+ submitted to Licensor for inclusion in the Work by the copyright owner
+ or by an individual or Legal Entity authorized to submit on behalf of
+ the copyright owner. For the purposes of this definition, "submitted"
+ means any form of electronic, verbal, or written communication sent
+ to the Licensor or its representatives, including but not limited to
+ communication on electronic mailing lists, source code control systems,
+ and issue tracking systems that are managed by, or on behalf of, the
+ Licensor for the purpose of discussing and improving the Work, but
+ excluding communication that is conspicuously marked or otherwise
+ designated in writing by the copyright owner as "Not a Contribution."
+
+ "Contributor" shall mean Licensor and any individual or Legal Entity
+ on behalf of whom a Contribution has been received by Licensor and
+ subsequently incorporated within the Work.
+
+ 2. Grant of Copyright License. Subject to the terms and conditions of
+ this License, each Contributor hereby grants to You a perpetual,
+ worldwide, non-exclusive, no-charge, royalty-free, irrevocable
+ copyright license to reproduce, prepare Derivative Works of,
+ publicly display, publicly perform, sublicense, and distribute the
+ Work and such Derivative Works in Source or Object form.
+
+ 3. Grant of Patent License. Subject to the terms and conditions of
+ this License, each Contributor hereby grants to You a perpetual,
+ worldwide, non-exclusive, no-charge, royalty-free, irrevocable
+ (except as stated in this section) patent license to make, have made,
+ use, offer to sell, sell, import, and otherwise transfer the Work,
+ where such license applies only to those patent claims licensable
+ by such Contributor that are necessarily infringed by their
+ Contribution(s) alone or by combination of their Contribution(s)
+ with the Work to which such Contribution(s) was submitted. If You
+ institute patent litigation against any entity (including a
+ cross-claim or counterclaim in a lawsuit) alleging that the Work
+ or a Contribution incorporated within the Work constitutes direct
+ or contributory patent infringement, then any patent licenses
+ granted to You under this License for that Work shall terminate
+ as of the date such litigation is filed.
+
+ 4. Redistribution. You may reproduce and distribute copies of the
+ Work or Derivative Works thereof in any medium, with or without
+ modifications, and in Source or Object form, provided that You
+ meet the following conditions:
+
+ (a) You must give any other recipients of the Work or
+ Derivative Works a copy of this License; and
+
+ (b) You must cause any modified files to carry prominent notices
+ stating that You changed the files; and
+
+ (c) You must retain, in the Source form of any Derivative Works
+ that You distribute, all copyright, patent, trademark, and
+ attribution notices from the Source form of the Work,
+ excluding those notices that do not pertain to any part of
+ the Derivative Works; and
+
+ (d) If the Work includes a "NOTICE" text file as part of its
+ distribution, then any Derivative Works that You distribute must
+ include a readable copy of the attribution notices contained
+ within such NOTICE file, excluding those notices that do not
+ pertain to any part of the Derivative Works, in at least one
+ of the following places: within a NOTICE text file distributed
+ as part of the Derivative Works; within the Source form or
+ documentation, if provided along with the Derivative Works; or,
+ within a display generated by the Derivative Works, if and
+ wherever such third-party notices normally appear. The contents
+ of the NOTICE file are for informational purposes only and
+ do not modify the License. You may add Your own attribution
+ notices within Derivative Works that You distribute, alongside
+ or as an addendum to the NOTICE text from the Work, provided
+ that such additional attribution notices cannot be construed
+ as modifying the License.
+
+ You may add Your own copyright statement to Your modifications and
+ may provide additional or different license terms and conditions
+ for use, reproduction, or distribution of Your modifications, or
+ for any such Derivative Works as a whole, provided Your use,
+ reproduction, and distribution of the Work otherwise complies with
+ the conditions stated in this License.
+
+ 5. Submission of Contributions. Unless You explicitly state otherwise,
+ any Contribution intentionally submitted for inclusion in the Work
+ by You to the Licensor shall be under the terms and conditions of
+ this License, without any additional terms or conditions.
+ Notwithstanding the above, nothing herein shall supersede or modify
+ the terms of any separate license agreement you may have executed
+ with Licensor regarding such Contributions.
+
+ 6. Trademarks. This License does not grant permission to use the trade
+ names, trademarks, service marks, or product names of the Licensor,
+ except as required for reasonable and customary use in describing the
+ origin of the Work and reproducing the content of the NOTICE file.
+
+ 7. Disclaimer of Warranty. Unless required by applicable law or
+ agreed to in writing, Licensor provides the Work (and each
+ Contributor provides its Contributions) on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
+ implied, including, without limitation, any warranties or conditions
+ of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
+ PARTICULAR PURPOSE. You are solely responsible for determining the
+ appropriateness of using or redistributing the Work and assume any
+ risks associated with Your exercise of permissions under this License.
+
+ 8. Limitation of Liability. In no event and under no legal theory,
+ whether in tort (including negligence), contract, or otherwise,
+ unless required by applicable law (such as deliberate and grossly
+ negligent acts) or agreed to in writing, shall any Contributor be
+ liable to You for damages, including any direct, indirect, special,
+ incidental, or consequential damages of any character arising as a
+ result of this License or out of the use or inability to use the
+ Work (including but not limited to damages for loss of goodwill,
+ work stoppage, computer failure or malfunction, or any and all
+ other commercial damages or losses), even if such Contributor
+ has been advised of the possibility of such damages.
+
+ 9. Accepting Warranty or Additional Liability. While redistributing
+ the Work or Derivative Works thereof, You may choose to offer,
+ and charge a fee for, acceptance of support, warranty, indemnity,
+ or other liability obligations and/or rights consistent with this
+ License. However, in accepting such obligations, You may act only
+ on Your own behalf and on Your sole responsibility, not on behalf
+ of any other Contributor, and only if You agree to indemnify,
+ defend, and hold each Contributor harmless for any liability
+ incurred by, or claims asserted against, such Contributor by reason
+ of your accepting any such warranty or additional liability.
+
+ END OF TERMS AND CONDITIONS
+
+ APPENDIX: How to apply the Apache License to your work.
+
+ To apply the Apache License to your work, attach the following
+ boilerplate notice, with the fields enclosed by brackets "[]"
+ replaced with your own identifying information. (Don't include
+ the brackets!) The text should be enclosed in the appropriate
+ comment syntax for the file format. We also recommend that a
+ file or class name and description of purpose be included on the
+ same "printed page" as the copyright notice for easier
+ identification within third-party archives.
+
+ Copyright [yyyy] [name of copyright owner]
+
+ Licensed under the Apache License, Version 2.0 (the "License");
+ you may not use this file except in compliance with the License.
+ You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
diff --git a/Makefile b/Makefile
new file mode 100644
index 0000000..ec3cfce
--- /dev/null
+++ b/Makefile
@@ -0,0 +1,63 @@
+.PHONY: web build dev-web dev-api mcp test vet clean placeholder
+
+# Go binary output name
+BINARY := enowx-rag
+
+# Directories
+MCP_DIR := mcp-server
+WEB_DIR := mcp-server/web
+
+# Go build flags
+GO_FLAGS := -trimpath
+
+# web: Build the React SPA into web/dist using npm ci + npm run build.
+# This ensures a clean install of dependencies followed by a production build.
+web:
+ cd $(WEB_DIR) && npm ci && npm run build
+
+# build: Full build. Depends on web target to ensure web/dist exists for embed.FS.
+# Produces a single self-contained binary at the repository root.
+build: web
+ cd $(MCP_DIR) && go build $(GO_FLAGS) -o ../$(BINARY) ./cmd/mcp-server
+
+# mcp: Build only the MCP server binary without building the web UI.
+# Uses a placeholder web/dist if it doesn't exist, so embed.FS compiles.
+# The resulting binary works in stdio MCP mode (no --serve UI).
+mcp:
+ @if [ ! -d $(WEB_DIR)/dist ]; then \
+ mkdir -p $(WEB_DIR)/dist; \
+ echo 'enowx-rag UI not built. Run make web to build the SPA.' > $(WEB_DIR)/dist/index.html; \
+ echo "Created web/dist placeholder for MCP-only build"; \
+ fi
+ cd $(MCP_DIR) && go build $(GO_FLAGS) -o mcp-server ./cmd/mcp-server
+
+# dev-web: Start Vite dev server for frontend development.
+dev-web:
+ cd $(WEB_DIR) && npm run dev
+
+# dev-api: Start the Go HTTP server in serve mode for backend development.
+dev-api:
+ cd $(MCP_DIR) && go run ./cmd/mcp-server --serve --addr :7777
+
+# test: Run all Go tests.
+test:
+ cd $(MCP_DIR) && go test ./... -count=1
+
+# vet: Run go vet for static analysis.
+vet:
+ cd $(MCP_DIR) && go vet ./...
+
+# placeholder: Create a minimal web/dist placeholder so go build succeeds
+# without a full web build. Useful for MCP-only development.
+placeholder:
+ @if [ ! -f $(WEB_DIR)/dist/index.html ]; then \
+ mkdir -p $(WEB_DIR)/dist; \
+ echo 'enowx-rag UI not built. Run make web to build the SPA.' > $(WEB_DIR)/dist/index.html; \
+ echo "Created web/dist placeholder"; \
+ else \
+ echo "web/dist/index.html already exists"; \
+ fi
+
+# clean: Remove build artifacts.
+clean:
+ rm -f $(MCP_DIR)/$(BINARY) $(BINARY) $(MCP_DIR)/mcp-server
diff --git a/NOTICE b/NOTICE
new file mode 100644
index 0000000..83fd022
--- /dev/null
+++ b/NOTICE
@@ -0,0 +1,17 @@
+enowx-rag
+Copyright 2026 Enow Dev.
+
+This product includes software developed by Enow Dev.
+(https://github.com/enowdev/enowx-rag).
+
+Licensed under the Apache License, Version 2.0 (the "License");
+you may not use this file except in compliance with the License.
+You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+Unless required by applicable law or agreed to in writing, software
+distributed under the License is distributed on an "AS IS" BASIS,
+WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+See the License for the specific language governing permissions and
+limitations under the License.
diff --git a/README.md b/README.md
index 8916ad3..2b1d5d4 100644
--- a/README.md
+++ b/README.md
@@ -1,10 +1,277 @@
# enowx-rag
+[](https://github.com/enowdev/enowx-rag/actions/workflows/ci.yml)
+[](LICENSE)
+[](mcp-server/go.mod)
+
Per-project RAG memory MCP server. Each project gets its own vector collection, so an LLM can index context about a codebase and retrieve it quickly.
+
+
+The server runs in **two modes** from a single binary:
+
+- **MCP stdio mode** (default): `enowx-rag` — talks to AI coding tools over stdio via the Model Context Protocol.
+- **HTTP serve mode**: `enowx-rag --serve` — starts an HTTP server with a REST API, SSE event stream, an embedded React dashboard, **and MCP over HTTP at `/mcp`** so agents can connect to enowx-rag as a remote daemon (e.g. on a VPS).
+
+### Remote daemon (MCP over HTTP)
+
+Run `enowx-rag --serve` on a host and connect agents remotely. Set `RAG_ADMIN_TOKEN` to secure it — it gates both `/api/*` and `/mcp` with a bearer token (unset = no auth, only for trusted networks). Put a TLS reverse proxy in front for public use.
+
+```bash
+RAG_ADMIN_TOKEN=$(openssl rand -hex 32) ./enowx-rag --serve --addr :7777
+```
+
+Point an MCP client at the daemon:
+
+```json
+{
+ "mcpServers": {
+ "enowx-rag": {
+ "url": "https://rag.example.com/mcp",
+ "headers": { "Authorization": "Bearer " }
+ }
+ }
+}
+```
+
+All MCP tools work identically to local stdio mode. See the **Docs → Remote / daemon** page for details.
+
+---
+
+## Why enowx-rag?
+
+LLM coding agents forget everything between sessions and only see the files you
+paste. enowx-rag gives an agent a **persistent, per-project memory** it can
+search — so it recalls your architecture decisions, gotchas, and conventions
+without you re-explaining them every time.
+
+- **Per-project isolation.** Each project is its own vector collection
+ (`project_`), so an agent working in one repo never retrieves context
+ leaked from another. Index a codebase once; query it in isolation.
+- **Retrieval that actually finds things.** Dense semantic search, optional
+ **hybrid** (dense + lexical RRF), **reranking** (Voyage `rerank-2.5`), and
+ near-duplicate **compression** — tunable per query. See the ranked results,
+ scores, and matched snippets live in the Playground.
+- **Persistent memory, durable metrics.** Design decisions and facts survive
+ across sessions and restarts; query latency (p50/p95), token usage, and
+ retrieval breakdown are persisted to a local SQLite DB (pure-Go, no cgo).
+- **Bring your own stack.** Vector stores: **Qdrant, pgvector, Chroma**.
+ Embedders: **Voyage AI, any OpenAI-compatible `/v1/embeddings` API, or
+ self-hosted TEI**. Swap models or move stores later with the built-in
+ **Migration** tool (re-embeds stored text — no re-scraping your codebase).
+- **One binary, two modes.** Local **MCP stdio** for your editor, or
+ `--serve` for a **remote daemon** (REST API + dashboard + MCP over HTTP),
+ gated by a bearer token — run it centrally on a VPS for a whole team.
+- **No lock-in, no toolchain.** A single self-contained binary with the
+ dashboard embedded. Install via `curl | sh`, Homebrew, npm, `go install`, or
+ a direct download. CGO-free, so it runs anywhere.
+
+### Dashboard preview
+
+The embedded dashboard (`enowx-rag --serve`) — real data from a live instance:
+
+| Overview | Retrieval Playground |
+|---|---|
+| [](docs/screenshots/overview.png) | [](docs/screenshots/playground.png) |
+| **Chunks** | **Migration** |
+| [](docs/screenshots/chunks.png) | [](docs/screenshots/migration.png) |
+| **Docs** | **Settings** |
+| [](docs/screenshots/docs.png) | [](docs/screenshots/settings.png) |
+
+---
+
+## Install
+
+enowx-rag ships as a single self-contained binary (the dashboard is embedded) —
+no runtime dependencies, no toolchain required. Pick whichever fits:
+
+**Install script (macOS/Linux)** — downloads the prebuilt binary for your platform:
+
+```bash
+curl -fsSL https://raw.githubusercontent.com/enowdev/enowx-rag/main/install.sh | sh
+```
+
+Pin a version or install location with env vars:
+`ENOWX_VERSION=v0.1.0 ENOWX_INSTALL_DIR=~/.local/bin`.
+
+**Homebrew (macOS/Linux):**
+
+```bash
+brew install enowdev/tap/enowx-rag
+```
+
+**npm** (installs the native binary via a thin wrapper; handy if you already use Node):
+
+```bash
+npm install -g enowx-rag
+```
+
+**Go** (if you have Go 1.26+; builds from source, the dashboard is embedded from a committed build):
+
+```bash
+go install github.com/enowdev/enowx-rag/mcp-server/cmd/mcp-server@latest
+```
+
+**Prebuilt binaries** for macOS/Linux (amd64/arm64) and Windows (amd64) are
+attached to every [GitHub Release](https://github.com/enowdev/enowx-rag/releases).
+
+Verify with `enowx-rag version`, then run `enowx-rag --serve` and open
+`http://localhost:7777`.
+
+---
+
+## Quick Start (one-command deploy)
+
+### From source
+
+```bash
+git clone https://github.com/enowdev/enowx-rag.git
+cd enowx-rag
+make build && ./enowx-rag --serve
+```
+
+This builds the React SPA, compiles the Go binary (with the SPA embedded), and starts the HTTP server on port 7777. Open `http://localhost:7777` in your browser.
+
+Set your embedding provider before starting:
+
+```bash
+export RAG_VOYAGE_API_KEY=your-voyage-api-key
+make build && ./enowx-rag --serve
+```
+
+### With Docker (all-in-one)
+
+```bash
+export RAG_VOYAGE_API_KEY=your-voyage-api-key
+docker compose -f docker-compose.all-in-one.yml up -d
+```
+
+The enowx-rag container serves both the API and UI on port 7777. Qdrant starts automatically as the vector store. See [Docker all-in-one](#docker-all-in-one) below for details.
+
+---
+
+## Two modes of operation
+
+### MCP stdio mode (default)
+
+Run without `--serve` to use the MCP stdio transport. This is the mode you configure in your AI coding tool (Claude Code, Cursor, Cline, etc.):
+
+```bash
+./enowx-rag
+```
+
+MCP tools available: `rag_create_project`, `rag_delete_project`, `rag_index`, `rag_index_project`, `rag_semantic_search`, `rag_retrieve_context`, `rag_list_projects`, `rag_project_exists`, `rag_list_points`, `rag_delete_points`, `rag_stats`. Logs go to stderr (stdout is the MCP protocol stream).
+
+### HTTP serve mode
+
+Run with `--serve` to start the HTTP API + embedded UI:
+
+```bash
+# Default port 7777
+./enowx-rag --serve
+
+# Custom port
+./enowx-rag --serve --addr :8080
+
+# With admin token auth (protects /api/* endpoints)
+RAG_ADMIN_TOKEN=your-secret ./enowx-rag --serve
+```
+
+| Flag | Default | Description |
+| --- | --- | --- |
+| `--serve` | `false` | Run as HTTP server instead of stdio MCP |
+| `--addr` | `:7777` | HTTP listen address (only used with `--serve`) |
+
+---
+
+## REST API endpoints
+
+When running in `--serve` mode, the following REST endpoints are available:
+
+| Method | Endpoint | Description |
+| --- | --- | --- |
+| `GET` | `/api/projects` | List all projects with chunk counts |
+| `GET` | `/api/projects/{id}` | Get project detail (404 if not found) |
+| `GET` | `/api/projects/{id}/points` | List chunks (supports `?source_file=`, `?offset=`, `?limit=`) |
+| `DELETE` | `/api/projects/{id}/points/{pointId}` | Delete a single chunk |
+| `POST` | `/api/projects/{id}/reindex` | Re-index a project directory (body: `{"directory": "/path"}`) |
+| `DELETE` | `/api/projects/{id}` | Delete a project collection |
+| `POST` | `/api/search` | Search (body: `{"project_id", "query", "k", "recall", "hybrid", "rerank", "compress"}`) |
+| `GET` | `/api/stats` | Aggregate stats (total projects, chunks, embed model) |
+| `GET` | `/api/metrics` | Query metrics: latency (avg/p50/p95), token usage, backend, `persistent` flag |
+| `GET` | `/api/events` | SSE stream of realtime events (index, search, etc.) |
+| `POST` | `/api/setup/test` | Test connectivity (localhost or admin token required) |
+| `POST` | `/api/setup/apply` | Save config to `~/.enowx-rag/config.yaml` (localhost or admin token required) |
+| `GET` | `/api/setup/status` | Check if config exists |
+| `POST` | `/api/setup/install-mcp` | Install the MCP server into a client's config (merge + backup) |
+| `GET` | `/api/setup/probe` | Report what's installed (MCP per client, skill, AGENTS.md block) for idempotent setup |
+| `POST` | `/api/setup/write-agents-md` | Merge the enowx-rag block into a project's AGENTS.md (localhost/admin token) |
+| `POST` | `/api/migrate` | Migrate/re-embed a project into a new destination (async, SSE progress) |
+| `GET` | `/api/docs/setup` | Markdown setup instructions for an AI agent to follow |
+
+Non-API routes serve the embedded React SPA (client-side routing for `/playground`, `/chunks`, `/migration`, `/setup`, etc.).
+
+**Agent-driven setup.** Paste a short prompt into your AI coding agent telling it
+to read `GET /api/docs/setup` and follow it. The agent probes what's already in
+place (`GET /api/setup/probe`) and installs only the missing pieces — the MCP
+server (`POST /api/setup/install-mcp`), the skill, and the project's AGENTS.md
+block (`POST /api/setup/write-agents-md`, merged idempotently). The Install step
+of the wizard shows the copy-paste prompt.
+
+**Query metrics** are recorded for every search and exposed at `/api/metrics`:
+latency percentiles, Voyage token usage (embed + rerank), and — for hybrid
+searches on pgvector — the dense/lexical retrieval breakdown. Metrics are
+persisted durably to a local SQLite file (`~/.enowx-rag/metrics.db`, pure-Go,
+no external service), so they survive restarts on any backend. If the file can't
+be opened, metrics fall back to in-memory (`"persistent": false`).
+
+Example:
+
+```bash
+curl http://localhost:7777/api/projects
+curl -X POST http://localhost:7777/api/search \
+ -H 'Content-Type: application/json' \
+ -d '{"project_id": "my-project", "query": "how does auth work", "k": 5, "hybrid": true, "rerank": true}'
+```
+
+---
+
+## Optional admin token auth
+
+When the `RAG_ADMIN_TOKEN` environment variable is set, all `/api/*` endpoints require an `Authorization: Bearer ` header. The SPA and static assets are always served without auth.
+
+```bash
+# Enable auth
+export RAG_ADMIN_TOKEN=my-secret-token
+./enowx-rag --serve
+
+# API requests must include the token
+curl -H "Authorization: Bearer my-secret-token" http://localhost:7777/api/projects
+```
+
+When `RAG_ADMIN_TOKEN` is **not set**, no authentication is required (default behavior). This makes it easy to run locally without auth and enable it only when exposing the server to the internet.
+
+---
+
+## Docker all-in-one
+
+The `docker-compose.all-in-one.yml` file at the repository root starts the enowx-rag server (API + UI) alongside Qdrant in a single command:
+
+```bash
+export RAG_VOYAGE_API_KEY=your-voyage-api-key
+docker compose -f docker-compose.all-in-one.yml up -d
+```
+
+- enowx-rag container serves API + SPA on port **7777**
+- Qdrant starts automatically on port 6333
+- PostgreSQL (pgvector) and TEI are available as commented-out services in the compose file
+- Set `RAG_ADMIN_TOKEN` in the compose environment to enable auth
+
+To use pgvector instead of Qdrant, uncomment the `postgres` service and switch `RAG_VECTOR_STORE` to `pgvector` in the compose file.
+
---
-## Quick setup (copy-paste this to your AI agent)
+## Quick setup for AI agents (copy-paste this to your AI agent)
There are two setup paths. Pick the one that matches your situation:
@@ -448,15 +715,24 @@ Each project has its own collection: `project_PROJECT_ID`. Do not mix project me
```
enowx-rag/
-├── AGENTS.md # Universal agent install guide (this repo)
-├── CLAUDE.md # Claude-family quick reference (this repo)
-├── README.md # This file
-├── mcp-server/ # Go MCP server (stdio transport)
-│ ├── cmd/mcp-server
-│ ├── pkg/rag # Provider interface + Qdrant, Chroma, pgvector
-│ ├── Dockerfile
-│ └── docker-compose.yml
-└── skill/ # Factory Droid skill
+├── AGENTS.md # Universal agent install guide (this repo)
+├── CLAUDE.md # Claude-family quick reference (this repo)
+├── README.md # This file
+├── Makefile # Build pipeline: make web, make build, make dev-*
+├── docker-compose.all-in-one.yml # All-in-one: enowx-rag + Qdrant (optional PG, TEI)
+├── mcp-server/ # Go server (MCP stdio + HTTP serve modes)
+│ ├── cmd/mcp-server/main.go # Entry point: --serve / --addr flags
+│ ├── pkg/rag # Provider interface + Qdrant, Chroma, pgvector, Voyage, TEI
+│ ├── pkg/core # Service layer (shared by MCP + HTTP)
+│ ├── pkg/config # Config file (~/.enowx-rag/config.yaml)
+│ ├── pkg/httpapi # Chi router, REST handlers, SSE, admin auth
+│ ├── pkg/indexer # File indexer with content hashing
+│ ├── web/ # React SPA (Vite + React + TypeScript + Tailwind)
+│ │ ├── dist/ # Build output (embedded via go:embed)
+│ │ └── embed.go # //go:embed all:dist
+│ ├── Dockerfile # Multi-stage: builds SPA + Go binary
+│ └── docker-compose.yml # Backend-only compose (Qdrant + TEI)
+└── skill/ # Factory Droid skill
├── enowx-rag.md
└── templates/
└── AGENTS.md
@@ -466,10 +742,49 @@ enowx-rag/
| Vector store | Embedder | Status |
| --- | --- | --- |
-| Qdrant | TEI (self-hosted) | Ready |
-| Qdrant | Voyage AI | Ready |
-| Chroma | TEI (self-hosted) | Ready |
-| pgvector | TEI (self-hosted) | Ready |
+| Qdrant | TEI (self-hosted) | Supported |
+| Qdrant | Voyage AI | Supported |
+| pgvector | TEI (self-hosted) | Supported |
+| pgvector | Voyage AI | Supported — recommended (hybrid search + retrieval breakdown) |
+| Chroma | TEI / Voyage AI | Experimental — see note below |
+
+**Feature support by backend:**
+
+| Feature | Qdrant | pgvector | Chroma |
+| --- | :---: | :---: | :---: |
+| Index / semantic search / delete | ✅ | ✅ | ⚠️ |
+| Project list + stats (dashboard) | ✅ | ✅ | ⚠️ |
+| Hybrid search (dense + lexical RRF) | — | ✅ | — |
+| Retrieval breakdown (dense/lexical) | — | ✅ | — |
+| Token metrics (Voyage) | ✅ | ✅ | ✅ |
+
+> **Chroma is experimental.** The provider targets Chroma's legacy `/api/v1`
+> REST API and has only been tested against mocks, not a live server. Chroma
+> ≥ 0.6 moved to `/api/v2` (tenant/database paths, UUID-addressed collections),
+> so the current provider may not work against modern Chroma. Use **Qdrant** or
+> **pgvector** for a supported setup. Contributions to port Chroma to `/api/v2`
+> are welcome.
+
+## Migration
+
+The dashboard's **Migration** page (and `POST /api/migrate`) re-embeds a
+project's stored text into a new destination. Because embedding vectors are
+model-specific and not portable, migration re-embeds from text (which enowx-rag
+stores alongside every chunk) rather than copying raw vectors. Use it to:
+
+- **Change embedding model or dimension** — pick a new embedder/model/dim; the
+ destination collection is re-embedded from the source text.
+ (For pgvector, a new dimension requires a **new table name** — all projects in
+ one pgvector table share a fixed vector dimension.)
+- **Move between vector stores** — e.g. Qdrant → pgvector.
+- **Import from an external cloud vector DB** — **Qdrant Cloud** is verified;
+ **Pinecone**, **Weaviate**, and **Chroma Cloud** connectors are *experimental*
+ (built from vendor docs, mock-tested only — not verified against a live
+ account) and are labelled as such in the UI.
+
+Migrations run asynchronously with live progress over SSE. The source project is
+never removed automatically; after a successful migration the UI offers an
+explicit "Delete source" action.
## Embedding options
@@ -489,9 +804,34 @@ Set `RAG_EMBEDDER=voyage` (or just set `RAG_VOYAGE_API_KEY` — it auto-detects)
}
```
-### Option B: TEI (self-hosted)
+### Option B: OpenAI-compatible (any `/v1/embeddings` API)
-Runs a local Text Embeddings Inference container. Requires Docker and ~1 GB of RAM.
+Works with OpenAI, Together, Jina, Mistral, DeepInfra, LiteLLM, a local Ollama, and anything else that speaks the OpenAI embeddings protocol. Point `RAG_OPENAI_BASE_URL` at the provider and set the model. Leave the API key empty for a local/no-auth endpoint. `RAG_OPENAI_DIM` is optional (`0` = auto-detect; models like `text-embedding-3-*` honor it).
+
+```json
+"env": {
+ "RAG_VECTOR_STORE": "qdrant",
+ "RAG_QDRANT_URL": "http://localhost:6333",
+ "RAG_EMBEDDER": "openai",
+ "RAG_OPENAI_BASE_URL": "https://api.openai.com/v1",
+ "RAG_OPENAI_API_KEY": "sk-...",
+ "RAG_OPENAI_MODEL": "text-embedding-3-small"
+}
+```
+
+Local Ollama example (no API key needed):
+
+```json
+"env": {
+ "RAG_EMBEDDER": "openai",
+ "RAG_OPENAI_BASE_URL": "http://localhost:11434/v1",
+ "RAG_OPENAI_MODEL": "nomic-embed-text"
+}
+```
+
+### Option C: TEI (self-hosted)
+
+Runs a local Text Embeddings Inference container — serve **any** local embedding model (BGE, GTE, E5, nomic-embed, …); enowx-rag only needs its URL. Requires Docker and ~1 GB of RAM.
```bash
cd mcp-server && docker compose up -d qdrant tei-embedding
@@ -508,16 +848,27 @@ cd mcp-server && docker compose up -d qdrant tei-embedding
## Environment variables
+All configuration is via environment variables (or config file at `~/.enowx-rag/config.yaml`). Priority: **env var > config file > default**.
+
| Variable | Default | Description |
| --- | --- | --- |
-| `RAG_VECTOR_STORE` | `qdrant` | `qdrant`, `chroma`, `pgvector` |
-| `RAG_EMBEDDER` | `voyage` | `voyage`, `tei` (falls back to `tei` if `RAG_VOYAGE_API_KEY` is not set) |
+| `RAG_VECTOR_STORE` | `qdrant` | Vector store: `qdrant`, `chroma`, or `pgvector` |
+| `RAG_EMBEDDER` | `voyage` | Embedding provider: `voyage`, `openai` (any OpenAI-compatible API), or `tei` (auto-detects: falls back to `tei` if `RAG_VOYAGE_API_KEY` is not set) |
| `RAG_QDRANT_URL` | `http://localhost:6333` | Qdrant REST URL |
+| `RAG_QDRANT_API_KEY` | *(empty)* | Optional Qdrant API key (for Qdrant Cloud) |
| `RAG_CHROMA_URL` | `http://localhost:8000` | Chroma REST URL |
-| `RAG_PGVECTOR_DSN` | - | Postgres connection string |
-| `RAG_TEI_URL` | `http://localhost:8081` | Text Embeddings Inference URL |
-| `RAG_VOYAGE_API_KEY` | - | Voyage AI API key (required when `RAG_EMBEDDER=voyage`) |
-| `RAG_VOYAGE_MODEL` | `voyage-4` | Voyage AI model name |
+| `RAG_PGVECTOR_DSN` | *(empty)* | PostgreSQL connection string (e.g., `postgresql://user@localhost:5432/dbname`). Required when `RAG_VECTOR_STORE=pgvector` |
+| `RAG_TEI_URL` | `http://localhost:8081` | Text Embeddings Inference URL. Used when `RAG_EMBEDDER=tei` |
+| `RAG_VOYAGE_API_KEY` | *(empty)* | Voyage AI API key (required when `RAG_EMBEDDER=voyage`). Get a free key at [voyageai.com](https://voyageai.com) (200M free tokens with voyage-4) |
+| `RAG_VOYAGE_MODEL` | `voyage-4` | Voyage AI embedding model name |
+| `RAG_OPENAI_BASE_URL` | `https://api.openai.com/v1` | OpenAI-compatible embeddings base URL (used when `RAG_EMBEDDER=openai`). Point at OpenAI, Together, Jina, a local Ollama (`http://localhost:11434/v1`), etc. |
+| `RAG_OPENAI_API_KEY` | *(empty)* | API key for the OpenAI-compatible endpoint. Leave empty for local/no-auth endpoints |
+| `RAG_OPENAI_MODEL` | *(empty)* | Embedding model name (required when `RAG_EMBEDDER=openai`), e.g. `text-embedding-3-small`, `nomic-embed-text` |
+| `RAG_OPENAI_DIM` | `0` | Output dimension for the OpenAI embedder. `0` = auto-detect; models like `text-embedding-3-*` honor an explicit value |
+| `RAG_VECTOR_DIM` | `1024` | Embedding vector dimension (matches voyage-4 default). Override only if using a different model with a different dimension |
+| `RAG_RERANKER_MODEL` | *(empty)* | Reranker model name (e.g., `rerank-2.5`). When set and `RAG_VOYAGE_API_KEY` is available, reranking is enabled for search |
+| `RAG_ADMIN_TOKEN` | *(empty)* | Optional admin token. When set, all `/api/*` endpoints require `Authorization: Bearer ` header. When unset, no auth is required |
+| `RAG_CORS_ORIGIN` | *(empty)* | Optional CORS origin for the SSE event stream (`/api/events`). When set (e.g. `*` or `https://app.example.com`), it becomes the `Access-Control-Allow-Origin` header. When unset, no CORS header is sent and the stream stays same-origin only |
## Tools
@@ -530,7 +881,11 @@ cd mcp-server && docker compose up -d qdrant tei-embedding
## Notes
-- The MCP server uses stdio transport by default.
+- The server has two modes: **MCP stdio** (default, no flags) and **HTTP serve** (`--serve`). Both use the same core service layer.
+- In stdio mode, logs go to stderr (stdout is the MCP protocol stream).
+- In serve mode, the HTTP server provides a REST API, SSE event stream, and an embedded React dashboard UI.
+- `make build` produces a single self-contained binary with the SPA embedded via `embed.FS`.
- Each project gets its own collection/index: `project_`.
-- The existing Coolify `robloxkit-rag` stack exposes Qdrant on `localhost:6333` and TEI on `localhost:8081` when Docker/Colima is running.
+- Config priority: environment variable > config file (`~/.enowx-rag/config.yaml`) > built-in defaults.
+- Optional admin token auth (`RAG_ADMIN_TOKEN`) protects `/api/*` endpoints when set. No auth when unset.
diff --git a/SECURITY.md b/SECURITY.md
new file mode 100644
index 0000000..9119015
--- /dev/null
+++ b/SECURITY.md
@@ -0,0 +1,49 @@
+# Security Policy
+
+## Reporting a vulnerability
+
+We take the security of enowx-rag seriously. If you discover a vulnerability,
+please report it **privately** — do not open a public issue.
+
+**How to report:**
+
+- Use GitHub's [private vulnerability reporting](https://github.com/enowdev/enowx-rag/security/advisories/new)
+ (Security → Advisories → Report a vulnerability), or
+- Email the maintainer at the address listed on the
+ [GitHub profile](https://github.com/enowdev).
+
+Please include:
+
+- A description of the vulnerability and its impact
+- Steps to reproduce (proof-of-concept if possible)
+- Affected version / commit
+- Your suggested remediation, if any
+
+## What to expect
+
+- We aim to acknowledge your report within **3 business days**.
+- We will keep you informed as we investigate and work on a fix.
+- Once a fix is released, we will credit you in the advisory unless you prefer
+ to remain anonymous.
+
+## Scope
+
+This project is an MCP server and skill that handles:
+
+- API keys for embedding/reranking providers (e.g. Voyage AI) via environment
+ variables
+- An optional admin token (`RAG_ADMIN_TOKEN`) protecting the HTTP `/api/*`
+ endpoints
+- Content indexed from user codebases, stored in a vector database
+
+Areas of particular interest for security reports include: authentication
+bypass on the HTTP API, cross-project data leakage, SSRF/injection in the
+vector-store or embedding requests, and secret exposure in logs or responses.
+
+## Hardening notes for operators
+
+- Set `RAG_ADMIN_TOKEN` when exposing the `--serve` HTTP API beyond localhost.
+- The SSE event stream (`/api/events`) is same-origin only unless you set
+ `RAG_CORS_ORIGIN` — leave it unset in single-origin deployments.
+- Never commit `RAG_VOYAGE_API_KEY` or other secrets; pass them via the
+ environment or your MCP client config.
diff --git a/docker-compose.all-in-one.yml b/docker-compose.all-in-one.yml
new file mode 100644
index 0000000..5ffe7c7
--- /dev/null
+++ b/docker-compose.all-in-one.yml
@@ -0,0 +1,119 @@
+# docker-compose.all-in-one.yml
+# All-in-one deployment: enowx-rag server (API + UI) with optional backend services.
+#
+# Usage:
+# docker compose -f docker-compose.all-in-one.yml up -d
+#
+# The enowx-rag container serves both the REST API and the embedded React SPA
+# on port 7777. Backend services (Qdrant + TEI or PostgreSQL) start
+# automatically.
+#
+# To enable optional admin token auth, set RAG_ADMIN_TOKEN in the environment
+# or uncomment the line below. When set, all /api/* endpoints require an
+# Authorization: Bearer header.
+#
+# Environment variables can be customized in the enowx-rag service section below.
+
+services:
+ # --- enowx-rag server (API + embedded UI) ---
+ enowx-rag:
+ build:
+ context: ./mcp-server
+ dockerfile: Dockerfile
+ container_name: enowx-rag
+ ports:
+ - "7777:7777"
+ environment:
+ # --- Vector store: choose one ---
+ RAG_VECTOR_STORE: "qdrant"
+ # RAG_VECTOR_STORE: "pgvector"
+
+ # --- Embedder: voyage (recommended) or tei ---
+ RAG_EMBEDDER: "voyage"
+ RAG_VOYAGE_API_KEY: "${RAG_VOYAGE_API_KEY:-}"
+ RAG_VOYAGE_MODEL: "voyage-4"
+ RAG_RERANKER_MODEL: "rerank-2.5"
+
+ # --- Qdrant config (when RAG_VECTOR_STORE=qdrant) ---
+ RAG_QDRANT_URL: "http://qdrant:6333"
+
+ # --- pgvector config (when RAG_VECTOR_STORE=pgvector) ---
+ # RAG_PGVECTOR_DSN: "postgresql://enowxrag:enowxrag@postgres:5432/enowxrag"
+
+ # --- TEI config (when RAG_EMBEDDER=tei) ---
+ # RAG_TEI_URL: "http://tei-embedding:80"
+
+ # --- Optional admin token auth (uncomment to enable) ---
+ # RAG_ADMIN_TOKEN: "change-me-to-a-random-string"
+ depends_on:
+ qdrant:
+ condition: service_healthy
+ restart: unless-stopped
+ # ENTRYPOINT in Dockerfile is ["./enowx-rag"], so command must NOT
+ # include the binary name — only the flags to pass to it.
+ command: ["--serve", "--addr", ":7777"]
+ healthcheck:
+ # When RAG_ADMIN_TOKEN is set, /api/* requires an Authorization header.
+ # Use a shell-form test so the header is sent only when the token is set.
+ test: ["CMD-SHELL", "wget --spider -q --header=\"$${RAG_ADMIN_TOKEN:+Authorization: Bearer $$RAG_ADMIN_TOKEN}\" http://localhost:7777/api/stats"]
+ interval: 30s
+ timeout: 10s
+ retries: 3
+
+ # --- Qdrant vector store ---
+ qdrant:
+ image: qdrant/qdrant:latest
+ container_name: enowx-rag-qdrant
+ ports:
+ - "6333:6333"
+ volumes:
+ - qdrant-data:/qdrant/storage
+ restart: unless-stopped
+ healthcheck:
+ test: ["CMD", "wget", "--spider", "-q", "http://localhost:6333/healthz"]
+ interval: 30s
+ timeout: 10s
+ retries: 3
+
+ # --- Optional: PostgreSQL with pgvector (uncomment to use) ---
+ # postgres:
+ # image: pgvector/pgvector:pg16
+ # container_name: enowx-rag-postgres
+ # ports:
+ # - "5432:5432"
+ # environment:
+ # POSTGRES_USER: enowxrag
+ # POSTGRES_PASSWORD: enowxrag
+ # POSTGRES_DB: enowxrag
+ # volumes:
+ # - postgres-data:/var/lib/postgresql/data
+ # restart: unless-stopped
+ # healthcheck:
+ # test: ["CMD-SHELL", "pg_isready -U enowxrag"]
+ # interval: 30s
+ # timeout: 10s
+ # retries: 3
+
+ # --- Optional: TEI embedder (uncomment when RAG_EMBEDDER=tei) ---
+ # tei-embedding:
+ # image: ghcr.io/huggingface/text-embeddings-inference:cpu-1.7
+ # container_name: enowx-rag-tei
+ # ports:
+ # - "8081:80"
+ # environment:
+ # MODEL_ID: BAAI/bge-small-en-v1.5
+ # RUST_LOG: info
+ # MAX_BATCH_TOKENS: "16384"
+ # volumes:
+ # - tei-data:/data
+ # restart: unless-stopped
+ # healthcheck:
+ # test: ["CMD", "wget", "--spider", "-q", "http://localhost/health"]
+ # interval: 30s
+ # timeout: 10s
+ # retries: 3
+
+volumes:
+ qdrant-data:
+ # postgres-data:
+ # tei-data:
diff --git a/docs/HANDOFF.md b/docs/HANDOFF.md
new file mode 100644
index 0000000..60db8a0
--- /dev/null
+++ b/docs/HANDOFF.md
@@ -0,0 +1,218 @@
+# HANDOFF — enowx-rag
+
+> Untuk agent (Claude Code / Droid / Cursor / dll) yang melanjutkan pekerjaan di project ini.
+> Baca ini **dulu**, lalu [`PLANNING.md`](./PLANNING.md) untuk spesifikasi implementasi.
+> Terakhir diperbarui: 2026-07-11.
+
+---
+
+## 1. Apa ini
+
+`enowx-rag` = **per-project RAG memory MCP server** (Go, stdio transport). Tiap project punya collection vektor terisolasi (`project_`) sehingga LLM bisa meng-index konteks codebase dan meretrieve-nya cepat.
+
+**Sedang dikembangkan** menjadi platform RAG dengan UI (dashboard + retrieval playground + onboarding wizard) — lihat `PLANNING.md`. Kode UI/HTTP **belum ada**; yang ada sekarang murni MCP server.
+
+- Repo: https://github.com/enowdev/enowx-rag
+- Lokal: `/Users/enowdev/Project/enowx-rag`
+- Bahasa: Go 1.26 (module `github.com/enowdev/enowx-rag`)
+- Binary: `mcp-server/mcp-server` (hasil `go build ./cmd/mcp-server`)
+
+---
+
+## 2. PENTING — project ID untuk RAG project ini
+
+Saat memakai RAG memory untuk **project enowx-rag sendiri**, gunakan:
+
+```
+PROJECT_ID = enowx-rag
+```
+
+Collection: `project_enowx-rag`. Sudah ada isinya (17 file, 76 chunk per 2026-07-11).
+
+---
+
+## 3. Cara pakai RAG memory (WAJIB, tiap sesi)
+
+Project ini memakai MCP server-nya sendiri untuk memori. Alur wajib:
+
+### Sebelum coding / menjawab pertanyaan arsitektur
+Panggil MCP tool:
+```
+rag_retrieve_context(project_id="enowx-rag", query="")
+```
+Baca konteks yang kembali. Kalau relevan, pakai untuk membentuk jawaban/rencana. Kalau kosong/tak relevan, lanjut normal.
+
+### Setelah menyelesaikan pekerjaan
+1. Ringkas apa yang diubah.
+2. Sinkronkan file ke RAG:
+ ```
+ rag_index_project(project_id="enowx-rag", directory="/Users/enowdev/Project/enowx-rag")
+ ```
+ Ini menangani file baru, edit, dan penghapusan otomatis.
+3. Simpan fakta/keputusan penting yang tak terlihat dari kode:
+ ```
+ rag_index(project_id="enowx-rag", documents=[{content:"...", meta:{type:"decision"}}])
+ ```
+
+### Tag metadata yang dipakai
+`type:architecture`, `type:decision`, `type:api`, `type:bugfix`, `type:howto`, `type:snippet`. Jaga chunk pendek & satu ide per chunk.
+
+---
+
+## 4. MCP tools yang tersedia
+
+| Tool | Fungsi | Argumen utama |
+|---|---|---|
+| `rag_create_project` | Buat collection project (aman dipanggil ulang) | `project_id` |
+| `rag_delete_project` | Hapus collection + semua memori | `project_id` |
+| `rag_index` | Index dokumen manual (fakta/keputusan) | `project_id`, `documents[]` |
+| `rag_index_project` | Scan direktori & auto-index semua file kode/teks (handle insert + delete) | `project_id`, `directory` |
+| `rag_semantic_search` | Semantic search, kembalikan chunk + skor | `project_id`, `query`, `limit` |
+| `rag_retrieve_context` | String konteks ringkas untuk LLM (gabungan top chunk) | `project_id`, `query`, `limit` |
+
+Catatan implementasi:
+- `rag_index_project` skip `node_modules`, `.git`, `vendor`, `dist`, `build`, dll (lihat `defaultIgnores` di `pkg/indexer/indexer.go`), skip file > 500KB, chunkSize 1500 char.
+- Stale reconcile per `source_dir` (base name direktori) — meng-index dua direktori berbeda ke project sama **tidak** saling menghapus.
+
+---
+
+## 5. Skill
+
+Skill (`skill/enowx-rag.md`, ~20KB) adalah panduan setup/onboarding yang bisa dipasang ke tool apa pun.
+
+- **Terpasang di:** `~/.factory/skills/enowx-rag/skill.md` (Factory Droid global).
+- **Install ulang / ke tool lain:**
+ ```bash
+ mkdir -p ~/.factory/skills/enowx-rag
+ cp /Users/enowdev/Project/enowx-rag/skill/enowx-rag.md ~/.factory/skills/enowx-rag/skill.md
+ ```
+ Untuk tool lain, taruh di direktori skill tool tersebut (mis. `.agents/skills/enowx-rag/skill.md`). Jangan asumsikan `~/.factory` untuk tool non-Droid.
+- Skill berisi dua path: **Option A** (setup penuh dari nol) & **Option B** (onboard project baru saat MCP sudah terpasang). Detail lengkap di `README.md`.
+
+---
+
+## 6. Setup MCP server (kalau belum jalan)
+
+### Config MCP (Voyage — direkomendasikan)
+Claude Code (`~/.claude.json` atau `.mcp.json`):
+```json
+{
+ "mcpServers": {
+ "enowx-rag": {
+ "command": "/Users/enowdev/Project/enowx-rag/mcp-server/mcp-server",
+ "env": {
+ "RAG_VECTOR_STORE": "qdrant",
+ "RAG_QDRANT_URL": "http://localhost:6333",
+ "RAG_VOYAGE_API_KEY": "your-voyage-api-key",
+ "RAG_VOYAGE_MODEL": "voyage-4"
+ }
+ }
+ }
+}
+```
+
+### Build binary
+```bash
+cd /Users/enowdev/Project/enowx-rag/mcp-server
+go build ./cmd/mcp-server
+```
+
+### Backend lokal (kalau tak pakai Voyage/Qdrant cloud)
+```bash
+cd /Users/enowdev/Project/enowx-rag/mcp-server
+docker compose up -d qdrant tei-embedding
+# verify
+curl -f http://localhost:6333/healthz
+curl -f http://localhost:8081/health
+```
+
+Format config untuk tool lain (Cursor, Zed, Windsurf, Codex, dll) ada lengkap di `README.md` §4.
+
+---
+
+## 7. Environment variables
+
+| Variable | Default | Keterangan |
+|---|---|---|
+| `RAG_VECTOR_STORE` | `qdrant` | `qdrant` \| `chroma` \| `pgvector` |
+| `RAG_EMBEDDER` | `voyage` | `voyage` \| `tei` (fallback ke `tei` jika `RAG_VOYAGE_API_KEY` kosong) |
+| `RAG_QDRANT_URL` | `http://localhost:6333` | Qdrant REST URL |
+| `RAG_QDRANT_API_KEY` | — | opsional, Qdrant cloud |
+| `RAG_CHROMA_URL` | `http://localhost:8000` | Chroma REST URL |
+| `RAG_PGVECTOR_DSN` | — | Postgres connection string |
+| `RAG_TEI_URL` | `http://localhost:8081` | TEI URL |
+| `RAG_VOYAGE_API_KEY` | — | wajib saat `RAG_EMBEDDER=voyage` |
+| `RAG_VOYAGE_MODEL` | `voyage-4` | model Voyage |
+| `RAG_VECTOR_DIM` | (dari model) | override dimensi vektor |
+
+---
+
+## 8. Peta kode (di mana mengubah apa)
+
+Semua path relatif ke `mcp-server/`.
+
+| Ingin ubah… | Ke file… |
+|---|---|
+| Tool MCP / entry point / config env | `cmd/mcp-server/main.go` |
+| Kontrak provider/embedder | `pkg/rag/provider.go` |
+| Logika pgvector (+hybrid nanti) | `pkg/rag/pgvector.go` |
+| Logika Qdrant | `pkg/rag/qdrant.go` |
+| Embedding Voyage (fix dim/query nanti) | `pkg/rag/voyage.go` |
+| Embedding TEI lokal | `pkg/rag/tei.go` |
+| Scan direktori / chunking / incremental sync | `pkg/indexer/indexer.go` |
+| Backend lokal (Qdrant+TEI) | `docker-compose.yml` |
+
+**Gotcha kode yang sudah diketahui** (detail + fix di `PLANNING.md` §4):
+- `voyage.go`: `VectorSize()` hardcoded 1024; `EmbedQuery` (input_type=query) ada tapi belum dipakai `SemanticSearch`; `output_dimension` tak dikirim.
+- Belum ada metadata versioning (`embed_model`/`embed_dim`/`content_hash`).
+
+---
+
+## 9. Konvensi & aturan project
+
+- **Jangan ubah signature `Provider` interface** — hanya extend (tambah interface opsional seperti `QueryEmbedder`/`Reranker`).
+- **MCP stdio harus tetap jalan tanpa perubahan perilaku.** Fitur baru (HTTP/UI) di belakang flag `--serve`. Env var menang atas config file.
+- **1 binary.** UI di-embed via `embed.FS`. Build: `web/dist` dulu, baru `go build` (Makefile `build` depend `web`).
+- **Embedding tak boleh dicampur** antar model/dimensi dalam satu collection → ganti = re-index penuh.
+- **Log ke stderr, bukan stdout** di mode stdio (stdout dipakai protokol MCP). Lihat `fmt.Fprintf(os.Stderr, ...)` di `main.go`.
+- **Desain UI true-black flat, zero gradient** — jangan reuse look RobloxKit (glass/gradient). Token di `PLANNING.md` §10.
+
+---
+
+## 10. Build & test
+
+```bash
+cd /Users/enowdev/Project/enowx-rag/mcp-server
+go build ./cmd/mcp-server # build
+go vet ./... # static check
+go test ./... # test (jika ada)
+```
+
+Git: branch default `main`, remote `origin` = GitHub `enowdev/enowx-rag`. Commit/push **hanya jika diminta user**.
+
+---
+
+## 11. Status saat ini & langkah berikutnya
+
+Sudah beres:
+- [x] Project ter-index ke RAG (`project_enowx-rag`).
+- [x] Skill terpasang (`~/.factory/skills/enowx-rag/skill.md`).
+- [x] `AGENTS.md` + `CLAUDE.md` di root.
+- [x] Planning detail: `docs/PLANNING.md`.
+- [x] Mockup dashboard (approved): `docs/mockups/dashboard.html`.
+
+Belum:
+- [ ] Mockup onboarding wizard (`docs/mockups/onboarding.html`).
+- [ ] Implementasi Fase 0–5 (lihat `PLANNING.md`).
+
+**Mulai dari mana:** `PLANNING.md` §4 (Fase 0 — fix Voyage + metadata + service layer). Itu murah, berdampak besar, dan prasyarat semua fase lain. Urutan rekomendasi: Fase 0 → 4 → 1 → 3 → 2 → 5.
+
+---
+
+## 12. Dokumen terkait
+
+- [`PLANNING.md`](./PLANNING.md) — spesifikasi implementasi lengkap + contoh kode.
+- [`mockups/dashboard.html`](./mockups/dashboard.html) — referensi visual UI (approved).
+- [`../README.md`](../README.md) — setup guide lengkap semua tool.
+- [`../skill/enowx-rag.md`](../skill/enowx-rag.md) — skill (template AGENTS.md/CLAUDE.md).
+- [`../AGENTS.md`](../AGENTS.md) — panduan install universal.
diff --git a/docs/PLANNING.md b/docs/PLANNING.md
new file mode 100644
index 0000000..103aa5f
--- /dev/null
+++ b/docs/PLANNING.md
@@ -0,0 +1,790 @@
+# enowx-rag — Planning: Advanced RAG Platform + UI
+
+> Status: rencana implementasi (belum dikerjakan). Dokumen ini adalah spesifikasi untuk agent yang akan mengimplementasikan.
+> Terakhir diperbarui: 2026-07-11.
+> Baca juga: [`HANDOFF.md`](./HANDOFF.md) (cara pakai skill/MCP/RAG untuk project ini) dan [`mockups/dashboard.html`](./mockups/dashboard.html) (referensi visual UI yang sudah di-approve).
+
+Rencana menaikkan `enowx-rag` dari MCP server (stdio, headless) menjadi **platform RAG self-host open-source** dengan UI: dashboard, retrieval playground, dan **onboarding wizard** (pilih embedding + vector DB, local/cloud, set endpoint, auto-setup) — **tanpa membuang** MCP server yang sudah ada.
+
+---
+
+## Daftar isi
+
+1. [Keputusan terkunci](#0-keputusan-yang-sudah-dikunci)
+2. [Kondisi existing (peta kode)](#1-kondisi-existing-peta-kode-nyata)
+3. [Arsitektur target](#2-arsitektur-target)
+4. [Struktur folder target](#3-struktur-folder-target)
+5. [Fase 0 — Refactor & fix fondasi (dengan kode)](#4-fase-0--refactor--fix-fondasi)
+6. [Fase 1 — HTTP API (dengan kode)](#5-fase-1--http-api-layer)
+7. [Fase 2 — Onboarding Wizard (dengan kode)](#6-fase-2--onboarding-wizard)
+8. [Fase 3 — Dashboard & Playground (dengan kode)](#7-fase-3--dashboard--playground)
+9. [Fase 4 — Kualitas RAG: rerank + hybrid (dengan kode)](#8-fase-4--kualitas-rag)
+10. [Fase 5 — Polish & distribusi](#9-fase-5--polish--distribusi)
+11. [Kriteria UI (design tokens)](#10-kriteria-ui--design-tokens)
+12. [Risiko & gotcha](#11-risiko--gotcha)
+13. [Status pengerjaan](#12-status-pengerjaan)
+
+---
+
+## 0. Keputusan yang sudah dikunci
+
+| Aspek | Keputusan | Alasan |
+|---|---|---|
+| Target | Open-source self-host showcase | Orang bisa self-host; fokus DX & kemudahan deploy |
+| Onboarding | Wizard: pilih embedding + vector DB, local/cloud, set endpoint, auto-setup | Nilai jual utama |
+| Embedding default | Voyage-4 @ 1024-dim (pluggable) | Terbukti hemat (53.9M token → 10+ project besar) & akurat |
+| Vector store (UI penuh) | pgvector direkomendasikan; tetap multi-provider | SQL bebas → dashboard & hybrid gampang |
+| UI stack | React + Vite + Tailwind + shadcn/ui (baseColor neutral) + framer-motion (hemat) + lucide-react | Selaras ekosistem React user; di-embed ke Go binary |
+| Distribusi | **1 binary** — SPA build → `embed.FS` → `go build` | Sesuai filosofi existing |
+| Realtime | SSE (Server-Sent Events) | One-way cukup; native `net/http` |
+| Desain | **True-black flat, zero gradient** (lihat §10) | Selera user; dev-tool |
+
+---
+
+## 1. Kondisi existing (peta kode nyata)
+
+Semua path relatif ke `mcp-server/`.
+
+| File | Isi | Catatan penting |
+|---|---|---|
+| `cmd/mcp-server/main.go` | Entry point, `Config`, `buildProvider()`, 6 MCP tool | Transport **stdio saja**. `main()` panggil `server.Run(ctx, &mcp.StdioTransport{})` |
+| `pkg/rag/provider.go` | `Provider` interface + `EmbeddingClient` interface + `Document`/`Result`/`PointInfo` | Interface bersih, titik extend utama |
+| `pkg/rag/pgvector.go` | `PGVectorProvider` — single table `project_memory`, HNSW cosine | `SemanticSearch` pakai `Embed` (document type), **belum** `EmbedQuery` |
+| `pkg/rag/qdrant.go` | `QdrantProvider` — REST, per-project collection, optional API key | `vectorDim` dari `embedder.VectorSize()` |
+| `pkg/rag/chroma.go` | `ChromaProvider` | — |
+| `pkg/rag/voyage.go` | `VoyageEmbeddingClient` — batch 128, `input_type` document/query | ⚠️ `VectorSize()` **hardcoded return 1024**; `EmbedQuery` ada tapi tak dipakai; **tak kirim `output_dimension`** |
+| `pkg/rag/tei.go` | `TEIEmbeddingClient` — batch 32, `/embed`, `VectorSize()` via `/info` | Fallback lokal |
+| `pkg/indexer/indexer.go` | `Indexer.IndexProject()` — walk dir, chunk, incremental sync via `source_dir`/`source_file` | chunkSize default 1500 (dari main.go), stale reconcile per `source_dir` |
+| `docker-compose.yml` | qdrant + tei-embedding + mcp-server | TEI model `BAAI/bge-small-en-v1.5` (384-dim) |
+
+### Provider interface (existing — JANGAN diubah signature-nya, hanya di-extend)
+
+```go
+// pkg/rag/provider.go
+type Provider interface {
+ CreateCollection(ctx context.Context, projectID string) error
+ DeleteCollection(ctx context.Context, projectID string) error
+ Index(ctx context.Context, projectID string, docs []Document) error
+ SemanticSearch(ctx context.Context, projectID, query string, limit int) ([]Result, error)
+ Embed(ctx context.Context, text string) ([]float32, error)
+ DeletePoints(ctx context.Context, projectID string, pointIDs []string) error
+ ListPointIDs(ctx context.Context, projectID string, metaFilter map[string]string) ([]string, error)
+ ListPoints(ctx context.Context, projectID string, metaFilter map[string]string) ([]PointInfo, error)
+ Close() error
+}
+```
+
+### 6 MCP tool existing
+`rag_create_project`, `rag_delete_project`, `rag_index`, `rag_semantic_search`, `rag_retrieve_context`, `rag_index_project`.
+
+### Gap ke visi
+- ❌ Tidak ada HTTP server / API / UI.
+- ❌ Tidak ada reranker.
+- ❌ Tidak ada hybrid search (pgvector belum pakai `tsvector`).
+- ⚠️ `Voyage.VectorSize()` hardcoded 1024; `EmbedQuery` (input_type=query) belum dipakai di `SemanticSearch`; `output_dimension` (Matryoshka) tak dikirim.
+- ❌ Metadata versioning (`embed_model`, `embed_dim`, `chunk_version`, `content_hash`) belum ada.
+- ❌ Config cuma env var; belum ada config file/wizard.
+
+---
+
+## 2. Arsitektur target
+
+```
+┌─────────────────────────────────────────────┐
+│ pkg/rag core (Go) │
+│ Provider iface · Embedder · Indexer · Rerank │
+└───────────────┬──────────────┬───────────────┘
+ │ │
+ ┌─────────▼───┐ ┌──────▼──────────────┐
+ │ MCP (stdio) │ │ HTTP server (chi) │
+ │ (existing) │ │ REST + SSE + embed │
+ └─────────────┘ └──────┬──────────────┘
+ │ embed.FS
+ ┌──────▼──────┐
+ │ React SPA │ (Vite build → dist → embedded)
+ │ wizard·dash │
+ └─────────────┘
+```
+
+**Prinsip:** MCP dan HTTP dua-duanya adapter tipis di atas core yang sama. `go build` tetap hasilkan **1 binary**.
+
+**Mode jalan:**
+- `enowx-rag` (default) → stdio MCP (seperti sekarang, tak berubah).
+- `enowx-rag --serve [--addr :7777]` → HTTP + UI.
+
+---
+
+## 3. Struktur folder target
+
+```
+mcp-server/
+├── cmd/
+│ └── mcp-server/
+│ └── main.go # tambah flag --serve; pisah runStdio() / runHTTP()
+├── pkg/
+│ ├── rag/ # (existing) provider + embedder
+│ │ ├── provider.go
+│ │ ├── pgvector.go # + hybrid (tsvector + RRF)
+│ │ ├── qdrant.go
+│ │ ├── chroma.go
+│ │ ├── voyage.go # fix VectorSize/EmbedQuery/output_dimension
+│ │ ├── tei.go
+│ │ └── rerank.go # BARU: Voyage reranker client
+│ ├── indexer/ # (existing) + content_hash + versioning
+│ │ └── indexer.go
+│ ├── config/ # BARU: load/save ~/.enowx-rag/config.yaml
+│ │ └── config.go
+│ ├── core/ # BARU: service layer dipakai MCP & HTTP
+│ │ └── service.go
+│ └── httpapi/ # BARU: chi router, SSE, embed FS
+│ ├── server.go
+│ ├── handlers.go
+│ ├── sse.go
+│ └── setup.go # wizard endpoints
+├── web/ # BARU: React SPA (Vite)
+│ ├── package.json
+│ ├── vite.config.ts
+│ ├── index.html
+│ ├── src/
+│ │ ├── main.tsx
+│ │ ├── App.tsx
+│ │ ├── lib/api.ts
+│ │ ├── lib/sse.ts
+│ │ ├── styles/tokens.css # design token true-black flat (dari §10)
+│ │ ├── components/ui/ # shadcn components
+│ │ ├── pages/Overview.tsx
+│ │ ├── pages/Playground.tsx
+│ │ ├── pages/Chunks.tsx
+│ │ └── pages/onboarding/ # wizard steps
+│ └── dist/ # hasil build → di-embed
+├── web/embed.go # BARU: //go:embed dist
+├── Makefile # BARU: web-build → go-build
+├── docker-compose.yml
+└── Dockerfile
+```
+
+---
+
+## 4. Fase 0 — Refactor & fix fondasi
+
+Prasyarat semua fase lain. Murah, dampak besar.
+
+### 4.1 Fix Voyage: Matryoshka + input_type=query
+
+**Masalah** (`pkg/rag/voyage.go`):
+- `VectorSize()` hardcoded `return 1024` → tak bisa ganti dimensi.
+- `EmbedQuery` (input_type=query) sudah ada tapi tak dipanggil oleh provider.
+- `output_dimension` tak dikirim ke API.
+
+**Perbaikan** — tambah field `Dim` dan kirim `output_dimension`:
+
+```go
+// pkg/rag/voyage.go
+type VoyageEmbeddingClient struct {
+ APIKey string
+ Model string
+ Dim int // BARU: 256/512/1024/2048 (Matryoshka). 0 = default model.
+ client *http.Client
+}
+
+func NewVoyageEmbeddingClient(apiKey, model string, dim int) *VoyageEmbeddingClient {
+ if model == "" {
+ model = "voyage-4"
+ }
+ if dim == 0 {
+ dim = 1024 // sweet spot untuk voyage-4 + HNSW pgvector
+ }
+ return &VoyageEmbeddingClient{
+ APIKey: apiKey, Model: model, Dim: dim,
+ client: &http.Client{Timeout: 120 * time.Second},
+ }
+}
+
+type voyageRequest struct {
+ Input []string `json:"input"`
+ Model string `json:"model"`
+ InputType string `json:"input_type,omitempty"`
+ OutputDimension int `json:"output_dimension,omitempty"` // BARU
+}
+
+func (c *VoyageEmbeddingClient) embedBatch(ctx context.Context, texts []string, inputType string) ([][]float32, error) {
+ body, _ := json.Marshal(voyageRequest{
+ Input: texts,
+ Model: c.Model,
+ InputType: inputType,
+ OutputDimension: c.Dim, // BARU
+ })
+ // ... sisanya sama
+}
+
+// VectorSize sekarang mengembalikan dimensi yang benar.
+func (c *VoyageEmbeddingClient) VectorSize() int {
+ if c.Dim > 0 {
+ return c.Dim
+ }
+ return 1024
+}
+```
+
+**Pakai `EmbedQuery` di provider.** Tambah interface opsional lalu deteksi di `SemanticSearch`:
+
+```go
+// pkg/rag/provider.go — tambah interface opsional
+type QueryEmbedder interface {
+ EmbedQuery(ctx context.Context, text string) ([]float32, error)
+}
+```
+
+```go
+// pkg/rag/pgvector.go — di SemanticSearch, ganti embed query:
+func (p *PGVectorProvider) queryVector(ctx context.Context, query string) ([]float32, error) {
+ if qe, ok := p.embedder.(QueryEmbedder); ok {
+ return qe.EmbedQuery(ctx, query) // input_type=query → retrieval lebih akurat
+ }
+ vecs, err := p.embedder.Embed(ctx, []string{query})
+ if err != nil {
+ return nil, err
+ }
+ return vecs[0], nil
+}
+```
+
+**Update `main.go`** — teruskan `RAG_VECTOR_DIM` ke konstruktor:
+
+```go
+// cmd/mcp-server/main.go, di buildProvider()
+case "voyage":
+ if cfg.VoyageAPIKey == "" {
+ return nil, fmt.Errorf("RAG_VOYAGE_API_KEY is required for voyage embedder")
+ }
+ embedder = rag.NewVoyageEmbeddingClient(cfg.VoyageAPIKey, cfg.VoyageModel, cfg.VectorDim)
+```
+
+⚠️ **Konsekuensi:** ganti dimensi = ganti ukuran vektor = **re-index penuh** & tabel/collection baru. Simpan `embed_dim` di metadata (lihat 4.2).
+
+### 4.2 Metadata versioning (buka re-index selektif)
+
+Di `pkg/indexer/indexer.go`, saat membuat `Document`, tambah metadata:
+
+```go
+import "crypto/sha256"
+import "encoding/hex"
+
+func contentHash(s string) string {
+ h := sha256.Sum256([]byte(s))
+ return hex.EncodeToString(h[:8]) // 16 hex chars cukup
+}
+
+// di dalam loop chunk:
+docs = append(docs, rag.Document{
+ ID: docID,
+ Content: fmt.Sprintf("File: %s\n\n%s", relPath, chunk),
+ Meta: map[string]string{
+ "source_file": relPath,
+ "source_dir": sourceDir,
+ "chunk_index": fmt.Sprintf("%d", i),
+ "content_hash": contentHash(chunk), // BARU: skip re-embed jika sama
+ "chunk_version": "v2", // BARU: bump saat strategi chunk berubah
+ // embed_model & embed_dim diisi di provider.Index (tahu model aktif)
+ },
+})
+```
+
+Di `provider.Index`, tambahkan `embed_model` & `embed_dim` ke tiap metadata sebelum simpan. Ini memungkinkan UI menampilkan model/dim per chunk dan re-index selektif hanya chunk yang `content_hash`-nya berubah.
+
+### 4.3 Ekstrak service layer (`pkg/core`)
+
+Supaya MCP dan HTTP tak duplikasi logika:
+
+```go
+// pkg/core/service.go
+package core
+
+type Service struct {
+ provider rag.Provider
+ reranker rag.Reranker // nil kalau tak dikonfigurasi
+ indexer *indexer.Indexer
+ events *EventBus // untuk SSE
+}
+
+func New(provider rag.Provider, reranker rag.Reranker) *Service { /* ... */ }
+
+// Dipakai MCP tool DAN HTTP handler:
+func (s *Service) Search(ctx context.Context, projectID, query string, opts SearchOpts) ([]rag.Result, error)
+func (s *Service) IndexProject(ctx context.Context, projectID, dir string) (*indexer.SyncResult, error)
+func (s *Service) ListProjects(ctx context.Context) ([]ProjectStat, error)
+```
+
+MCP tool di `main.go` kemudian jadi wrapper tipis di atas `Service`.
+
+---
+
+## 5. Fase 1 — HTTP API layer
+
+### 5.1 Flag & mode di main.go
+
+```go
+// cmd/mcp-server/main.go
+func main() {
+ serve := flag.Bool("serve", false, "run HTTP+UI server instead of stdio MCP")
+ addr := flag.String("addr", ":7777", "HTTP listen address")
+ flag.Parse()
+
+ cfg := loadConfig()
+ svc := buildService(cfg) // wrap provider + reranker + indexer
+
+ if *serve {
+ runHTTP(svc, *addr) // httpapi.NewServer(svc).Run(addr)
+ return
+ }
+ runStdio(svc) // MCP seperti sekarang
+}
+```
+
+### 5.2 Router (chi) + embed SPA
+
+```go
+// pkg/httpapi/server.go
+package httpapi
+
+import (
+ "net/http"
+ "github.com/go-chi/chi/v5"
+ "github.com/go-chi/chi/v5/middleware"
+)
+
+func NewServer(svc *core.Service, ui fs.FS) http.Handler {
+ r := chi.NewRouter()
+ r.Use(middleware.Logger, middleware.Recoverer)
+
+ r.Route("/api", func(r chi.Router) {
+ r.Get("/projects", h.listProjects)
+ r.Get("/projects/{id}", h.getProject)
+ r.Get("/projects/{id}/points", h.listPoints)
+ r.Post("/projects/{id}/reindex", h.reindex)
+ r.Delete("/projects/{id}", h.deleteProject)
+ r.Post("/search", h.search)
+ r.Get("/stats", h.stats)
+ r.Get("/events", h.sse) // SSE stream
+ // wizard (Fase 2):
+ r.Post("/setup/test", h.setupTest)
+ r.Post("/setup/apply", h.setupApply)
+ r.Get("/setup/status", h.setupStatus)
+ })
+
+ // SPA fallback: serve embedded dist, index.html untuk route non-file
+ r.Handle("/*", spaHandler(ui))
+ return r
+}
+```
+
+```go
+// web/embed.go
+package web
+
+import "embed"
+
+//go:embed all:dist
+var Dist embed.FS
+```
+
+### 5.3 Handler search (contoh, dengan opsi hybrid/rerank)
+
+```go
+// pkg/httpapi/handlers.go
+type searchReq struct {
+ ProjectID string `json:"project_id"`
+ Query string `json:"query"`
+ K int `json:"k"`
+ Recall int `json:"recall"`
+ Hybrid bool `json:"hybrid"`
+ Rerank bool `json:"rerank"`
+}
+
+func (h *Handlers) search(w http.ResponseWriter, r *http.Request) {
+ var req searchReq
+ json.NewDecoder(r.Body).Decode(&req)
+ res, err := h.svc.Search(r.Context(), req.ProjectID, req.Query, core.SearchOpts{
+ K: req.K, Recall: req.Recall, Hybrid: req.Hybrid, Rerank: req.Rerank,
+ })
+ if err != nil { writeErr(w, 500, err); return }
+ writeJSON(w, map[string]any{"results": res})
+}
+```
+
+### 5.4 SSE (realtime progress)
+
+```go
+// pkg/httpapi/sse.go
+func (h *Handlers) sse(w http.ResponseWriter, r *http.Request) {
+ w.Header().Set("Content-Type", "text/event-stream")
+ w.Header().Set("Cache-Control", "no-cache")
+ w.Header().Set("Connection", "keep-alive")
+ flusher := w.(http.Flusher)
+
+ ch := h.svc.Subscribe() // channel dari EventBus
+ defer h.svc.Unsubscribe(ch)
+
+ for {
+ select {
+ case <-r.Context().Done():
+ return
+ case ev := <-ch:
+ b, _ := json.Marshal(ev)
+ fmt.Fprintf(w, "event: %s\ndata: %s\n\n", ev.Type, b)
+ flusher.Flush()
+ }
+ }
+}
+```
+
+Frontend konsumsi: `new EventSource("/api/events")`.
+
+---
+
+## 6. Fase 2 — Onboarding Wizard
+
+**JANGAN LUPA — ini nilai jual utama.** Dijalankan saat first-run (config belum ada) atau via menu Setup.
+
+### 6.1 Config file
+
+```go
+// pkg/config/config.go
+package config
+
+type Config struct {
+ VectorStore string `yaml:"vector_store"` // pgvector|qdrant|chroma
+ Embedder string `yaml:"embedder"` // voyage|tei|openai
+ Voyage struct {
+ APIKey string `yaml:"api_key"`
+ Model string `yaml:"model"`
+ Dim int `yaml:"dim"`
+ } `yaml:"voyage"`
+ PGVectorDSN string `yaml:"pgvector_dsn"`
+ QdrantURL string `yaml:"qdrant_url"`
+ // ...
+}
+
+func Path() string { // ~/.enowx-rag/config.yaml
+ home, _ := os.UserHomeDir()
+ return filepath.Join(home, ".enowx-rag", "config.yaml")
+}
+func Load() (*Config, error) { /* baca yaml; error jika belum ada → trigger wizard */ }
+func Save(c *Config) error { /* mkdir + tulis yaml, chmod 0600 (ada API key) */ }
+```
+
+Prioritas config: **env var > config.yaml > default** (env var menang, biar MCP existing tak berubah perilaku).
+
+### 6.2 Endpoint test connection
+
+```go
+// pkg/httpapi/setup.go
+type testReq struct {
+ VectorStore string `json:"vector_store"`
+ Embedder string `json:"embedder"`
+ // endpoints + keys...
+}
+type testResult struct {
+ VectorStore compCheck `json:"vector_store"`
+ Embedder compCheck `json:"embedder"`
+}
+type compCheck struct {
+ OK bool `json:"ok"`
+ Message string `json:"message"` // actionable: "Qdrant unreachable at localhost:6333 — run `docker compose up -d qdrant`"
+ Latency int `json:"latency_ms"`
+}
+
+func (h *Handlers) setupTest(w http.ResponseWriter, r *http.Request) {
+ // coba connect vector store (health check) + coba embed 1 kata via embedder
+ // kembalikan per-komponen hijau/merah + pesan
+}
+```
+
+### 6.3 Flow wizard (React, 6 step)
+
+```
+web/src/pages/onboarding/
+├── Wizard.tsx # stepper state machine
+├── StepWelcome.tsx # deteksi env (Docker? port kepakai?)
+├── StepVectorStore.tsx # kartu pgvector/qdrant/chroma + local/cloud + endpoint
+├── StepEmbedding.tsx # kartu voyage/tei/openai + key + model + DIMENSI
+├── StepTest.tsx # tombol Test → POST /api/setup/test → hijau/merah per komponen
+├── StepAutoSetup.tsx # generate compose ATAU jalankan (SSE progress)
+└── StepDone.tsx # POST /api/setup/apply → simpan → ke dashboard
+```
+
+Contoh state machine (ringkas):
+
+```tsx
+// Wizard.tsx
+const STEPS = ["welcome","vector","embedding","test","setup","done"] as const;
+type Step = typeof STEPS[number];
+
+export function Wizard() {
+ const [step, setStep] = useState("welcome");
+ const [cfg, setCfg] = useState(defaultDraft);
+ const next = () => setStep(STEPS[STEPS.indexOf(step)+1]);
+ const back = () => setStep(STEPS[STEPS.indexOf(step)-1]);
+ // render step sesuai `step`, pass cfg + setCfg + next/back
+}
+```
+
+### 6.4 Catatan desain wizard
+- Setiap step bisa mundur; draft state disimpan.
+- Field endpoint/DSN/key pakai **mono**; tombol reveal untuk key.
+- **Peringatan penting** di StepEmbedding: "Ganti model/dimensi mengunci collection — perlu re-index penuh."
+- Auto-setup default: **generate `docker-compose.yml` + tampilkan command** (aman). Opsi "jalankan otomatis" dengan disclaimer.
+- Empty/error state jelas & actionable.
+- **Mockup wizard: TODO** `docs/mockups/onboarding.html` (belum dibuat).
+
+---
+
+## 7. Fase 3 — Dashboard & Playground
+
+Referensi visual: **`docs/mockups/dashboard.html`** (sudah di-approve). Layout: sidebar fixed + bento grid full-width.
+
+### 7.1 Peta halaman
+- **Overview** (`pages/Overview.tsx`) — KPI row (chunks, embedding, latency+sparkline, token usage) + Retrieval playground + Index status + Chunk distribution + Retrieval breakdown + Recent files + Activity (bento grid seperti mockup).
+- **Playground** (`pages/Playground.tsx`) — versi full dari playground: query → chunk + skor + highlight + toggle Hybrid/Rerank/Compress.
+- **Chunks** (`pages/Chunks.tsx`) — browse/filter/hapus chunk per file.
+- **Setup** — masuk ke Wizard.
+
+### 7.2 Client API
+
+```ts
+// web/src/lib/api.ts
+export async function search(body: SearchReq): Promise {
+ const r = await fetch("/api/search", {
+ method: "POST", headers: {"content-type":"application/json"},
+ body: JSON.stringify(body),
+ });
+ if (!r.ok) throw new Error((await r.json()).error);
+ return r.json();
+}
+export async function listProjects(): Promise {
+ return (await fetch("/api/projects")).json();
+}
+```
+
+### 7.3 SSE hook (realtime activity)
+
+```ts
+// web/src/lib/sse.ts
+export function useEvents(onEvent: (e: RagEvent) => void) {
+ useEffect(() => {
+ const es = new EventSource("/api/events");
+ es.onmessage = (m) => onEvent(JSON.parse(m.data));
+ return () => es.close();
+ }, []);
+}
+```
+
+---
+
+## 8. Fase 4 — Kualitas RAG
+
+Ini yang bikin "advance". Dampak > UI apapun.
+
+### 8.1 Reranker (Voyage rerank-2.5)
+
+```go
+// pkg/rag/rerank.go
+package rag
+
+type Reranker interface {
+ Rerank(ctx context.Context, query string, docs []string, topK int) ([]RerankHit, error)
+}
+type RerankHit struct {
+ Index int `json:"index"` // index di slice docs asli
+ Score float64 `json:"relevance_score"`
+}
+
+type VoyageReranker struct {
+ APIKey string
+ Model string // "rerank-2.5"
+ client *http.Client
+}
+
+const voyageRerankURL = "https://api.voyageai.com/v1/rerank"
+
+func (r *VoyageReranker) Rerank(ctx context.Context, query string, docs []string, topK int) ([]RerankHit, error) {
+ body, _ := json.Marshal(map[string]any{
+ "query": query, "documents": docs, "model": r.Model, "top_k": topK,
+ })
+ req, _ := http.NewRequestWithContext(ctx, "POST", voyageRerankURL, bytes.NewReader(body))
+ req.Header.Set("Authorization", "Bearer "+r.APIKey)
+ req.Header.Set("Content-Type", "application/json")
+ resp, err := r.client.Do(req)
+ if err != nil { return nil, err }
+ defer resp.Body.Close()
+ var out struct{ Data []RerankHit `json:"data"` }
+ json.NewDecoder(resp.Body).Decode(&out)
+ return out.Data, nil
+}
+```
+
+Alur di `core.Service.Search`: retrieval recall besar (mis. 40) → rerank → top-K kecil (mis. 4).
+
+```go
+// pkg/core/service.go
+func (s *Service) Search(ctx context.Context, projectID, query string, o SearchOpts) ([]rag.Result, error) {
+ recall := o.Recall; if recall == 0 { recall = 40 }
+ k := o.K; if k == 0 { k = 5 }
+
+ cands, err := s.provider.SemanticSearch(ctx, projectID, query, recall)
+ if err != nil { return nil, err }
+
+ if o.Rerank && s.reranker != nil && len(cands) > 0 {
+ docs := make([]string, len(cands))
+ for i, c := range cands { docs[i] = c.Content }
+ hits, err := s.reranker.Rerank(ctx, query, docs, k)
+ if err == nil {
+ out := make([]rag.Result, 0, len(hits))
+ for _, h := range hits {
+ r := cands[h.Index]; r.Score = h.Score
+ out = append(out, r)
+ }
+ return out, nil
+ }
+ // fallback ke urutan semantic kalau rerank gagal
+ }
+ if len(cands) > k { cands = cands[:k] }
+ return cands, nil
+}
+```
+
+### 8.2 Hybrid search (pgvector: dense + tsvector + RRF)
+
+Tambah kolom tsvector + index di `ensureTable`:
+
+```sql
+-- pkg/rag/pgvector.go, ensureTable()
+ALTER TABLE project_memory ADD COLUMN IF NOT EXISTS content_tsv tsvector
+ GENERATED ALWAYS AS (to_tsvector('simple', content)) STORED;
+CREATE INDEX IF NOT EXISTS idx_project_memory_tsv
+ ON project_memory USING GIN (content_tsv);
+```
+
+Query hybrid dengan Reciprocal Rank Fusion (RRF):
+
+```sql
+-- dense + lexical, gabung via RRF (k=60 konvensi)
+WITH dense AS (
+ SELECT id, content, metadata,
+ ROW_NUMBER() OVER (ORDER BY embedding <=> $1::vector) AS rank
+ FROM project_memory WHERE project_id = $3
+ ORDER BY embedding <=> $1::vector LIMIT $4
+),
+lexical AS (
+ SELECT id, content, metadata,
+ ROW_NUMBER() OVER (ORDER BY ts_rank(content_tsv, plainto_tsquery('simple',$2)) DESC) AS rank
+ FROM project_memory
+ WHERE project_id = $3 AND content_tsv @@ plainto_tsquery('simple',$2)
+ LIMIT $4
+)
+SELECT COALESCE(d.id,l.id) AS id,
+ COALESCE(d.content,l.content) AS content,
+ COALESCE(d.metadata,l.metadata) AS metadata,
+ (COALESCE(1.0/(60+d.rank),0) + COALESCE(1.0/(60+l.rank),0)) AS score
+FROM dense d FULL OUTER JOIN lexical l USING (id)
+ORDER BY score DESC LIMIT $5;
+```
+
+> Untuk korpus kode (nama fungsi/API eksak), lexical sering menang — hybrid menutup gap yang dense saja lewatkan.
+
+### 8.3 Contextual compression (opsional, hemat token)
+Setelah rerank, potong kalimat non-relevan tiap chunk sebelum dikirim ke LLM. Bisa heuristik (skor per kalimat) atau LLM-based. Toggle "Compress" di UI.
+
+---
+
+## 9. Fase 5 — Polish & distribusi
+
+### 9.1 Makefile (build pipeline 1 binary)
+
+```makefile
+# Makefile
+.PHONY: web build
+
+web:
+ cd web && npm ci && npm run build # hasil ke web/dist
+
+build: web
+ cd mcp-server && go build -o enowx-rag ./cmd/mcp-server
+
+dev-web:
+ cd web && npm run dev # vite dev server proxy ke :7777
+```
+
+### 9.2 Lain-lain
+- `docker-compose.yml` all-in-one (sudah ada, tambah service UI opsional).
+- README + GIF wizard, one-command deploy.
+- Auth ringan opsional (single admin token via `RAG_ADMIN_TOKEN`) bila di-expose ke internet.
+
+---
+
+## 10. Kriteria UI — design tokens
+
+**Arah: true-black flat, zero gradient.** Tervalidasi lewat `docs/mockups/dashboard.html`.
+
+Prinsip:
+- Dark bg **`#000000`**, surface `#0a0a0a` — hitam pekat, bukan biru-kehitaman.
+- **Zero gradient.** Semua warna solid. Kedalaman lewat **hairline border `1px` + beda surface**, bukan shadow/glow/glass.
+- Light & dark **sederajat** (palet zinc netral); toggle stamp `data-theme`.
+- **1 accent solid** (violet) hanya untuk aksi & endpoint. **Skor pakai warna semantik terpisah** (hijau/amber/abu).
+- **Mono** untuk semua data teknis (path, ID, skor, DSN).
+- Data-first, density tinggi. Sidebar fixed, konten full-width (bento grid).
+- **NO** glass/nebula/gradient/Aceternity/Magic UI.
+
+Token (salin ke `web/src/styles/tokens.css`):
+
+```css
+:root { /* light */
+ --bg:#ffffff; --surface:#fafafa; --surface-2:#f4f4f5;
+ --border:#e4e4e7; --border-strong:#d4d4d8;
+ --text:#09090b; --text-dim:#52525b; --text-faint:#a1a1aa;
+ --accent:#6d4aff; --accent-fg:#ffffff;
+ --good:#1a7f37; --warn:#9a6700; --crit:#cf222e;
+}
+:root[data-theme="dark"], @media (prefers-color-scheme: dark) { /* dark */
+ --bg:#000000; --surface:#0a0a0a; --surface-2:#101012;
+ --border:#1e1e21; --border-strong:#2a2a2e;
+ --text:#fafafa; --text-dim:#a1a1aa; --text-faint:#6b6b73;
+ --accent:#7c5cff; --accent-fg:#ffffff;
+ --good:#3fb950; --warn:#d29922; --crit:#f85149;
+}
+```
+
+Font: UI = Inter/Geist; data/mono = JetBrains Mono / Geist Mono. **Bukan** Rubik/Nunito (itu punya RobloxKit — beda produk).
+
+---
+
+## 11. Risiko & gotcha
+
+- ⚠️ **Ganti embedding model/dimensi = re-index penuh.** Vektor beda dimensi/model tak boleh dicampur dalam satu collection. Wizard harus mengunci & memperingatkan.
+- ⚠️ **Auto-setup Docker dari binary** kompleks/rawan (izin, port bentrok). Default: generate compose + tampilkan command, bukan eksekusi paksa.
+- ⚠️ **Multi-provider × wizard** melipatgandakan matrix testing. Utamakan **pgvector** untuk UI penuh (hybrid butuh tsvector — hanya pgvector); provider lain "supported tapi basic".
+- ⚠️ **Env var menang atas config.yaml** — jaga agar MCP stdio existing tak berubah perilaku saat file config muncul.
+- ⚠️ **HNSW pgvector**: butuh index (jangan seq scan). Untuk irit storage pertimbangkan `halfvec(1024)`. Voyage output normalized → cosine.
+- ⚠️ **Build pipeline**: `web/dist` harus ada sebelum `go build` (embed.FS gagal kalau folder kosong). Makefile `build` depend `web`.
+- ⚠️ **Selera desain ≠ RobloxKit**: RobloxKit glass+gradient+rose; enowx-rag true-black flat zero gradient. Reuse ekosistem React saja, bukan look-nya.
+- ⚠️ **Indexer chunkSize 1500** dipilih agar chunk + prefix "File:" muat di limit token TEI (~512 tok, ~3 char/token). Kalau pindah ke Voyage penuh, bisa dinaikkan.
+
+---
+
+## 12. Status pengerjaan
+
+- [x] Project ter-index ke RAG (`project_enowx-rag`, 17 file, 76 chunk).
+- [x] Skill terpasang (`~/.factory/skills/enowx-rag/skill.md`).
+- [x] AGENTS.md & CLAUDE.md sudah ada di root.
+- [x] Kriteria UI dikunci (true-black flat) + mockup dashboard di-approve (`docs/mockups/dashboard.html`).
+- [x] Planning detail dengan contoh kode (dokumen ini).
+- [x] HANDOFF.md untuk agent lain.
+- [ ] Mockup onboarding wizard (`docs/mockups/onboarding.html`).
+- [ ] Fase 0 — fix Voyage + metadata versioning + service layer.
+- [ ] Fase 1 — HTTP API + SSE + embed FS.
+- [ ] Fase 2 — Onboarding wizard.
+- [ ] Fase 3 — Dashboard & playground.
+- [ ] Fase 4 — Reranker + hybrid.
+- [ ] Fase 5 — Polish & distribusi.
+
+**Urutan rekomendasi:** Fase 0 → 4 (kualitas dulu, murah & berdampak) → 1 → 3 (biar bisa lihat hasil) → 2 (wizard, paling makan waktu) → 5.
diff --git a/docs/mockups/dashboard.html b/docs/mockups/dashboard.html
new file mode 100644
index 0000000..47dcb54
--- /dev/null
+++ b/docs/mockups/dashboard.html
@@ -0,0 +1,607 @@
+enowx-rag — Dashboard Mockup
+
+
+
// List only points belonging to this source_dir so that
+// indexing a different directory into the same project
+// doesn't wipe the first. Reconcile against currentSet.
+
+
+
+
+
+ 0.874
+
+
+
+
pgvector.go · DeletePoints()type:snippet
+
DELETE FROM project_memory
+WHERE project_id = $1 AND id = ANY($2)
+// stale ids resolved from ListPoints() by source_dir
+
+
+
+
+
+ 0.661
+
+
+
+
pgvector.go · ListPoints()type:snippet
+
SELECT id, metadata->>'source_file' FROM project_memory
+WHERE project_id = $1 AND metadata->>'source_dir' = $2
+
+
+
+
+
+ 0.402
+
+
+
+
provider.go · Provider ifacetype:api
+
// DeletePoints removes specific points by ID from the
+// project collection.
+
+
+
+
+
+
+
+
+
+
Index status
+ ● synced
+
+
+
Files scanned17
+
Chunks indexed76
+
Points deleted0
+
Chunk versionv2 · 1500c
+
Last syncjust now
+
+
+
+
+
+
Retrieval breakdown
this query
+
+
+
+
+
+
+
Dense (vector)58%
+
Lexical (tsvector)42%
+
Reranker moved2 ↑
+
+
+
+
+
+
+
Chunk distribution
chunks per file
+
+
+
README.md12
+
pkg/rag/qdrant.go7
+
cmd/mcp-server/main.go9
+
pkg/indexer/indexer.go6
+
pkg/rag/pgvector.go8
+
pkg/rag/chroma.go5
+
+
+
+
+
+
+
Recent files
+
+
+
pkg/rag/pgvector.go8 ch
+
pkg/indexer/indexer.go6 ch
+
cmd/mcp-server/main.go9 ch
+
pkg/rag/voyage.go4 ch
+
pkg/rag/tei.go3 ch
+
README.md12 ch
+
skill/enowx-rag.md7 ch
+
+
+
+
+
+
+
Activity
live
+
+
+
12:19:41✓ synced76 chunks · 0 deleted
+
12:14:02✓ querylatency 213ms · k=4
+
12:19:38embed17 files → voyage-4
+
12:13:55rerank40 → 4 · rerank-2.5
+
12:19:37scan/Project/enowx-rag
+
12:09:20✓ querylatency 198ms · k=4
+
+
+
+
+
+
+
+ Mockup — gaya true-black flat: dark #000000, zero gradient, kedalaman lewat 1px border & beda surface.
+ Aksen tunggal violet hanya untuk aksi & endpoint; warna semantik (skor) terpisah. Data teknis pakai mono. Coba tombol tema di kanan atas.
+
This wizard will guide you through configuring your RAG memory server. You will set up a vector store, choose an embedding provider, test connectivity, optionally run auto-setup for local backends, and then start indexing your projects.
+
+
+
+
+
+
+
+ Docker
+ available
+
+
+
+ PostgreSQL
+ :5432 · pgvector 0.8.0
+
+
+
+ Qdrant
+ :6333 — not running
+
+
+
+ TEI Embedder
+ :8081 — not running
+
+
+
+
The wizard will configure: (1) vector store selection, (2) embedding provider, (3) connection test, (4) optional auto-setup for local Docker backends, (5) save configuration to ~/.enowx-rag/config.yaml.
+
+
+
+
+
+
+
+
+
+
+
+
+
Step 2 of 6 — Vector Store
+
+
+
1
Welcome
+
+
2
Vector Store
+
+
3
Embedding
+
+
4
Test
+
+
5
Auto Setup
+
+
6
Done
+
+
+
+
+
Choose a Vector Store
+ 2 / 6
+
+
+
Select the database that will store your vector embeddings. Each option supports full semantic search and metadata filtering.
+
+
+
+
pgvector
+
PostgreSQL extension for vector similarity search. Supports hybrid search via tsvector + RRF fusion.
+
hybrid · GIN index · tsvector
+
+
+
Qdrant
+
Dedicated vector search engine with high performance and rich filtering capabilities.
+
payload filter · HNSW
+
+
+
Chroma
+
Lightweight, open-source embedding database. Simple setup for development and testing.
Select the service that will generate vector embeddings from your text. Voyage AI is recommended for production quality.
+
+
+
+
Voyage AI
+
Cloud API with state-of-the-art embeddings. voyage-4 model offers 200M free tokens.
+
cloud · 1024-dim · rerank-2.5
+
+
+
TEI
+
Text Embeddings Inference server. Self-hosted, runs locally via Docker for zero-cost inference.
+
self-hosted · Docker · :8081
+
+
+
+
+
+
+
+ pa-••••••••••••••••••••••••
+
+
+
+
+
+
+
+
+ voyage-4
+
+
+
+
+
+
+ 1024
+
+
+
+
+
+
+
+ Re-index required. Changing the embedding model or dimension after data has been indexed will require a full re-index. Existing vectors with a different model/dimension cannot be mixed in the same collection.
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
Step 4 of 6 — Test Connection
+
+
+
1
Welcome
+
+
2
Vector Store
+
+
3
Embedding
+
+
4
Test
+
+
5
Auto Setup
+
+
6
Done
+
+
+
+
+
Test Connection
+ 4 / 6
+ POST /api/setup/test
+
+
+
Verify that your vector store and embedding provider are reachable. Click Test Connection to run per-component health checks.
+
+
+
+
+
+
+
+
+
+
Vector Store — pgvector
+
Connected to postgresql://enowdev@localhost:5432/enowxrag. pgvector extension confirmed.
+
+ 12ms
+
+
+
+
+
+
Embedder — voyage-4
+
Embedded test word "hello" → 1024-dim vector. API key valid.
+
+ 87ms
+
+
+
+
+
+
Reranker — rerank-2.5
+
Failed: connection refused at api.voyageai.com. Check API key or run docker compose up -d for local reranker.
+
+ —
+
+
+
+
+
+ 2 of 3 components passed. You can proceed with a working setup (reranker is optional), or fix the failing component and re-test.
+
+
+
+
+
+
+
+ 2/3 passed — proceed with override
+
+
+
+
+
+
+
+
+
+
+
Step 5 of 6 — Auto Setup
+
+
+
1
Welcome
+
+
2
Vector Store
+
+
3
Embedding
+
+
4
Test
+
+
5
Auto Setup
+
+
6
Done
+
+
+
+
+
Auto Setup
+ 5 / 6
+ Local backend
+
+
+
Based on your selections, here is the generated docker-compose.yml and the commands to start your local backend. Commands are shown for review — they are not executed automatically.
# Start local backend
+docker compose up -d postgres
+
+# Verify pgvector extension
+psql -h localhost -U enowdev -d enowxrag \
+ -c "CREATE EXTENSION IF NOT EXISTS vector;"
+
+# Start enowx-rag server
+./enowx-rag --serve --addr :7777
+
+
+
+
+
+
Run automatically
+
Execute the docker-compose and setup commands automatically. Progress will be streamed via SSE.
+
+
+
+
+
+
+ Disclaimer: Auto-run will start Docker containers on your machine. Ensure ports 5432 (and 6333/8081 if applicable) are available. You are responsible for any resources created.
+
+
+
+
+
+
14:22:01pulling pgvector/pgvector:pg16…
+
14:22:14image pulled, starting container…
+
14:22:15✓ postgres container started
+
14:22:16waiting for postgres to be ready…
+
14:22:19✓ postgres ready on :5432
+
14:22:19creating pgvector extension…
+
14:22:20✓ extension "vector" created
+
14:22:20✓ auto-setup complete
+
+
+
+
+
+
+
+
+
+
+
+
+
+
Step 6 of 6 — Done
+
+
+
1
Welcome
+
+
2
Vector Store
+
+
3
Embedding
+
+
4
Test
+
+
5
Auto Setup
+
+
6
Done
+
+
+
+
+
Configuration Complete
+ 6 / 6
+ POST /api/setup/apply
+
+
+
✓
+
You are all set!
+
Review your configuration below and click Finish to save and launch the dashboard.
After saving, you will be redirected to the dashboard where you can index your first project and start using semantic search.
+
+
+
+
+
+
+
+
+
+
+
+ Mockup — true-black flat design: dark #000000, zero gradient, depth via 1px border & surface contrast.
+ Single violet accent for actions only. Technical data in monospace. Matches dashboard.html style.
+ Toggle theme with the button in the top bar.
+