* fix(claude-code): fix plugin installation and release workflow - Fix plugin.json author field (string → object) to pass claude plugin validate - Add hindsight-integrations/.claude-plugin/marketplace.json so users can install via: claude plugin marketplace add vectorize-io/hindsight --sparse hindsight-integrations - Update README and install.sh with correct two-command install flow - Fix release-integration.yml: add explicit package.json check for typescript type and add plugin type for integrations with neither pyproject.toml nor package.json (prevents claude-code from incorrectly falling into the typescript build path) - Add CHANGELOG.md for the claude-code integration * remove install.sh — users install via claude plugin commands directly * test(claude-code): add 116 unit tests for plugin hooks and lib modules * feat(claude-code): user settings.json at CLAUDE_PLUGIN_DATA for stable config Plugin now checks CLAUDE_PLUGIN_DATA/settings.json after the versioned plugin default, giving users a path that persists across updates: ~/.claude/plugins/data/hindsight-memory-hindsight/settings.json Loading order: defaults → plugin settings.json → user settings.json → env vars * fix(claude-code): use ~/.hindsight/claude-code.json for user config Matches the ~/.openclaw/openclaw.json convention. Removes the confusing CLAUDE_PLUGIN_DATA path whose name depends on marketplace+plugin identifiers. * docs(claude-code): add ToS hint for claude-code LLM provider option * fix(claude-code): set author to Hindsight Team in plugin.json * ci: add test-claude-code-integration job to run plugin unit tests
144 lines
4.9 KiB
Python
144 lines
4.9 KiB
Python
"""Configuration management for Hindsight plugin.
|
|
|
|
Loads settings from settings.json (plugin defaults) merged with environment
|
|
variable overrides. Full config schema matching Openclaw's 30+ options.
|
|
"""
|
|
|
|
import json
|
|
import os
|
|
import sys
|
|
|
|
DEFAULTS = {
|
|
# Recall
|
|
"autoRecall": True,
|
|
"recallBudget": "mid",
|
|
"recallMaxTokens": 1024,
|
|
"recallTypes": ["world", "experience"],
|
|
"recallContextTurns": 1,
|
|
"recallMaxQueryChars": 800,
|
|
"recallRoles": ["user", "assistant"],
|
|
"recallPromptPreamble": (
|
|
"Relevant memories from past conversations (prioritize recent when "
|
|
"conflicting). Only use memories that are directly useful to continue "
|
|
"this conversation; ignore the rest:"
|
|
),
|
|
"recallTopK": None,
|
|
# Retain
|
|
"autoRetain": True,
|
|
"retainRoles": ["user", "assistant"],
|
|
"retainEveryNTurns": 10,
|
|
"retainOverlapTurns": 2,
|
|
"retainContext": "claude-code",
|
|
# Connection
|
|
"hindsightApiUrl": None,
|
|
"hindsightApiToken": None,
|
|
"apiPort": 9077,
|
|
"daemonIdleTimeout": 0,
|
|
"embedVersion": "latest",
|
|
"embedPackagePath": None,
|
|
# Bank
|
|
"bankId": None,
|
|
"bankIdPrefix": "",
|
|
"dynamicBankId": False,
|
|
"dynamicBankGranularity": ["agent", "project"],
|
|
"bankMission": "",
|
|
"retainMission": None,
|
|
"agentName": "claude-code",
|
|
# LLM (for daemon mode)
|
|
"llmProvider": None,
|
|
"llmModel": None,
|
|
"llmApiKeyEnv": None,
|
|
# Misc
|
|
"debug": False,
|
|
}
|
|
|
|
# Map env var names to config keys and their types
|
|
ENV_OVERRIDES = {
|
|
"HINDSIGHT_API_URL": ("hindsightApiUrl", str),
|
|
"HINDSIGHT_API_TOKEN": ("hindsightApiToken", str),
|
|
"HINDSIGHT_BANK_ID": ("bankId", str),
|
|
"HINDSIGHT_AGENT_NAME": ("agentName", str),
|
|
"HINDSIGHT_AUTO_RECALL": ("autoRecall", bool),
|
|
"HINDSIGHT_AUTO_RETAIN": ("autoRetain", bool),
|
|
"HINDSIGHT_RECALL_BUDGET": ("recallBudget", str),
|
|
"HINDSIGHT_RECALL_MAX_TOKENS": ("recallMaxTokens", int),
|
|
"HINDSIGHT_RECALL_MAX_QUERY_CHARS": ("recallMaxQueryChars", int),
|
|
"HINDSIGHT_RECALL_CONTEXT_TURNS": ("recallContextTurns", int),
|
|
"HINDSIGHT_API_PORT": ("apiPort", int),
|
|
"HINDSIGHT_DAEMON_IDLE_TIMEOUT": ("daemonIdleTimeout", int),
|
|
"HINDSIGHT_EMBED_VERSION": ("embedVersion", str),
|
|
"HINDSIGHT_EMBED_PACKAGE_PATH": ("embedPackagePath", str),
|
|
"HINDSIGHT_DYNAMIC_BANK_ID": ("dynamicBankId", bool),
|
|
"HINDSIGHT_BANK_MISSION": ("bankMission", str),
|
|
"HINDSIGHT_LLM_PROVIDER": ("llmProvider", str),
|
|
"HINDSIGHT_LLM_MODEL": ("llmModel", str),
|
|
"HINDSIGHT_DEBUG": ("debug", bool),
|
|
}
|
|
|
|
|
|
def _cast_env(value: str, typ):
|
|
"""Cast environment variable string to target type. Returns None on failure."""
|
|
try:
|
|
if typ is bool:
|
|
return value.lower() in ("true", "1", "yes")
|
|
if typ is int:
|
|
return int(value)
|
|
return value
|
|
except (ValueError, AttributeError):
|
|
return None
|
|
|
|
|
|
def _load_settings_file(path: str, config: dict) -> None:
|
|
"""Merge a settings.json file into config in-place. Silently skips if missing."""
|
|
if not os.path.exists(path):
|
|
return
|
|
try:
|
|
with open(path) as f:
|
|
file_config = json.load(f)
|
|
config.update({k: v for k, v in file_config.items() if v is not None})
|
|
except (json.JSONDecodeError, OSError) as e:
|
|
debug_log(config, f"Failed to load {path}: {e}")
|
|
|
|
|
|
USER_CONFIG_PATH = os.path.join(os.path.expanduser("~"), ".hindsight", "claude-code.json")
|
|
|
|
|
|
def load_config() -> dict:
|
|
"""Load plugin configuration from settings.json + env overrides.
|
|
|
|
Loading order (later entries win):
|
|
1. Built-in defaults
|
|
2. Plugin default settings.json (CLAUDE_PLUGIN_ROOT/settings.json)
|
|
3. User config (~/.hindsight/claude-code.json)
|
|
4. Environment variable overrides
|
|
|
|
~/.hindsight/claude-code.json is the recommended place to configure the
|
|
plugin — same convention as ~/.openclaw/openclaw.json. It is stable across
|
|
plugin updates and marketplace changes.
|
|
"""
|
|
config = dict(DEFAULTS)
|
|
|
|
# 1. Plugin default settings.json (ships with the plugin, version-specific path)
|
|
plugin_root = os.environ.get("CLAUDE_PLUGIN_ROOT", "")
|
|
if not plugin_root:
|
|
plugin_root = os.path.dirname(os.path.dirname(os.path.dirname(os.path.abspath(__file__))))
|
|
_load_settings_file(os.path.join(plugin_root, "settings.json"), config)
|
|
|
|
# 2. User config — stable, version-independent, matches openclaw convention
|
|
_load_settings_file(USER_CONFIG_PATH, config)
|
|
|
|
# Apply environment variable overrides
|
|
for env_name, (key, typ) in ENV_OVERRIDES.items():
|
|
val = os.environ.get(env_name)
|
|
if val is not None:
|
|
cast_val = _cast_env(val, typ)
|
|
if cast_val is not None:
|
|
config[key] = cast_val
|
|
|
|
return config
|
|
|
|
|
|
def debug_log(config: dict, *args):
|
|
"""Log to stderr if debug mode is enabled."""
|
|
if config.get("debug"):
|
|
print("[Hindsight]", *args, file=sys.stderr)
|