feat: simplify mcp installation + ui standalone (#41)
This commit is contained in:
parent
8ecb5d3a0c
commit
1c6acc3ba0
19 changed files with 729 additions and 5503 deletions
51
.github/workflows/release.yml
vendored
51
.github/workflows/release.yml
vendored
|
|
@ -117,6 +117,47 @@ jobs:
|
|||
path: hindsight-clients/typescript/*.tgz
|
||||
retention-days: 1
|
||||
|
||||
release-control-plane:
|
||||
runs-on: ubuntu-latest
|
||||
environment: npm
|
||||
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
|
||||
- name: Set up Node.js
|
||||
uses: actions/setup-node@v4
|
||||
with:
|
||||
node-version: '20'
|
||||
registry-url: 'https://registry.npmjs.org'
|
||||
cache: 'npm'
|
||||
cache-dependency-path: package-lock.json
|
||||
|
||||
- name: Install dependencies
|
||||
run: npm ci
|
||||
|
||||
- name: Build TypeScript client (dependency)
|
||||
run: npm run build --workspace=hindsight-clients/typescript
|
||||
|
||||
- name: Build
|
||||
run: npm run build --workspace=hindsight-control-plane
|
||||
|
||||
- name: Publish to npm
|
||||
working-directory: ./hindsight-control-plane
|
||||
run: npm publish --access public
|
||||
env:
|
||||
NODE_AUTH_TOKEN: ${{ secrets.NPM_TOKEN }}
|
||||
|
||||
- name: Pack for GitHub release
|
||||
working-directory: ./hindsight-control-plane
|
||||
run: npm pack
|
||||
|
||||
- name: Upload artifacts
|
||||
uses: actions/upload-artifact@v4
|
||||
with:
|
||||
name: control-plane
|
||||
path: hindsight-control-plane/*.tgz
|
||||
retention-days: 1
|
||||
|
||||
release-rust-cli:
|
||||
runs-on: ${{ matrix.os }}
|
||||
strategy:
|
||||
|
|
@ -287,7 +328,7 @@ jobs:
|
|||
|
||||
create-github-release:
|
||||
runs-on: ubuntu-latest
|
||||
needs: [release-python-packages, release-typescript-client, release-rust-cli, release-docker-images, release-helm-chart]
|
||||
needs: [release-python-packages, release-typescript-client, release-control-plane, release-rust-cli, release-docker-images, release-helm-chart]
|
||||
permissions:
|
||||
contents: write
|
||||
|
||||
|
|
@ -310,6 +351,12 @@ jobs:
|
|||
name: typescript-client
|
||||
path: ./artifacts/typescript-client
|
||||
|
||||
- name: Download Control Plane
|
||||
uses: actions/download-artifact@v4
|
||||
with:
|
||||
name: control-plane
|
||||
path: ./artifacts/control-plane
|
||||
|
||||
- name: Download Rust CLI (Linux)
|
||||
uses: actions/download-artifact@v4
|
||||
with:
|
||||
|
|
@ -344,6 +391,8 @@ jobs:
|
|||
cp artifacts/python-packages/hindsight-integrations/litellm/dist/* release-assets/ || true
|
||||
# TypeScript client
|
||||
cp artifacts/typescript-client/*.tgz release-assets/ || true
|
||||
# Control Plane
|
||||
cp artifacts/control-plane/*.tgz release-assets/ || true
|
||||
# Rust CLI binaries
|
||||
cp artifacts/rust-cli-linux/hindsight-linux-amd64 release-assets/ || true
|
||||
cp artifacts/rust-cli-darwin-amd64/hindsight-darwin-amd64 release-assets/ || true
|
||||
|
|
|
|||
27
.github/workflows/test.yml
vendored
27
.github/workflows/test.yml
vendored
|
|
@ -80,6 +80,33 @@ jobs:
|
|||
- name: Build TypeScript client
|
||||
run: npm run build --workspace=hindsight-clients/typescript
|
||||
|
||||
build-control-plane:
|
||||
runs-on: ubuntu-latest
|
||||
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
|
||||
- name: Set up Node.js
|
||||
uses: actions/setup-node@v4
|
||||
with:
|
||||
node-version: '20'
|
||||
cache: 'npm'
|
||||
cache-dependency-path: package-lock.json
|
||||
|
||||
- name: Install dependencies
|
||||
run: npm ci
|
||||
|
||||
- name: Build TypeScript client (dependency)
|
||||
run: npm run build --workspace=hindsight-clients/typescript
|
||||
|
||||
- name: Build control plane
|
||||
run: npm run build --workspace=hindsight-control-plane
|
||||
|
||||
- name: Verify standalone build
|
||||
run: |
|
||||
test -f hindsight-control-plane/standalone/server.js || exit 1
|
||||
node hindsight-control-plane/bin/cli.js --help
|
||||
|
||||
build-docs:
|
||||
runs-on: ubuntu-latest
|
||||
|
||||
|
|
|
|||
|
|
@ -72,30 +72,39 @@ FROM node:20-slim AS cp-builder
|
|||
ARG INCLUDE_CP
|
||||
RUN if [ "$INCLUDE_CP" != "true" ]; then echo "Skipping CP build" && exit 0; fi
|
||||
|
||||
WORKDIR /app
|
||||
|
||||
# Copy built SDK
|
||||
COPY --from=sdk-builder /app/hindsight-clients/typescript /app/sdk
|
||||
# Create directory structure matching the monorepo layout
|
||||
# This is required because build:standalone script expects .next/standalone/memory-poc/hindsight-control-plane
|
||||
WORKDIR /app/memory-poc/hindsight-control-plane
|
||||
|
||||
# Install Control Plane dependencies
|
||||
# Only copy package.json (not package-lock.json) to ensure npm installs
|
||||
# correct platform-specific native bindings for lightningcss/tailwindcss
|
||||
COPY hindsight-control-plane/package.json ./
|
||||
# Remove the file: dependency on SDK (we'll copy it directly later)
|
||||
RUN sed -i '/"@vectorize-io\/hindsight-client":/d' package.json
|
||||
RUN npm install
|
||||
|
||||
# Copy Control Plane source (excluding node_modules via .dockerignore)
|
||||
COPY hindsight-control-plane/ ./
|
||||
# Remove package-lock.json to avoid conflicts with installed native bindings
|
||||
RUN rm -f package-lock.json
|
||||
# Also remove the file: dependency from package.json (restored by COPY above)
|
||||
RUN rm -f package-lock.json && sed -i '/"@vectorize-io\/hindsight-client":/d' package.json
|
||||
|
||||
# Link SDK (temporary for build)
|
||||
RUN cd /app/sdk && npm link && cd /app && npm link @vectorize-io/hindsight-client
|
||||
# Copy built SDK directly into node_modules (more reliable than npm link in Docker)
|
||||
COPY --from=sdk-builder /app/hindsight-clients/typescript ./node_modules/@vectorize-io/hindsight-client
|
||||
|
||||
# Build Control Plane
|
||||
RUN npm run build
|
||||
# Build Control Plane - run next build first, then custom standalone copy
|
||||
# (The build:standalone script expects a specific path structure that differs in Docker)
|
||||
RUN npm exec -- next build
|
||||
|
||||
# Create public directory if it doesn't exist
|
||||
RUN mkdir -p public
|
||||
# Create standalone directory structure manually
|
||||
# Next.js standalone output structure varies, so we find server.js and work from there
|
||||
RUN mkdir -p standalone/.next && \
|
||||
STANDALONE_ROOT=$(dirname $(find .next/standalone -name "server.js" | head -1)) && \
|
||||
cp -r "$STANDALONE_ROOT"/* standalone/ && \
|
||||
cp -r .next/static standalone/.next/static && \
|
||||
mkdir -p standalone/public && \
|
||||
cp -r public/* standalone/public/ 2>/dev/null || true
|
||||
|
||||
# =============================================================================
|
||||
# Stage: Final Image - API Only
|
||||
|
|
@ -172,9 +181,9 @@ COPY --from=sdk-builder /app/hindsight-clients/typescript /app/sdk
|
|||
|
||||
# Copy Control Plane standalone build
|
||||
WORKDIR /app/control-plane
|
||||
COPY --from=cp-builder /app/.next/standalone ./
|
||||
COPY --from=cp-builder /app/.next/static ./.next/static
|
||||
COPY --from=cp-builder /app/public ./public
|
||||
COPY --from=cp-builder /app/memory-poc/hindsight-control-plane/standalone ./
|
||||
COPY --from=cp-builder /app/memory-poc/hindsight-control-plane/.next/static ./.next/static
|
||||
COPY --from=cp-builder /app/memory-poc/hindsight-control-plane/public ./public
|
||||
|
||||
WORKDIR /app
|
||||
|
||||
|
|
@ -226,9 +235,9 @@ COPY --from=sdk-builder /app/hindsight-clients/typescript /app/sdk
|
|||
|
||||
# Copy Control Plane standalone build
|
||||
WORKDIR /app/control-plane
|
||||
COPY --from=cp-builder /app/.next/standalone ./
|
||||
COPY --from=cp-builder /app/.next/static ./.next/static
|
||||
COPY --from=cp-builder /app/public ./public
|
||||
COPY --from=cp-builder /app/memory-poc/hindsight-control-plane/standalone ./
|
||||
COPY --from=cp-builder /app/memory-poc/hindsight-control-plane/.next/static ./.next/static
|
||||
COPY --from=cp-builder /app/memory-poc/hindsight-control-plane/public ./public
|
||||
|
||||
WORKDIR /app
|
||||
|
||||
|
|
|
|||
|
|
@ -31,6 +31,7 @@ ENV_LOG_LEVEL = "HINDSIGHT_API_LOG_LEVEL"
|
|||
ENV_MCP_ENABLED = "HINDSIGHT_API_MCP_ENABLED"
|
||||
ENV_GRAPH_RETRIEVER = "HINDSIGHT_API_GRAPH_RETRIEVER"
|
||||
ENV_MCP_LOCAL_BANK_ID = "HINDSIGHT_API_MCP_LOCAL_BANK_ID"
|
||||
ENV_MCP_INSTRUCTIONS = "HINDSIGHT_API_MCP_INSTRUCTIONS"
|
||||
|
||||
# Default values
|
||||
DEFAULT_DATABASE_URL = "pg0"
|
||||
|
|
@ -50,6 +51,26 @@ DEFAULT_MCP_ENABLED = True
|
|||
DEFAULT_GRAPH_RETRIEVER = "bfs" # Options: "bfs", "mpfp"
|
||||
DEFAULT_MCP_LOCAL_BANK_ID = "mcp"
|
||||
|
||||
# Default MCP tool descriptions (can be customized via env vars)
|
||||
DEFAULT_MCP_RETAIN_DESCRIPTION = """Store important information to long-term memory.
|
||||
|
||||
Use this tool PROACTIVELY whenever the user shares:
|
||||
- Personal facts, preferences, or interests
|
||||
- Important events or milestones
|
||||
- User history, experiences, or background
|
||||
- Decisions, opinions, or stated preferences
|
||||
- Goals, plans, or future intentions
|
||||
- Relationships or people mentioned
|
||||
- Work context, projects, or responsibilities"""
|
||||
|
||||
DEFAULT_MCP_RECALL_DESCRIPTION = """Search memories to provide personalized, context-aware responses.
|
||||
|
||||
Use this tool PROACTIVELY to:
|
||||
- Check user's preferences before making suggestions
|
||||
- Recall user's history to provide continuity
|
||||
- Remember user's goals and context
|
||||
- Personalize responses based on past interactions"""
|
||||
|
||||
# Required embedding dimension for database schema
|
||||
EMBEDDING_DIMENSION = 384
|
||||
|
||||
|
|
@ -142,7 +163,9 @@ class HindsightConfig:
|
|||
def configure_logging(self) -> None:
|
||||
"""Configure Python logging based on the log level."""
|
||||
logging.basicConfig(
|
||||
level=self.get_python_log_level(), format="%(asctime)s - %(levelname)s - %(name)s - %(message)s"
|
||||
level=self.get_python_log_level(),
|
||||
format="%(asctime)s - %(levelname)s - %(name)s - %(message)s",
|
||||
force=True, # Override any existing configuration
|
||||
)
|
||||
|
||||
def log_config(self) -> None:
|
||||
|
|
|
|||
|
|
@ -172,7 +172,7 @@ class LLMProvider:
|
|||
|
||||
# Check if model supports reasoning parameter (o1, o3, gpt-5 families)
|
||||
model_lower = self.model.lower()
|
||||
is_reasoning_model = any(x in model_lower for x in ["gpt-5", "o1", "o3"])
|
||||
is_reasoning_model = any(x in model_lower for x in ["gpt-5", "o1", "o3", "deepseek"])
|
||||
|
||||
# For GPT-4 and GPT-4.1 models, cap max_completion_tokens to 32000
|
||||
# For GPT-4o models, cap to 16384
|
||||
|
|
@ -194,7 +194,7 @@ class LLMProvider:
|
|||
call_params["temperature"] = temperature
|
||||
|
||||
# Set reasoning_effort for reasoning models (OpenAI gpt-5, o1, o3)
|
||||
if is_reasoning_model and self.provider == "openai":
|
||||
if is_reasoning_model:
|
||||
call_params["reasoning_effort"] = self.reasoning_effort
|
||||
|
||||
# Provider-specific parameters
|
||||
|
|
@ -203,7 +203,6 @@ class LLMProvider:
|
|||
extra_body = {"service_tier": "auto"}
|
||||
# Only add reasoning parameters for reasoning models
|
||||
if is_reasoning_model:
|
||||
extra_body["reasoning_effort"] = self.reasoning_effort
|
||||
extra_body["include_reasoning"] = False
|
||||
call_params["extra_body"] = extra_body
|
||||
|
||||
|
|
|
|||
|
|
@ -107,6 +107,10 @@ async def retain_batch(
|
|||
)
|
||||
|
||||
if not extracted_facts:
|
||||
total_time = time.time() - start_time
|
||||
logger.info(
|
||||
f"RETAIN_BATCH COMPLETE: 0 facts extracted from {len(contents)} contents in {total_time:.3f}s (nothing to store)"
|
||||
)
|
||||
return [[] for _ in contents]
|
||||
|
||||
# Apply fact_type_override if provided
|
||||
|
|
|
|||
|
|
@ -127,8 +127,10 @@ def main():
|
|||
port=args.port,
|
||||
log_level=args.log_level,
|
||||
mcp_enabled=config.mcp_enabled,
|
||||
graph_retriever=config.graph_retriever,
|
||||
)
|
||||
config.configure_logging()
|
||||
config.log_config()
|
||||
|
||||
# Register cleanup handlers
|
||||
atexit.register(_cleanup)
|
||||
|
|
|
|||
|
|
@ -28,7 +28,15 @@ Environment variables:
|
|||
HINDSIGHT_API_LLM_PROVIDER: Optional. LLM provider (default: "openai").
|
||||
HINDSIGHT_API_LLM_MODEL: Optional. LLM model (default: "gpt-4o-mini").
|
||||
HINDSIGHT_API_MCP_LOCAL_BANK_ID: Optional. Memory bank ID (default: "mcp").
|
||||
HINDSIGHT_API_LOG_LEVEL: Optional. Log level (default: "info").
|
||||
HINDSIGHT_API_LOG_LEVEL: Optional. Log level (default: "warning").
|
||||
HINDSIGHT_API_MCP_INSTRUCTIONS: Optional. Additional instructions appended to both retain and recall tools.
|
||||
|
||||
Example custom instructions (these are ADDED to the default behavior):
|
||||
To also store assistant actions:
|
||||
HINDSIGHT_API_MCP_INSTRUCTIONS="Also store every action you take, including tool calls, code written, and decisions made."
|
||||
|
||||
To also store conversation summaries:
|
||||
HINDSIGHT_API_MCP_INSTRUCTIONS="Also store summaries of important conversations and their outcomes."
|
||||
"""
|
||||
|
||||
import logging
|
||||
|
|
@ -36,14 +44,19 @@ import os
|
|||
import sys
|
||||
|
||||
from mcp.server.fastmcp import FastMCP
|
||||
from mcp.types import Icon
|
||||
|
||||
from hindsight_api.config import (
|
||||
DEFAULT_MCP_LOCAL_BANK_ID,
|
||||
DEFAULT_MCP_RECALL_DESCRIPTION,
|
||||
DEFAULT_MCP_RETAIN_DESCRIPTION,
|
||||
ENV_MCP_INSTRUCTIONS,
|
||||
ENV_MCP_LOCAL_BANK_ID,
|
||||
)
|
||||
|
||||
# Configure logging - default to info
|
||||
_log_level_str = os.environ.get("HINDSIGHT_API_LOG_LEVEL", "info").lower()
|
||||
# Configure logging - default to warning to avoid polluting stderr during MCP init
|
||||
# MCP clients interpret stderr output as errors, so we suppress INFO logs by default
|
||||
_log_level_str = os.environ.get("HINDSIGHT_API_LOG_LEVEL", "warning").lower()
|
||||
_log_level_map = {
|
||||
"critical": logging.CRITICAL,
|
||||
"error": logging.ERROR,
|
||||
|
|
@ -79,22 +92,21 @@ def create_local_mcp_server(bank_id: str, memory=None) -> FastMCP:
|
|||
if memory is None:
|
||||
memory = MemoryEngine(db_url="pg0://hindsight-mcp")
|
||||
|
||||
# Get custom instructions from environment variable (appended to both tools)
|
||||
extra_instructions = os.environ.get(ENV_MCP_INSTRUCTIONS, "")
|
||||
|
||||
retain_description = DEFAULT_MCP_RETAIN_DESCRIPTION
|
||||
recall_description = DEFAULT_MCP_RECALL_DESCRIPTION
|
||||
|
||||
if extra_instructions:
|
||||
retain_description = f"{DEFAULT_MCP_RETAIN_DESCRIPTION}\n\nAdditional instructions: {extra_instructions}"
|
||||
recall_description = f"{DEFAULT_MCP_RECALL_DESCRIPTION}\n\nAdditional instructions: {extra_instructions}"
|
||||
|
||||
mcp = FastMCP("hindsight")
|
||||
|
||||
@mcp.tool()
|
||||
@mcp.tool(description=retain_description)
|
||||
async def retain(content: str, context: str = "general") -> dict:
|
||||
"""
|
||||
Store important information to long-term memory.
|
||||
|
||||
Use this tool PROACTIVELY whenever the user shares:
|
||||
- Personal facts, preferences, or interests
|
||||
- Important events or milestones
|
||||
- User history, experiences, or background
|
||||
- Decisions, opinions, or stated preferences
|
||||
- Goals, plans, or future intentions
|
||||
- Relationships or people mentioned
|
||||
- Work context, projects, or responsibilities
|
||||
|
||||
Args:
|
||||
content: The fact/memory to store (be specific and include relevant details)
|
||||
context: Category for the memory (e.g., 'preferences', 'work', 'hobbies', 'family'). Default: 'general'
|
||||
|
|
@ -111,17 +123,9 @@ def create_local_mcp_server(bank_id: str, memory=None) -> FastMCP:
|
|||
asyncio.create_task(_retain())
|
||||
return {"status": "accepted", "message": "Memory storage initiated"}
|
||||
|
||||
@mcp.tool()
|
||||
@mcp.tool(description=recall_description)
|
||||
async def recall(query: str, max_tokens: int = 4096, budget: str = "low") -> dict:
|
||||
"""
|
||||
Search memories to provide personalized, context-aware responses.
|
||||
|
||||
Use this tool PROACTIVELY to:
|
||||
- Check user's preferences before making suggestions
|
||||
- Recall user's history to provide continuity
|
||||
- Remember user's goals and context
|
||||
- Personalize responses based on past interactions
|
||||
|
||||
Args:
|
||||
query: Natural language search query (e.g., "user's food preferences", "what projects is user working on")
|
||||
max_tokens: Maximum tokens to return in results (default: 4096)
|
||||
|
|
@ -153,10 +157,9 @@ async def _initialize_and_run(bank_id: str):
|
|||
from hindsight_api import MemoryEngine
|
||||
|
||||
# Create and initialize memory engine with pg0 embedded database
|
||||
print("Initializing memory engine...", file=sys.stderr)
|
||||
# Note: We avoid printing to stderr during init as MCP clients show it as "errors"
|
||||
memory = MemoryEngine(db_url="pg0://hindsight-mcp")
|
||||
await memory.initialize()
|
||||
print("Memory engine initialized.", file=sys.stderr)
|
||||
|
||||
# Create and run the server
|
||||
mcp = create_local_mcp_server(bank_id, memory=memory)
|
||||
|
|
@ -179,8 +182,8 @@ def main():
|
|||
# Get bank ID from environment, default to "mcp"
|
||||
bank_id = os.environ.get(ENV_MCP_LOCAL_BANK_ID, DEFAULT_MCP_LOCAL_BANK_ID)
|
||||
|
||||
# Print startup message to stderr (stdout is reserved for MCP protocol)
|
||||
print(f"Hindsight MCP server starting (bank_id={bank_id})...", file=sys.stderr)
|
||||
# Note: We don't print to stderr as MCP clients display it as "error output"
|
||||
# Use HINDSIGHT_API_LOG_LEVEL=debug for verbose startup logging
|
||||
|
||||
# Run the async initialization and server
|
||||
asyncio.run(_initialize_and_run(bank_id))
|
||||
|
|
|
|||
|
|
@ -91,6 +91,9 @@ enum Commands {
|
|||
#[command(alias = "tui")]
|
||||
Explore,
|
||||
|
||||
/// Launch the web-based control plane UI
|
||||
Ui,
|
||||
|
||||
/// Configure the CLI (API URL, etc.)
|
||||
#[command(after_help = "Configuration priority:\n 1. Environment variable (HINDSIGHT_API_URL) - highest priority\n 2. Config file (~/.hindsight/config)\n 3. Default (http://localhost:8888)")]
|
||||
Configure {
|
||||
|
|
@ -373,6 +376,11 @@ fn run() -> Result<()> {
|
|||
return handle_configure(api_url, output_format);
|
||||
}
|
||||
|
||||
// Handle ui command - needs config but not API client
|
||||
if let Commands::Ui = cli.command {
|
||||
return handle_ui(output_format);
|
||||
}
|
||||
|
||||
// Load configuration
|
||||
let config = Config::from_env().unwrap_or_else(|e| {
|
||||
ui::print_error(&format!("Configuration error: {}", e));
|
||||
|
|
@ -390,6 +398,7 @@ fn run() -> Result<()> {
|
|||
// Execute command and handle errors
|
||||
let result: Result<()> = match cli.command {
|
||||
Commands::Configure { .. } => unreachable!(), // Handled above
|
||||
Commands::Ui => unreachable!(), // Handled above
|
||||
Commands::Explore => commands::explore::run(&client),
|
||||
Commands::Bank(bank_cmd) => match bank_cmd {
|
||||
BankCommands::List => commands::bank::list(&client, verbose, output_format),
|
||||
|
|
@ -521,3 +530,50 @@ fn handle_configure(api_url: Option<String>, output_format: OutputFormat) -> Res
|
|||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn handle_ui(output_format: OutputFormat) -> Result<()> {
|
||||
use std::process::Command;
|
||||
|
||||
// Load configuration to get the API URL
|
||||
let config = Config::load().unwrap_or_else(|e| {
|
||||
ui::print_error(&format!("Configuration error: {}", e));
|
||||
errors::print_config_help();
|
||||
std::process::exit(1);
|
||||
});
|
||||
|
||||
let api_url = config.api_url();
|
||||
|
||||
if output_format == OutputFormat::Pretty {
|
||||
ui::print_info("Launching Hindsight Control Plane UI...");
|
||||
println!();
|
||||
println!(" API URL: {}", api_url);
|
||||
println!();
|
||||
}
|
||||
|
||||
// Run npx @vectorize-io/hindsight-control-plane --api-url {api_url}
|
||||
let status = Command::new("npx")
|
||||
.arg("@vectorize-io/hindsight-control-plane")
|
||||
.arg("--api-url")
|
||||
.arg(api_url)
|
||||
.status();
|
||||
|
||||
match status {
|
||||
Ok(exit_status) => {
|
||||
if !exit_status.success() {
|
||||
if let Some(code) = exit_status.code() {
|
||||
std::process::exit(code);
|
||||
} else {
|
||||
std::process::exit(1);
|
||||
}
|
||||
}
|
||||
}
|
||||
Err(e) => {
|
||||
ui::print_error(&format!("Failed to launch control plane UI: {}", e));
|
||||
ui::print_info("Make sure you have Node.js and npm installed.");
|
||||
ui::print_info("You can also install the control plane globally: npm install -g @vectorize-io/hindsight-control-plane");
|
||||
std::process::exit(1);
|
||||
}
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
|
|
|||
1
hindsight-control-plane/.gitignore
vendored
1
hindsight-control-plane/.gitignore
vendored
|
|
@ -14,6 +14,7 @@
|
|||
|
||||
# production
|
||||
/build
|
||||
/standalone
|
||||
|
||||
# misc
|
||||
.DS_Store
|
||||
|
|
|
|||
86
hindsight-control-plane/bin/cli.js
Executable file
86
hindsight-control-plane/bin/cli.js
Executable file
|
|
@ -0,0 +1,86 @@
|
|||
#!/usr/bin/env node
|
||||
|
||||
const { spawn } = require('child_process');
|
||||
const path = require('path');
|
||||
const fs = require('fs');
|
||||
|
||||
const args = process.argv.slice(2);
|
||||
|
||||
// Parse command line arguments
|
||||
let port = process.env.PORT || 9999;
|
||||
let hostname = process.env.HOSTNAME || '0.0.0.0';
|
||||
let apiUrl = process.env.HINDSIGHT_CP_DATAPLANE_API_URL;
|
||||
|
||||
for (let i = 0; i < args.length; i++) {
|
||||
if (args[i] === '--port' || args[i] === '-p') {
|
||||
port = args[++i];
|
||||
} else if (args[i] === '--hostname' || args[i] === '-H') {
|
||||
hostname = args[++i];
|
||||
} else if (args[i] === '--api-url' || args[i] === '-a') {
|
||||
apiUrl = args[++i];
|
||||
} else if (args[i] === '--help' || args[i] === '-h') {
|
||||
console.log(`
|
||||
Hindsight Control Plane
|
||||
|
||||
Usage: hindsight-control-plane [options]
|
||||
|
||||
Options:
|
||||
-p, --port <port> Port to listen on (default: 9999, env: PORT)
|
||||
-H, --hostname <host> Hostname to bind to (default: 0.0.0.0, env: HOSTNAME)
|
||||
-a, --api-url <url> Hindsight API URL (env: HINDSIGHT_CP_DATAPLANE_API_URL)
|
||||
-h, --help Show this help message
|
||||
|
||||
Environment Variables:
|
||||
PORT Port to listen on
|
||||
HOSTNAME Hostname to bind to
|
||||
HINDSIGHT_CP_DATAPLANE_API_URL URL of the Hindsight API server
|
||||
`);
|
||||
process.exit(0);
|
||||
}
|
||||
}
|
||||
|
||||
// Find the standalone server
|
||||
const standaloneDir = path.join(__dirname, '..', 'standalone');
|
||||
const serverPath = path.join(standaloneDir, 'server.js');
|
||||
|
||||
if (!fs.existsSync(serverPath)) {
|
||||
console.error('Error: Standalone server not found at', serverPath);
|
||||
console.error('This package may not have been built correctly.');
|
||||
process.exit(1);
|
||||
}
|
||||
|
||||
// Set up environment
|
||||
const env = {
|
||||
...process.env,
|
||||
PORT: String(port),
|
||||
HOSTNAME: hostname,
|
||||
};
|
||||
|
||||
if (apiUrl) {
|
||||
env.HINDSIGHT_CP_DATAPLANE_API_URL = apiUrl;
|
||||
}
|
||||
|
||||
console.log(`Starting Hindsight Control Plane on http://${hostname}:${port}`);
|
||||
if (apiUrl) {
|
||||
console.log(`API URL: ${apiUrl}`);
|
||||
}
|
||||
|
||||
// Run the standalone server
|
||||
const server = spawn('node', [serverPath], {
|
||||
cwd: standaloneDir,
|
||||
env,
|
||||
stdio: 'inherit',
|
||||
});
|
||||
|
||||
server.on('error', (err) => {
|
||||
console.error('Failed to start server:', err.message);
|
||||
process.exit(1);
|
||||
});
|
||||
|
||||
server.on('close', (code) => {
|
||||
process.exit(code || 0);
|
||||
});
|
||||
|
||||
// Handle signals
|
||||
process.on('SIGTERM', () => server.kill('SIGTERM'));
|
||||
process.on('SIGINT', () => server.kill('SIGINT'));
|
||||
|
|
@ -1,17 +1,26 @@
|
|||
{
|
||||
"name": "hindsight-control-plane",
|
||||
"name": "@vectorize-io/hindsight-control-plane",
|
||||
"version": "0.1.8",
|
||||
"private": true,
|
||||
"description": "Control plane for Hindsight - Semantic memory system",
|
||||
"bin": {
|
||||
"hindsight-control-plane": "./bin/cli.js"
|
||||
},
|
||||
"files": [
|
||||
"bin",
|
||||
"standalone",
|
||||
"public"
|
||||
],
|
||||
"scripts": {
|
||||
"dev": "next dev",
|
||||
"build": "next build",
|
||||
"build": "next build && npm run build:standalone",
|
||||
"build:standalone": "rm -rf standalone && STANDALONE_ROOT=$(dirname $(find .next/standalone -name 'server.js' | head -1)) && cp -r \"$STANDALONE_ROOT\" standalone && mkdir -p standalone/.next && cp -r .next/static standalone/.next/static && mkdir -p standalone/public && cp -r public/* standalone/public/ 2>/dev/null || true",
|
||||
"start": "next start",
|
||||
"lint": "next lint"
|
||||
"lint": "next lint",
|
||||
"prepublishOnly": "npm run build"
|
||||
},
|
||||
"keywords": [],
|
||||
"keywords": ["hindsight", "memory", "semantic", "ai"],
|
||||
"author": "Hindsight Team",
|
||||
"license": "ISC",
|
||||
"description": "Control plane for Hindsight - Semantic memory system",
|
||||
"dependencies": {
|
||||
"@radix-ui/react-checkbox": "^1.3.3",
|
||||
"@radix-ui/react-dialog": "^1.1.15",
|
||||
|
|
@ -27,7 +36,6 @@
|
|||
"@types/node": "^24.10.0",
|
||||
"@types/react": "^19.2.2",
|
||||
"@types/react-dom": "^19.2.2",
|
||||
"@vectorize-io/hindsight-client": "file:../hindsight-clients/typescript",
|
||||
"autoprefixer": "^10.4.21",
|
||||
"class-variance-authority": "^0.7.1",
|
||||
"clsx": "^2.1.1",
|
||||
|
|
@ -50,6 +58,7 @@
|
|||
"typescript": "^5.9.3"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@vectorize-io/hindsight-client": "file:../hindsight-clients/typescript",
|
||||
"@eslint/eslintrc": "^3.3.3",
|
||||
"@eslint/js": "^9.39.2",
|
||||
"eslint-plugin-react": "^7.37.5",
|
||||
|
|
|
|||
|
|
@ -133,6 +133,38 @@ hindsight-api --workers 4 # Multiple worker processes
|
|||
hindsight-api --log-level debug # Verbose logging
|
||||
```
|
||||
|
||||
### Control Plane
|
||||
|
||||
The Control Plane (Web UI) can be run standalone using npx:
|
||||
|
||||
```bash
|
||||
npx @vectorize-io/hindsight-control-plane --api-url http://localhost:8888
|
||||
```
|
||||
|
||||
This connects to your running API server and provides a visual interface for managing memory banks, exploring entities, and testing queries.
|
||||
|
||||
#### Options
|
||||
|
||||
| Option | Environment Variable | Default | Description |
|
||||
|--------|---------------------|---------|-------------|
|
||||
| `-p, --port` | `PORT` | 9999 | Port to listen on |
|
||||
| `-H, --hostname` | `HOSTNAME` | 0.0.0.0 | Hostname to bind to |
|
||||
| `-a, --api-url` | `HINDSIGHT_CP_DATAPLANE_API_URL` | http://localhost:8888 | Hindsight API URL |
|
||||
|
||||
#### Examples
|
||||
|
||||
```bash
|
||||
# Run on custom port
|
||||
npx @vectorize-io/hindsight-control-plane --port 9999 --api-url http://localhost:8888
|
||||
|
||||
# Using environment variables
|
||||
export HINDSIGHT_CP_DATAPLANE_API_URL=http://api.example.com
|
||||
npx @vectorize-io/hindsight-control-plane
|
||||
|
||||
# Production deployment
|
||||
PORT=80 HINDSIGHT_CP_DATAPLANE_API_URL=https://api.hindsight.io npx @vectorize-io/hindsight-control-plane
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Next Steps
|
||||
|
|
|
|||
|
|
@ -25,12 +25,10 @@ Web UI for managing and exploring your memory banks:
|
|||
- View ingestion history and operations
|
||||
- Test recall queries interactively
|
||||
|
||||
```
|
||||
hindsight-control-plane # Default port: 9999
|
||||
```
|
||||
|
||||
The Control Plane connects to the API service and provides a visual interface for development and debugging.
|
||||
|
||||
For bare metal deployments, you can run the Control Plane standalone using npx. See [Installation - Bare Metal](./installation#control-plane) for details.
|
||||
|
||||
## Deployment Options
|
||||
|
||||
| Deployment | Services | Use Case |
|
||||
|
|
@ -39,4 +37,4 @@ The Control Plane connects to the API service and provides a visual interface fo
|
|||
| **Helm / Kubernetes** | Separate pods | Production, scaling |
|
||||
| **Bare metal** | Run independently | Custom deployments |
|
||||
|
||||
In the Docker quickstart, both services run in a single container. For production Kubernetes deployments, they run as separate pods with independent scaling.
|
||||
In the Docker quickstart, both services run in a single container. For production Kubernetes deployments, they run as separate pods with independent scaling. For bare metal, you can run the API via pip and the Control Plane via npx.
|
||||
|
|
|
|||
|
|
@ -177,6 +177,25 @@ hindsight memory recall <bank_id> "query" -o yaml
|
|||
| `--help` | Show help |
|
||||
| `--version` | Show version |
|
||||
|
||||
## Control Plane UI
|
||||
|
||||
Launch the web-based Control Plane UI directly from the CLI:
|
||||
|
||||
```bash
|
||||
hindsight ui
|
||||
```
|
||||
|
||||
This runs the Control Plane locally on port 9999 using the API URL from your configuration. The UI provides:
|
||||
|
||||
- **Memory bank management** — Browse and manage all your banks
|
||||
- **Entity explorer** — Visualize the knowledge graph
|
||||
- **Query testing** — Interactive recall and reflect testing
|
||||
- **Operation history** — View ingestion and processing logs
|
||||
|
||||
:::tip
|
||||
The UI command requires Node.js to be installed. It automatically downloads and runs the `@vectorize-io/hindsight-control-plane` package via npx.
|
||||
:::
|
||||
|
||||
## Interactive Explorer
|
||||
|
||||
Launch the TUI explorer for visual navigation of your memory banks:
|
||||
|
|
|
|||
|
|
@ -7,28 +7,35 @@ sidebar_position: 2
|
|||
Hindsight provides a fully local MCP server that runs entirely on your machine with an embedded PostgreSQL database. No external server or database setup required.
|
||||
|
||||
This is ideal for:
|
||||
- **Personal use with Claude Code** — Give Claude long-term memory across conversations
|
||||
- **Personal use with Claude Desktop** — Give Claude long-term memory across conversations
|
||||
- **Development and testing** — Quick setup without infrastructure
|
||||
- **Privacy-focused setups** — All data stays on your machine
|
||||
|
||||
## Quick Start
|
||||
|
||||
### With uvx (recommended)
|
||||
## Quick Install
|
||||
|
||||
```bash
|
||||
uvx --from hindsight-api hindsight-local-mcp
|
||||
curl -fsSL https://hindsight.vectorize.io/get-mcp | bash -s -- \
|
||||
--app claude-desktop \
|
||||
--set HINDSIGHT_API_LLM_API_KEY=sk-...
|
||||
```
|
||||
|
||||
### With pip
|
||||
This script will:
|
||||
1. Install [uv](https://docs.astral.sh/uv/) if not already installed
|
||||
2. Configure Claude Desktop to use the Hindsight MCP server
|
||||
3. Set the provided environment variables in the MCP configuration
|
||||
|
||||
```bash
|
||||
pip install hindsight-api
|
||||
hindsight-local-mcp
|
||||
```
|
||||
:::info Other MCP Applications
|
||||
The quick install script currently supports Claude Desktop only. For other MCP-compatible applications (Cursor, Cline, etc.), follow the [Manual Configuration](#manual-configuration) steps below.
|
||||
:::
|
||||
|
||||
## Claude Code Configuration
|
||||
## Manual Configuration
|
||||
|
||||
Add to your Claude Code MCP settings (`~/.claude/claude_desktop_config.json`):
|
||||
Add the following to your MCP client's configuration. For Claude Desktop:
|
||||
|
||||
- **macOS**: `~/Library/Application Support/Claude/claude_desktop_config.json`
|
||||
- **Linux**: `~/.config/Claude/claude_desktop_config.json`
|
||||
|
||||
For other MCP clients, refer to their documentation for the configuration file location.
|
||||
|
||||
```json
|
||||
{
|
||||
|
|
@ -37,7 +44,7 @@ Add to your Claude Code MCP settings (`~/.claude/claude_desktop_config.json`):
|
|||
"command": "uvx",
|
||||
"args": ["--from", "hindsight-api", "hindsight-local-mcp"],
|
||||
"env": {
|
||||
"HINDSIGHT_API_LLM_API_KEY": "your-openai-key"
|
||||
"HINDSIGHT_API_LLM_API_KEY": "sk-..."
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -55,7 +62,7 @@ By default, memories are stored in a bank called `mcp`. To use a different bank:
|
|||
"command": "uvx",
|
||||
"args": ["--from", "hindsight-api", "hindsight-local-mcp"],
|
||||
"env": {
|
||||
"HINDSIGHT_API_LLM_API_KEY": "your-openai-key",
|
||||
"HINDSIGHT_API_LLM_API_KEY": "sk-...",
|
||||
"HINDSIGHT_API_MCP_LOCAL_BANK_ID": "my-personal-memory"
|
||||
}
|
||||
}
|
||||
|
|
@ -72,6 +79,20 @@ All standard [Hindsight configuration variables](/developer/configuration) are s
|
|||
| Variable | Required | Default | Description |
|
||||
|----------|----------|---------|-------------|
|
||||
| `HINDSIGHT_API_MCP_LOCAL_BANK_ID` | No | `mcp` | Memory bank ID to use |
|
||||
| `HINDSIGHT_API_MCP_INSTRUCTIONS` | No | - | Additional instructions appended to both `retain` and `recall` tools |
|
||||
|
||||
### Customizing Tool Behavior
|
||||
|
||||
You can customize what gets stored by adding instructions to the tools. Re-run the install script with the additional `--set` flag:
|
||||
|
||||
```bash
|
||||
curl -fsSL https://hindsight.vectorize.io/get-mcp | bash -s -- \
|
||||
--app claude-desktop \
|
||||
--set HINDSIGHT_API_LLM_API_KEY=sk-... \
|
||||
--set HINDSIGHT_API_MCP_INSTRUCTIONS="Also store every action you take, code you write, and files you modify."
|
||||
```
|
||||
|
||||
These instructions are appended to the default tool descriptions, guiding Claude on when and how to use the memory tools.
|
||||
|
||||
## Available Tools
|
||||
|
||||
|
|
|
|||
301
hindsight-docs/static/get-mcp
Executable file
301
hindsight-docs/static/get-mcp
Executable file
|
|
@ -0,0 +1,301 @@
|
|||
#!/bin/bash
|
||||
#
|
||||
# Install Hindsight MCP server for Claude Desktop
|
||||
#
|
||||
# Usage:
|
||||
# curl -fsSL https://hindsight.vectorize.io/get-mcp | bash -s -- --app claude-desktop --set HINDSIGHT_API_LLM_API_KEY=YOUR_KEY
|
||||
#
|
||||
# Options:
|
||||
# --app Required. Target application (currently only: claude-desktop)
|
||||
# --set ENV=VALUE Set environment variable (can be repeated)
|
||||
#
|
||||
# Examples:
|
||||
# # With OpenAI
|
||||
# curl -fsSL https://hindsight.vectorize.io/get-mcp | bash -s -- \
|
||||
# --app claude-desktop \
|
||||
# --set HINDSIGHT_API_LLM_API_KEY=sk-...
|
||||
#
|
||||
# # With Ollama (local LLM)
|
||||
# curl -fsSL https://hindsight.vectorize.io/get-mcp | bash -s -- \
|
||||
# --app claude-desktop \
|
||||
# --set HINDSIGHT_API_LLM_PROVIDER=ollama \
|
||||
# --set HINDSIGHT_API_LLM_MODEL=llama3.2
|
||||
#
|
||||
# # With custom memory instructions
|
||||
# curl -fsSL https://hindsight.vectorize.io/get-mcp | bash -s -- \
|
||||
# --app claude-desktop \
|
||||
# --set HINDSIGHT_API_LLM_API_KEY=sk-... \
|
||||
# --set HINDSIGHT_API_MCP_INSTRUCTIONS="Also store every action you take and code you write."
|
||||
#
|
||||
|
||||
set -e
|
||||
|
||||
# Colors for output
|
||||
RED='\033[0;31m'
|
||||
GREEN='\033[0;32m'
|
||||
YELLOW='\033[1;33m'
|
||||
BLUE='\033[0;34m'
|
||||
NC='\033[0m' # No Color
|
||||
|
||||
print_info() {
|
||||
echo -e "${BLUE}ℹ${NC} $1"
|
||||
}
|
||||
|
||||
print_success() {
|
||||
echo -e "${GREEN}✓${NC} $1"
|
||||
}
|
||||
|
||||
print_error() {
|
||||
echo -e "${RED}✗${NC} $1"
|
||||
exit 1
|
||||
}
|
||||
|
||||
print_warning() {
|
||||
echo -e "${YELLOW}⚠${NC} $1"
|
||||
}
|
||||
|
||||
print_banner() {
|
||||
echo ""
|
||||
# ANSI logo
|
||||
echo -e " \033[38;2;9;127;184m▄\033[0m\033[48;2;8;130;178m\033[38;2;5;133;186m▄\033[0m \033[48;2;10;143;160m\033[38;2;10;143;165m▄\033[0m\033[38;2;7;140;156m▄\033[0m "
|
||||
echo -e " \033[38;2;8;125;192m▄\033[0m \033[38;2;3;132;191m▀\033[0m\033[38;2;2;133;192m▄\033[0m \033[38;2;3;132;180m▄\033[0m\033[38;2;1;137;184m▄\033[0m\033[38;2;3;133;174m▄\033[0m \033[38;2;3;142;176m▄\033[0m\033[38;2;4;142;169m▀\033[0m \033[38;2;10;144;164m▄\033[0m "
|
||||
echo -e "\033[38;2;6;121;195m▀\033[0m\033[38;2;5;128;203m▀\033[0m\033[48;2;5;124;195m\033[38;2;3;125;200m▄\033[0m\033[38;2;2;126;196m▄\033[0m\033[48;2;3;128;188m\033[38;2;1;131;196m▄\033[0m\033[48;2;0;152;219m\033[38;2;2;131;191m▄\033[0m\033[38;2;1;141;196m▀\033[0m\033[38;2;1;135;183m▀\033[0m\033[38;2;1;148;198m▀\033[0m\033[48;2;1;156;202m\033[38;2;2;135;180m▄\033[0m\033[48;2;4;134;169m\033[38;2;1;137;177m▄\033[0m\033[38;2;3;138;173m▄\033[0m\033[48;2;6;137;165m\033[38;2;2;140;170m▄\033[0m\033[38;2;7;144;169m▀\033[0m\033[38;2;7;139;158m▀\033[0m"
|
||||
echo -e " \033[48;2;2;128;202m\033[38;2;2;124;201m▄\033[0m\033[48;2;1;130;201m\033[38;2;0;135;212m▄\033[0m\033[38;2;2;128;196m▄\033[0m \033[48;2;2;142;204m\033[38;2;7;138;199m▄\033[0m \033[38;2;1;135;186m▄\033[0m\033[48;2;1;142;186m\033[38;2;2;144;194m▄\033[0m\033[48;2;3;138;176m\033[38;2;2;134;176m▄\033[0m "
|
||||
echo -e " \033[48;2;8;118;200m\033[38;2;8;121;209m▄\033[0m\033[38;2;3;121;203m▀\033[0m \033[38;2;3;122;192m▀\033[0m\033[38;2;1;138;216m▀\033[0m\033[48;2;0;138;210m\033[38;2;3;128;198m▄\033[0m\033[48;2;0;126;188m\033[38;2;2;131;198m▄\033[0m\033[48;2;0;142;205m\033[38;2;3;132;193m▄\033[0m\033[38;2;1;140;196m▀\033[0m \033[38;2;4;134;175m▀\033[0m\033[48;2;13;135;167m\033[38;2;8;136;174m▄\033[0m "
|
||||
echo ""
|
||||
echo -e " ${BLUE}HINDSIGHT MCP INSTALLER${NC}"
|
||||
echo ""
|
||||
}
|
||||
|
||||
# Parse arguments
|
||||
APP=""
|
||||
declare -a ENV_VARS=()
|
||||
|
||||
while [[ $# -gt 0 ]]; do
|
||||
case $1 in
|
||||
--app)
|
||||
APP="$2"
|
||||
shift 2
|
||||
;;
|
||||
--set)
|
||||
ENV_VARS+=("$2")
|
||||
shift 2
|
||||
;;
|
||||
-h|--help)
|
||||
echo "Usage: $0 --app <app> --set ENV=VALUE [--set ENV2=VALUE2 ...]"
|
||||
echo ""
|
||||
echo "Options:"
|
||||
echo " --app Required. Target application (currently only: claude-desktop)"
|
||||
echo " --set ENV=VALUE Set environment variable (can be repeated)"
|
||||
echo ""
|
||||
echo "Examples:"
|
||||
echo " # With OpenAI"
|
||||
echo " $0 --app claude-desktop --set HINDSIGHT_API_LLM_API_KEY=sk-..."
|
||||
echo ""
|
||||
echo " # With Ollama (local LLM, no API key needed)"
|
||||
echo " $0 --app claude-desktop --set HINDSIGHT_API_LLM_PROVIDER=ollama --set HINDSIGHT_API_LLM_MODEL=llama3.2"
|
||||
exit 0
|
||||
;;
|
||||
*)
|
||||
print_error "Unknown option: $1. Use --help for usage."
|
||||
;;
|
||||
esac
|
||||
done
|
||||
|
||||
# Validate required arguments
|
||||
if [ -z "$APP" ]; then
|
||||
print_error "Missing required argument: --app. Use --help for usage."
|
||||
fi
|
||||
|
||||
if [ "$APP" != "claude-desktop" ]; then
|
||||
print_error "Unsupported app: $APP. Currently only 'claude-desktop' is supported."
|
||||
fi
|
||||
|
||||
# Detect OS
|
||||
detect_os() {
|
||||
case "$(uname -s)" in
|
||||
Darwin*) echo "macos" ;;
|
||||
Linux*) echo "linux" ;;
|
||||
MINGW*|MSYS*|CYGWIN*) echo "windows" ;;
|
||||
*) echo "unknown" ;;
|
||||
esac
|
||||
}
|
||||
|
||||
OS=$(detect_os)
|
||||
|
||||
# Get Claude Desktop config path based on OS
|
||||
get_claude_config_path() {
|
||||
case "$OS" in
|
||||
macos)
|
||||
echo "$HOME/Library/Application Support/Claude/claude_desktop_config.json"
|
||||
;;
|
||||
linux)
|
||||
echo "$HOME/.config/Claude/claude_desktop_config.json"
|
||||
;;
|
||||
windows)
|
||||
echo "$APPDATA/Claude/claude_desktop_config.json"
|
||||
;;
|
||||
*)
|
||||
print_error "Unsupported operating system: $OS"
|
||||
;;
|
||||
esac
|
||||
}
|
||||
|
||||
# Check if uvx is installed and return its path
|
||||
find_uvx() {
|
||||
# Check if in PATH
|
||||
if command -v uvx &> /dev/null; then
|
||||
command -v uvx
|
||||
return 0
|
||||
fi
|
||||
|
||||
# Check common installation paths
|
||||
local paths=(
|
||||
"$HOME/.local/bin/uvx"
|
||||
"$HOME/.cargo/bin/uvx"
|
||||
"/usr/local/bin/uvx"
|
||||
)
|
||||
|
||||
for path in "${paths[@]}"; do
|
||||
if [ -f "$path" ]; then
|
||||
echo "$path"
|
||||
return 0
|
||||
fi
|
||||
done
|
||||
|
||||
return 1
|
||||
}
|
||||
|
||||
# Install uv (which includes uvx)
|
||||
install_uv() {
|
||||
print_info "Installing uv..."
|
||||
|
||||
if [ "$OS" = "windows" ]; then
|
||||
powershell -ExecutionPolicy ByPass -c "irm https://astral.sh/uv/install.ps1 | iex"
|
||||
else
|
||||
curl -LsSf https://astral.sh/uv/install.sh | sh
|
||||
fi
|
||||
|
||||
# Source the env to get uvx in path
|
||||
if [ -f "$HOME/.local/bin/env" ]; then
|
||||
source "$HOME/.local/bin/env"
|
||||
fi
|
||||
|
||||
# Find uvx again
|
||||
if ! UVX_PATH=$(find_uvx); then
|
||||
print_error "uv installed but uvx not found. Please check your installation."
|
||||
fi
|
||||
|
||||
print_success "uv installed successfully"
|
||||
}
|
||||
|
||||
# Update Claude Desktop config
|
||||
update_claude_config() {
|
||||
local config_path="$1"
|
||||
local uvx_path="$2"
|
||||
shift 2
|
||||
local env_vars=("$@")
|
||||
|
||||
# Create config directory if it doesn't exist
|
||||
mkdir -p "$(dirname "$config_path")"
|
||||
|
||||
# Check if jq is available (needed for both new and existing configs)
|
||||
if ! command -v jq &> /dev/null; then
|
||||
print_warning "jq not found. Installing..."
|
||||
if [ "$OS" = "macos" ]; then
|
||||
if command -v brew &> /dev/null; then
|
||||
brew install jq
|
||||
else
|
||||
print_error "Please install jq: brew install jq"
|
||||
fi
|
||||
elif [ "$OS" = "linux" ]; then
|
||||
if command -v apt-get &> /dev/null; then
|
||||
sudo apt-get install -y jq
|
||||
elif command -v yum &> /dev/null; then
|
||||
sudo yum install -y jq
|
||||
else
|
||||
print_error "Please install jq manually"
|
||||
fi
|
||||
fi
|
||||
fi
|
||||
|
||||
# Build the env object from env_vars array
|
||||
local env_json="{}"
|
||||
for env_var in "${env_vars[@]}"; do
|
||||
local key="${env_var%%=*}"
|
||||
local value="${env_var#*=}"
|
||||
env_json=$(echo "$env_json" | jq --arg k "$key" --arg v "$value" '. + {($k): $v}')
|
||||
done
|
||||
|
||||
# Build the hindsight server config
|
||||
local hindsight_config
|
||||
hindsight_config=$(jq -n \
|
||||
--arg uvx "$uvx_path" \
|
||||
--argjson env "$env_json" \
|
||||
'{
|
||||
"command": $uvx,
|
||||
"args": ["--from", "hindsight-api", "hindsight-local-mcp"],
|
||||
"env": $env
|
||||
}')
|
||||
|
||||
# Check if config file exists and has content
|
||||
if [ -f "$config_path" ] && [ -s "$config_path" ]; then
|
||||
print_info "Updating existing Claude Desktop config..."
|
||||
|
||||
# Backup existing config
|
||||
cp "$config_path" "${config_path}.backup"
|
||||
print_info "Backed up existing config to ${config_path}.backup"
|
||||
|
||||
# Add or update hindsight server in existing config
|
||||
local new_config
|
||||
new_config=$(jq --argjson hs "$hindsight_config" '.mcpServers.hindsight = $hs' "$config_path")
|
||||
|
||||
echo "$new_config" > "$config_path"
|
||||
else
|
||||
print_info "Creating new Claude Desktop config..."
|
||||
|
||||
# Create new config with hindsight server
|
||||
local new_config
|
||||
new_config=$(jq -n --argjson hs "$hindsight_config" '{"mcpServers": {"hindsight": $hs}}')
|
||||
echo "$new_config" > "$config_path"
|
||||
fi
|
||||
|
||||
print_success "Claude Desktop config updated: $config_path"
|
||||
}
|
||||
|
||||
# Main installation flow
|
||||
main() {
|
||||
print_banner
|
||||
|
||||
print_info "App: $APP"
|
||||
if [ ${#ENV_VARS[@]} -gt 0 ]; then
|
||||
print_info "Environment variables: ${#ENV_VARS[@]} configured"
|
||||
fi
|
||||
echo ""
|
||||
|
||||
# Step 1: Check/Install uvx
|
||||
print_info "Checking for uvx..."
|
||||
if UVX_PATH=$(find_uvx); then
|
||||
print_success "uvx found at: $UVX_PATH"
|
||||
else
|
||||
print_warning "uvx not found. Installing uv..."
|
||||
install_uv
|
||||
UVX_PATH=$(find_uvx)
|
||||
fi
|
||||
|
||||
# Step 2: Update Claude Desktop config
|
||||
CONFIG_PATH=$(get_claude_config_path)
|
||||
print_info "Configuring Claude Desktop..."
|
||||
update_claude_config "$CONFIG_PATH" "$UVX_PATH" "${ENV_VARS[@]}"
|
||||
|
||||
# Done!
|
||||
echo ""
|
||||
print_success "Installation complete!"
|
||||
echo ""
|
||||
print_info "Next steps:"
|
||||
echo " 1. Restart Claude Desktop"
|
||||
echo " 2. Look for the 'hindsight' tools (retain, recall) in Claude"
|
||||
echo ""
|
||||
}
|
||||
|
||||
main "$@"
|
||||
5437
package-lock.json
generated
5437
package-lock.json
generated
File diff suppressed because it is too large
Load diff
|
|
@ -2,7 +2,7 @@
|
|||
set -e
|
||||
|
||||
ROOT_DIR="$(git rev-parse --show-toplevel)"
|
||||
cd "$ROOT_DIR/hindsight-control-plane" || exit 1
|
||||
cd "$ROOT_DIR" || exit 1
|
||||
|
||||
# Check if .env exists in workspace root
|
||||
if [ ! -f "$ROOT_DIR/.env" ]; then
|
||||
|
|
@ -30,4 +30,4 @@ fi
|
|||
export HOSTNAME="${HINDSIGHT_CP_HOSTNAME:-0.0.0.0}"
|
||||
|
||||
# Run dev server
|
||||
npm run dev -w hindsight-control-plane
|
||||
npm run dev -w @vectorize-io/hindsight-control-plane
|
||||
Loading…
Reference in a new issue