Compare commits
1
Commits
fix-docdis
...
fix-embed
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
d25222ba9c |
@@ -193,22 +193,34 @@ print_banner
|
||||
|
||||
# Validate app parameter
|
||||
if [ -z "$APP" ]; then
|
||||
echo -e "${DIM}Select your AI coding assistant:${NC}"
|
||||
echo ""
|
||||
echo " ${BOLD}1)${NC} Claude Code"
|
||||
echo " ${BOLD}2)${NC} OpenCode"
|
||||
echo " ${BOLD}3)${NC} Codex CLI"
|
||||
echo ""
|
||||
read -p "Enter choice [1]: " app_choice
|
||||
app_choice=${app_choice:-1}
|
||||
# Check if we have a terminal for interactive prompts
|
||||
if [ -t 0 ] || [ -e /dev/tty ]; then
|
||||
echo -e "${DIM}Select your AI coding assistant:${NC}"
|
||||
echo ""
|
||||
echo " ${BOLD}1)${NC} Claude Code"
|
||||
echo " ${BOLD}2)${NC} OpenCode"
|
||||
echo " ${BOLD}3)${NC} Codex CLI"
|
||||
echo ""
|
||||
# Use /dev/tty for input if stdin is piped
|
||||
if [ -t 0 ]; then
|
||||
read -p "Enter choice [1]: " app_choice
|
||||
else
|
||||
read -p "Enter choice [1]: " app_choice </dev/tty
|
||||
fi
|
||||
app_choice=${app_choice:-1}
|
||||
|
||||
case $app_choice in
|
||||
1) APP="claude" ;;
|
||||
2) APP="opencode" ;;
|
||||
3) APP="codex" ;;
|
||||
*) APP="claude" ;;
|
||||
esac
|
||||
echo ""
|
||||
case $app_choice in
|
||||
1) APP="claude" ;;
|
||||
2) APP="opencode" ;;
|
||||
3) APP="codex" ;;
|
||||
*) APP="claude" ;;
|
||||
esac
|
||||
echo ""
|
||||
else
|
||||
# Non-interactive (CI) - default to claude
|
||||
APP="claude"
|
||||
print_info "Non-interactive mode detected, defaulting to Claude Code"
|
||||
fi
|
||||
fi
|
||||
|
||||
# Get skills directory for selected app
|
||||
|
||||
@@ -83,32 +83,141 @@ def get_config():
|
||||
}
|
||||
|
||||
|
||||
def do_configure(args):
|
||||
"""Interactive configuration setup with beautiful TUI."""
|
||||
import questionary
|
||||
from questionary import Style
|
||||
# Provider defaults: (provider_id, default_model, env_key_name)
|
||||
PROVIDER_DEFAULTS = {
|
||||
"openai": ("openai", "o3-mini", "OPENAI_API_KEY"),
|
||||
"groq": ("groq", "openai/gpt-oss-20b", "GROQ_API_KEY"),
|
||||
"google": ("google", "gemini-2.0-flash", "GOOGLE_API_KEY"),
|
||||
"ollama": ("ollama", "llama3.2", None),
|
||||
}
|
||||
|
||||
|
||||
def do_configure(args):
|
||||
"""Interactive configuration setup."""
|
||||
# If stdin is not a terminal (e.g., running via curl | bash),
|
||||
# reopen stdin from /dev/tty for interactive prompts
|
||||
# redirect stdin from /dev/tty for interactive prompts
|
||||
original_stdin = None
|
||||
if not sys.stdin.isatty():
|
||||
try:
|
||||
original_stdin = sys.stdin
|
||||
sys.stdin = open('/dev/tty', 'r')
|
||||
except OSError:
|
||||
print("Error: Cannot run interactive configuration without a terminal.", file=sys.stderr)
|
||||
print("Run directly: uvx hindsight-embed configure", file=sys.stderr)
|
||||
return 1
|
||||
# No terminal available - try non-interactive mode with env vars
|
||||
return _do_configure_from_env()
|
||||
|
||||
# Custom style for the prompts
|
||||
custom_style = Style([
|
||||
('qmark', 'fg:cyan bold'),
|
||||
('question', 'fg:white bold'),
|
||||
('answer', 'fg:cyan'),
|
||||
('pointer', 'fg:cyan bold'),
|
||||
('highlighted', 'fg:cyan bold'),
|
||||
('selected', 'fg:green'),
|
||||
('text', 'fg:white'),
|
||||
])
|
||||
try:
|
||||
return _do_configure_interactive()
|
||||
finally:
|
||||
if original_stdin is not None:
|
||||
sys.stdin.close()
|
||||
sys.stdin = original_stdin
|
||||
|
||||
|
||||
def _do_configure_from_env():
|
||||
"""Non-interactive configuration from environment variables (for CI)."""
|
||||
# Check for required environment variables
|
||||
api_key = os.environ.get("HINDSIGHT_EMBED_LLM_API_KEY") or os.environ.get("OPENAI_API_KEY")
|
||||
provider = os.environ.get("HINDSIGHT_EMBED_LLM_PROVIDER", "openai")
|
||||
|
||||
if provider not in PROVIDER_DEFAULTS:
|
||||
print(f"Error: Unknown provider '{provider}'. Supported: {', '.join(PROVIDER_DEFAULTS.keys())}", file=sys.stderr)
|
||||
return 1
|
||||
|
||||
_, default_model, env_key = PROVIDER_DEFAULTS[provider]
|
||||
|
||||
# Check for API key (required for non-ollama providers)
|
||||
if not api_key and provider != "ollama":
|
||||
print("Error: Cannot run interactive configuration without a terminal.", file=sys.stderr)
|
||||
print("", file=sys.stderr)
|
||||
print("For non-interactive (CI) mode, set environment variables:", file=sys.stderr)
|
||||
print(f" HINDSIGHT_EMBED_LLM_API_KEY=<your-api-key>", file=sys.stderr)
|
||||
print(f" HINDSIGHT_EMBED_LLM_PROVIDER={provider} # optional, default: openai", file=sys.stderr)
|
||||
print(f" HINDSIGHT_EMBED_LLM_MODEL=<model> # optional, default: {default_model}", file=sys.stderr)
|
||||
return 1
|
||||
|
||||
model = os.environ.get("HINDSIGHT_EMBED_LLM_MODEL", default_model)
|
||||
bank_id = os.environ.get("HINDSIGHT_EMBED_BANK_ID", "default")
|
||||
|
||||
print()
|
||||
print("\033[1m\033[36m Hindsight Embed - Non-interactive Configuration\033[0m")
|
||||
print()
|
||||
print(f" \033[2mProvider:\033[0m {provider}")
|
||||
print(f" \033[2mModel:\033[0m {model}")
|
||||
print(f" \033[2mBank ID:\033[0m {bank_id}")
|
||||
|
||||
# Save configuration
|
||||
CONFIG_DIR.mkdir(parents=True, exist_ok=True)
|
||||
|
||||
with open(CONFIG_FILE, "w") as f:
|
||||
f.write("# Hindsight Embed Configuration\n")
|
||||
f.write(f"# Generated by hindsight-embed configure (non-interactive)\n\n")
|
||||
f.write(f"HINDSIGHT_EMBED_LLM_PROVIDER={provider}\n")
|
||||
f.write(f"HINDSIGHT_EMBED_LLM_MODEL={model}\n")
|
||||
f.write(f"HINDSIGHT_EMBED_BANK_ID={bank_id}\n")
|
||||
if api_key:
|
||||
f.write(f"HINDSIGHT_EMBED_LLM_API_KEY={api_key}\n")
|
||||
|
||||
CONFIG_FILE.chmod(0o600)
|
||||
|
||||
print()
|
||||
print("\033[32m ✓ Configuration saved!\033[0m")
|
||||
print()
|
||||
|
||||
return 0
|
||||
|
||||
|
||||
def _prompt_choice(prompt: str, choices: list[tuple[str, str]], default: int = 1) -> str | None:
|
||||
"""Simple choice prompt that works with /dev/tty."""
|
||||
print(f"\033[1m{prompt}\033[0m")
|
||||
print()
|
||||
for i, (label, _) in enumerate(choices, 1):
|
||||
print(f" \033[36m{i})\033[0m {label}")
|
||||
print()
|
||||
try:
|
||||
response = input(f"Enter choice [{default}]: ").strip()
|
||||
if not response:
|
||||
return choices[default - 1][1]
|
||||
idx = int(response)
|
||||
if 1 <= idx <= len(choices):
|
||||
return choices[idx - 1][1]
|
||||
return choices[default - 1][1]
|
||||
except (ValueError, EOFError, KeyboardInterrupt):
|
||||
return None
|
||||
|
||||
|
||||
def _prompt_text(prompt: str, default: str = "") -> str | None:
|
||||
"""Simple text prompt."""
|
||||
try:
|
||||
suffix = f" [{default}]" if default else ""
|
||||
response = input(f"\033[1m{prompt}\033[0m{suffix}: ").strip()
|
||||
return response if response else default
|
||||
except (EOFError, KeyboardInterrupt):
|
||||
return None
|
||||
|
||||
|
||||
def _prompt_password(prompt: str) -> str | None:
|
||||
"""Simple password prompt."""
|
||||
import getpass
|
||||
try:
|
||||
return getpass.getpass(f"\033[1m{prompt}\033[0m: ")
|
||||
except (EOFError, KeyboardInterrupt):
|
||||
return None
|
||||
|
||||
|
||||
def _prompt_confirm(prompt: str, default: bool = True) -> bool | None:
|
||||
"""Simple yes/no prompt."""
|
||||
suffix = "[Y/n]" if default else "[y/N]"
|
||||
try:
|
||||
response = input(f"\033[1m{prompt}\033[0m {suffix}: ").strip().lower()
|
||||
if not response:
|
||||
return default
|
||||
return response in ('y', 'yes')
|
||||
except (EOFError, KeyboardInterrupt):
|
||||
return None
|
||||
|
||||
|
||||
def _do_configure_interactive():
|
||||
"""Internal interactive configuration."""
|
||||
print()
|
||||
print("\033[1m\033[36m ╭─────────────────────────────────────╮\033[0m")
|
||||
print("\033[1m\033[36m │ Hindsight Embed Configuration │\033[0m")
|
||||
@@ -117,82 +226,53 @@ def do_configure(args):
|
||||
|
||||
# Check existing config
|
||||
if CONFIG_FILE.exists():
|
||||
if not questionary.confirm(
|
||||
"Existing configuration found. Reconfigure?",
|
||||
default=False,
|
||||
style=custom_style,
|
||||
).ask():
|
||||
if not _prompt_confirm("Existing configuration found. Reconfigure?", default=False):
|
||||
print("\n\033[32m✓\033[0m Keeping existing configuration.")
|
||||
return 0
|
||||
print()
|
||||
|
||||
# Provider selection with descriptions
|
||||
# Provider selection
|
||||
providers = [
|
||||
questionary.Choice("OpenAI (recommended)", value=("openai", "o3-mini", "OpenAI")),
|
||||
questionary.Choice("Groq (fast & free tier)", value=("groq", "openai/gpt-oss-20b", "Groq")),
|
||||
questionary.Choice("Google Gemini", value=("google", "gemini-2.0-flash", "Google")),
|
||||
questionary.Choice("Ollama (local, no API key)", value=("ollama", "llama3.2", None)),
|
||||
("OpenAI (recommended)", "openai"),
|
||||
("Groq (fast & free tier)", "groq"),
|
||||
("Google Gemini", "google"),
|
||||
("Ollama (local, no API key)", "ollama"),
|
||||
]
|
||||
|
||||
result = questionary.select(
|
||||
"Select your LLM provider:",
|
||||
choices=providers,
|
||||
style=custom_style,
|
||||
).ask()
|
||||
|
||||
if result is None: # User cancelled
|
||||
provider = _prompt_choice("Select your LLM provider:", providers, default=1)
|
||||
if provider is None:
|
||||
print("\n\033[33m⚠\033[0m Configuration cancelled.")
|
||||
return 1
|
||||
|
||||
provider, default_model, key_name = result
|
||||
_, default_model, env_key = PROVIDER_DEFAULTS[provider]
|
||||
print()
|
||||
|
||||
# API key
|
||||
api_key = ""
|
||||
if key_name:
|
||||
env_keys = {
|
||||
"OpenAI": "OPENAI_API_KEY",
|
||||
"Groq": "GROQ_API_KEY",
|
||||
"Google": "GOOGLE_API_KEY",
|
||||
}
|
||||
env_key = env_keys.get(key_name, "")
|
||||
if env_key:
|
||||
existing = os.environ.get(env_key, "")
|
||||
|
||||
if existing:
|
||||
masked = existing[:8] + "..." + existing[-4:] if len(existing) > 12 else "***"
|
||||
if questionary.confirm(
|
||||
f"Found {key_name} key in ${env_key} ({masked}). Use it?",
|
||||
default=True,
|
||||
style=custom_style,
|
||||
).ask():
|
||||
if _prompt_confirm(f"Found API key in ${env_key} ({masked}). Use it?", default=True):
|
||||
api_key = existing
|
||||
print()
|
||||
|
||||
if not api_key:
|
||||
api_key = questionary.password(
|
||||
f"Enter your {key_name} API key:",
|
||||
style=custom_style,
|
||||
).ask()
|
||||
|
||||
api_key = _prompt_password("Enter your API key")
|
||||
if not api_key:
|
||||
print("\n\033[31m✗\033[0m API key is required.", file=sys.stderr)
|
||||
return 1
|
||||
print()
|
||||
|
||||
# Model selection
|
||||
model = questionary.text(
|
||||
"Model name:",
|
||||
default=default_model,
|
||||
style=custom_style,
|
||||
).ask()
|
||||
|
||||
model = _prompt_text("Model name", default=default_model)
|
||||
if model is None:
|
||||
return 1
|
||||
print()
|
||||
|
||||
# Bank ID
|
||||
bank_id = questionary.text(
|
||||
"Memory bank ID:",
|
||||
default="default",
|
||||
style=custom_style,
|
||||
).ask()
|
||||
|
||||
bank_id = _prompt_text("Memory bank ID", default="default")
|
||||
if bank_id is None:
|
||||
return 1
|
||||
|
||||
|
||||
@@ -9,7 +9,6 @@ description = "Hindsight embedded CLI - local memory operations without a server
|
||||
readme = "README.md"
|
||||
requires-python = ">=3.11"
|
||||
dependencies = [
|
||||
"questionary>=2.0.0",
|
||||
"httpx>=0.27.0",
|
||||
]
|
||||
|
||||
|
||||
@@ -1292,7 +1292,7 @@ wheels = [
|
||||
|
||||
[[package]]
|
||||
name = "hindsight-all"
|
||||
version = "0.1.12"
|
||||
version = "0.1.13"
|
||||
source = { editable = "hindsight" }
|
||||
dependencies = [
|
||||
{ name = "hindsight-api" },
|
||||
@@ -1316,7 +1316,7 @@ provides-extras = ["test"]
|
||||
|
||||
[[package]]
|
||||
name = "hindsight-api"
|
||||
version = "0.1.12"
|
||||
version = "0.1.13"
|
||||
source = { editable = "hindsight-api" }
|
||||
dependencies = [
|
||||
{ name = "alembic" },
|
||||
@@ -1422,7 +1422,7 @@ dev = [
|
||||
|
||||
[[package]]
|
||||
name = "hindsight-client"
|
||||
version = "0.1.12"
|
||||
version = "0.1.13"
|
||||
source = { editable = "hindsight-clients/python" }
|
||||
dependencies = [
|
||||
{ name = "aiohttp" },
|
||||
@@ -1456,7 +1456,7 @@ provides-extras = ["test"]
|
||||
|
||||
[[package]]
|
||||
name = "hindsight-dev"
|
||||
version = "0.1.12"
|
||||
version = "0.1.13"
|
||||
source = { editable = "hindsight-dev" }
|
||||
dependencies = [
|
||||
{ name = "hindsight-api" },
|
||||
@@ -1495,14 +1495,10 @@ version = "0.1.0"
|
||||
source = { editable = "hindsight-embed" }
|
||||
dependencies = [
|
||||
{ name = "httpx" },
|
||||
{ name = "questionary" },
|
||||
]
|
||||
|
||||
[package.metadata]
|
||||
requires-dist = [
|
||||
{ name = "httpx", specifier = ">=0.27.0" },
|
||||
{ name = "questionary", specifier = ">=2.0.0" },
|
||||
]
|
||||
requires-dist = [{ name = "httpx", specifier = ">=0.27.0" }]
|
||||
|
||||
[[package]]
|
||||
name = "httpcore"
|
||||
@@ -2915,18 +2911,6 @@ wheels = [
|
||||
{ url = "https://files.pythonhosted.org/packages/b8/db/14bafcb4af2139e046d03fd00dea7873e48eafe18b7d2797e73d6681f210/prometheus_client-0.23.1-py3-none-any.whl", hash = "sha256:dd1913e6e76b59cfe44e7a4b83e01afc9873c1bdfd2ed8739f1e76aeca115f99", size = 61145 },
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "prompt-toolkit"
|
||||
version = "3.0.52"
|
||||
source = { registry = "https://pypi.org/simple" }
|
||||
dependencies = [
|
||||
{ name = "wcwidth" },
|
||||
]
|
||||
sdist = { url = "https://files.pythonhosted.org/packages/a1/96/06e01a7b38dce6fe1db213e061a4602dd6032a8a97ef6c1a862537732421/prompt_toolkit-3.0.52.tar.gz", hash = "sha256:28cde192929c8e7321de85de1ddbe736f1375148b02f2e17edd840042b1be855", size = 434198 }
|
||||
wheels = [
|
||||
{ url = "https://files.pythonhosted.org/packages/84/03/0d3ce49e2505ae70cf43bc5bb3033955d2fc9f932163e84dc0779cc47f48/prompt_toolkit-3.0.52-py3-none-any.whl", hash = "sha256:9aac639a3bbd33284347de5ad8d68ecc044b91a762dc39b7c21095fcd6a19955", size = 391431 },
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "propcache"
|
||||
version = "0.4.1"
|
||||
@@ -3632,18 +3616,6 @@ wheels = [
|
||||
{ url = "https://files.pythonhosted.org/packages/f1/12/de94a39c2ef588c7e6455cfbe7343d3b2dc9d6b6b2f40c4c6565744c873d/pyyaml-6.0.3-cp314-cp314t-win_arm64.whl", hash = "sha256:ebc55a14a21cb14062aa4162f906cd962b28e2e9ea38f9b4391244cd8de4ae0b", size = 149341 },
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "questionary"
|
||||
version = "2.1.1"
|
||||
source = { registry = "https://pypi.org/simple" }
|
||||
dependencies = [
|
||||
{ name = "prompt-toolkit" },
|
||||
]
|
||||
sdist = { url = "https://files.pythonhosted.org/packages/f6/45/eafb0bba0f9988f6a2520f9ca2df2c82ddfa8d67c95d6625452e97b204a5/questionary-2.1.1.tar.gz", hash = "sha256:3d7e980292bb0107abaa79c68dd3eee3c561b83a0f89ae482860b181c8bd412d", size = 25845 }
|
||||
wheels = [
|
||||
{ url = "https://files.pythonhosted.org/packages/3c/26/1062c7ec1b053db9e499b4d2d5bc231743201b74051c973dadeac80a8f43/questionary-2.1.1-py3-none-any.whl", hash = "sha256:a51af13f345f1cdea62347589fbb6df3b290306ab8930713bfae4d475a7d4a59", size = 36753 },
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "redis"
|
||||
version = "7.1.0"
|
||||
@@ -4965,15 +4937,6 @@ wheels = [
|
||||
{ url = "https://files.pythonhosted.org/packages/6e/d4/ed38dd3b1767193de971e694aa544356e63353c33a85d948166b5ff58b9e/watchfiles-1.1.1-pp311-pypy311_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:3e6f39af2eab0118338902798b5aa6664f46ff66bc0280de76fca67a7f262a49", size = 457546 },
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "wcwidth"
|
||||
version = "0.2.14"
|
||||
source = { registry = "https://pypi.org/simple" }
|
||||
sdist = { url = "https://files.pythonhosted.org/packages/24/30/6b0809f4510673dc723187aeaf24c7f5459922d01e2f794277a3dfb90345/wcwidth-0.2.14.tar.gz", hash = "sha256:4d478375d31bc5395a3c55c40ccdf3354688364cd61c4f6adacaa9215d0b3605", size = 102293 }
|
||||
wheels = [
|
||||
{ url = "https://files.pythonhosted.org/packages/af/b5/123f13c975e9f27ab9c0770f514345bd406d0e8d3b7a0723af9d43f710af/wcwidth-0.2.14-py2.py3-none-any.whl", hash = "sha256:a7bb560c8aee30f9957e5f9895805edd20602f2d7f720186dfd906e82b4982e1", size = 37286 },
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "websockets"
|
||||
version = "15.0.1"
|
||||
|
||||
Reference in New Issue
Block a user