claude-memory-agent 2.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.env.example +107 -0
- package/README.md +200 -0
- package/agent_card.py +512 -0
- package/bin/cli.js +181 -0
- package/bin/postinstall.js +216 -0
- package/config.py +104 -0
- package/dashboard.html +2689 -0
- package/hooks/README.md +196 -0
- package/hooks/__pycache__/auto-detect-response.cpython-312.pyc +0 -0
- package/hooks/__pycache__/auto_capture.cpython-312.pyc +0 -0
- package/hooks/__pycache__/session_end.cpython-312.pyc +0 -0
- package/hooks/__pycache__/session_start.cpython-312.pyc +0 -0
- package/hooks/auto-detect-response.py +348 -0
- package/hooks/auto_capture.py +255 -0
- package/hooks/detect-correction.py +173 -0
- package/hooks/grounding-hook.py +348 -0
- package/hooks/log-tool-use.py +234 -0
- package/hooks/log-user-request.py +208 -0
- package/hooks/pre-tool-decision.py +218 -0
- package/hooks/problem-detector.py +343 -0
- package/hooks/session_end.py +192 -0
- package/hooks/session_start.py +227 -0
- package/install.py +887 -0
- package/main.py +2859 -0
- package/manager.py +997 -0
- package/package.json +55 -0
- package/requirements.txt +8 -0
- package/run_server.py +136 -0
- package/services/__init__.py +50 -0
- package/services/__pycache__/__init__.cpython-312.pyc +0 -0
- package/services/__pycache__/agent_registry.cpython-312.pyc +0 -0
- package/services/__pycache__/auth.cpython-312.pyc +0 -0
- package/services/__pycache__/auto_inject.cpython-312.pyc +0 -0
- package/services/__pycache__/claude_md_sync.cpython-312.pyc +0 -0
- package/services/__pycache__/cleanup.cpython-312.pyc +0 -0
- package/services/__pycache__/compaction_flush.cpython-312.pyc +0 -0
- package/services/__pycache__/confidence.cpython-312.pyc +0 -0
- package/services/__pycache__/daily_log.cpython-312.pyc +0 -0
- package/services/__pycache__/database.cpython-312.pyc +0 -0
- package/services/__pycache__/embeddings.cpython-312.pyc +0 -0
- package/services/__pycache__/insights.cpython-312.pyc +0 -0
- package/services/__pycache__/llm_analyzer.cpython-312.pyc +0 -0
- package/services/__pycache__/memory_md_sync.cpython-312.pyc +0 -0
- package/services/__pycache__/retry_queue.cpython-312.pyc +0 -0
- package/services/__pycache__/timeline.cpython-312.pyc +0 -0
- package/services/__pycache__/vector_index.cpython-312.pyc +0 -0
- package/services/__pycache__/websocket.cpython-312.pyc +0 -0
- package/services/agent_registry.py +753 -0
- package/services/auth.py +331 -0
- package/services/auto_inject.py +250 -0
- package/services/claude_md_sync.py +275 -0
- package/services/cleanup.py +667 -0
- package/services/compaction_flush.py +447 -0
- package/services/confidence.py +301 -0
- package/services/daily_log.py +333 -0
- package/services/database.py +2485 -0
- package/services/embeddings.py +358 -0
- package/services/insights.py +632 -0
- package/services/llm_analyzer.py +595 -0
- package/services/memory_md_sync.py +409 -0
- package/services/retry_queue.py +453 -0
- package/services/timeline.py +579 -0
- package/services/vector_index.py +398 -0
- package/services/websocket.py +257 -0
- package/skills/__init__.py +6 -0
- package/skills/__pycache__/__init__.cpython-312.pyc +0 -0
- package/skills/__pycache__/admin.cpython-312.pyc +0 -0
- package/skills/__pycache__/checkpoint.cpython-312.pyc +0 -0
- package/skills/__pycache__/claude_md.cpython-312.pyc +0 -0
- package/skills/__pycache__/cleanup.cpython-312.pyc +0 -0
- package/skills/__pycache__/grounding.cpython-312.pyc +0 -0
- package/skills/__pycache__/insights.cpython-312.pyc +0 -0
- package/skills/__pycache__/natural_language.cpython-312.pyc +0 -0
- package/skills/__pycache__/retrieve.cpython-312.pyc +0 -0
- package/skills/__pycache__/search.cpython-312.pyc +0 -0
- package/skills/__pycache__/state.cpython-312.pyc +0 -0
- package/skills/__pycache__/store.cpython-312.pyc +0 -0
- package/skills/__pycache__/summarize.cpython-312.pyc +0 -0
- package/skills/__pycache__/timeline.cpython-312.pyc +0 -0
- package/skills/__pycache__/verification.cpython-312.pyc +0 -0
- package/skills/admin.py +469 -0
- package/skills/checkpoint.py +198 -0
- package/skills/claude_md.py +363 -0
- package/skills/cleanup.py +241 -0
- package/skills/grounding.py +801 -0
- package/skills/insights.py +231 -0
- package/skills/natural_language.py +277 -0
- package/skills/retrieve.py +67 -0
- package/skills/search.py +213 -0
- package/skills/state.py +182 -0
- package/skills/store.py +179 -0
- package/skills/summarize.py +588 -0
- package/skills/timeline.py +387 -0
- package/skills/verification.py +391 -0
- package/start_daemon.py +155 -0
- package/test_automation.py +221 -0
- package/test_complete.py +338 -0
- package/test_full.py +322 -0
- package/update_system.py +817 -0
- package/verify_db.py +134 -0
package/install.py
ADDED
|
@@ -0,0 +1,887 @@
|
|
|
1
|
+
#!/usr/bin/env python3
|
|
2
|
+
"""
|
|
3
|
+
Claude Memory Agent - Installation Script
|
|
4
|
+
|
|
5
|
+
This script sets up the Claude Memory Agent for first-time use:
|
|
6
|
+
1. Creates .env file with auto-detected paths
|
|
7
|
+
2. Configures Claude Code MCP settings
|
|
8
|
+
3. Sets up hooks for auto-start and context injection
|
|
9
|
+
4. Creates platform-specific startup scripts
|
|
10
|
+
5. Installs Python dependencies
|
|
11
|
+
|
|
12
|
+
Usage:
|
|
13
|
+
python install.py # Interactive installation
|
|
14
|
+
python install.py --auto # Auto-install with defaults
|
|
15
|
+
python install.py --uninstall # Remove Claude Code integration
|
|
16
|
+
"""
|
|
17
|
+
import os
|
|
18
|
+
import sys
|
|
19
|
+
import json
|
|
20
|
+
import shutil
|
|
21
|
+
import argparse
|
|
22
|
+
import subprocess
|
|
23
|
+
from pathlib import Path
|
|
24
|
+
from typing import Optional, Dict, Any
|
|
25
|
+
|
|
26
|
+
# =============================================================================
|
|
27
|
+
# CONFIGURATION
|
|
28
|
+
# =============================================================================
|
|
29
|
+
|
|
30
|
+
# Agent directory (where this script lives)
|
|
31
|
+
AGENT_DIR = Path(__file__).parent.resolve()
|
|
32
|
+
|
|
33
|
+
# Default configuration
|
|
34
|
+
DEFAULT_CONFIG = {
|
|
35
|
+
"PORT": "8102",
|
|
36
|
+
"HOST": "0.0.0.0",
|
|
37
|
+
"MEMORY_AGENT_URL": "http://localhost:8102",
|
|
38
|
+
"OLLAMA_HOST": "http://localhost:11434",
|
|
39
|
+
"EMBEDDING_MODEL": "nomic-embed-text",
|
|
40
|
+
"LOG_LEVEL": "INFO",
|
|
41
|
+
"USE_VECTOR_INDEX": "true",
|
|
42
|
+
"DB_POOL_SIZE": "5",
|
|
43
|
+
"DB_TIMEOUT": "30.0",
|
|
44
|
+
"AUTH_ENABLED": "false",
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
# Claude Code settings paths
|
|
48
|
+
def get_claude_settings_dir() -> Path:
|
|
49
|
+
"""Get the Claude Code settings directory."""
|
|
50
|
+
if sys.platform == "win32":
|
|
51
|
+
return Path.home() / ".claude"
|
|
52
|
+
elif sys.platform == "darwin":
|
|
53
|
+
return Path.home() / ".claude"
|
|
54
|
+
else: # Linux
|
|
55
|
+
return Path.home() / ".claude"
|
|
56
|
+
|
|
57
|
+
def get_claude_settings_file() -> Path:
|
|
58
|
+
"""Get the Claude Code settings.json file path."""
|
|
59
|
+
return get_claude_settings_dir() / "settings.json"
|
|
60
|
+
|
|
61
|
+
def get_hooks_dir() -> Path:
|
|
62
|
+
"""Get the Claude Code hooks directory."""
|
|
63
|
+
return get_claude_settings_dir() / "hooks"
|
|
64
|
+
|
|
65
|
+
|
|
66
|
+
# =============================================================================
|
|
67
|
+
# UTILITY FUNCTIONS
|
|
68
|
+
# =============================================================================
|
|
69
|
+
|
|
70
|
+
def print_header(text: str):
|
|
71
|
+
"""Print a formatted header."""
|
|
72
|
+
print(f"\n{'='*60}")
|
|
73
|
+
print(f" {text}")
|
|
74
|
+
print(f"{'='*60}\n")
|
|
75
|
+
|
|
76
|
+
|
|
77
|
+
def print_step(step: int, total: int, text: str):
|
|
78
|
+
"""Print a step indicator."""
|
|
79
|
+
print(f"[{step}/{total}] {text}")
|
|
80
|
+
|
|
81
|
+
|
|
82
|
+
def print_success(text: str):
|
|
83
|
+
"""Print a success message."""
|
|
84
|
+
print(f" [OK] {text}")
|
|
85
|
+
|
|
86
|
+
|
|
87
|
+
def print_warning(text: str):
|
|
88
|
+
"""Print a warning message."""
|
|
89
|
+
print(f" [!!] {text}")
|
|
90
|
+
|
|
91
|
+
|
|
92
|
+
def print_error(text: str):
|
|
93
|
+
"""Print an error message."""
|
|
94
|
+
print(f" [ERROR] {text}")
|
|
95
|
+
|
|
96
|
+
|
|
97
|
+
def prompt_yes_no(question: str, default: bool = True) -> bool:
|
|
98
|
+
"""Prompt for yes/no answer."""
|
|
99
|
+
suffix = " [Y/n]: " if default else " [y/N]: "
|
|
100
|
+
while True:
|
|
101
|
+
answer = input(question + suffix).strip().lower()
|
|
102
|
+
if not answer:
|
|
103
|
+
return default
|
|
104
|
+
if answer in ("y", "yes"):
|
|
105
|
+
return True
|
|
106
|
+
if answer in ("n", "no"):
|
|
107
|
+
return False
|
|
108
|
+
print("Please answer 'y' or 'n'")
|
|
109
|
+
|
|
110
|
+
|
|
111
|
+
def prompt_value(question: str, default: str) -> str:
|
|
112
|
+
"""Prompt for a value with a default."""
|
|
113
|
+
answer = input(f"{question} [{default}]: ").strip()
|
|
114
|
+
return answer if answer else default
|
|
115
|
+
|
|
116
|
+
|
|
117
|
+
def check_python_version() -> bool:
|
|
118
|
+
"""Check if Python version is compatible."""
|
|
119
|
+
major, minor = sys.version_info[:2]
|
|
120
|
+
if major < 3 or (major == 3 and minor < 9):
|
|
121
|
+
print_error(f"Python 3.9+ required, found {major}.{minor}")
|
|
122
|
+
return False
|
|
123
|
+
print_success(f"Python {major}.{minor} detected")
|
|
124
|
+
return True
|
|
125
|
+
|
|
126
|
+
|
|
127
|
+
def check_nodejs() -> tuple[bool, Optional[str]]:
|
|
128
|
+
"""Check if Node.js is installed and get version."""
|
|
129
|
+
try:
|
|
130
|
+
result = subprocess.run(
|
|
131
|
+
["node", "--version"],
|
|
132
|
+
capture_output=True,
|
|
133
|
+
text=True,
|
|
134
|
+
timeout=5
|
|
135
|
+
)
|
|
136
|
+
if result.returncode == 0:
|
|
137
|
+
version = result.stdout.strip()
|
|
138
|
+
print_success(f"Node.js {version} detected")
|
|
139
|
+
return True, version
|
|
140
|
+
except FileNotFoundError:
|
|
141
|
+
pass
|
|
142
|
+
except subprocess.TimeoutExpired:
|
|
143
|
+
pass
|
|
144
|
+
except Exception:
|
|
145
|
+
pass
|
|
146
|
+
|
|
147
|
+
print_warning("Node.js not found")
|
|
148
|
+
return False, None
|
|
149
|
+
|
|
150
|
+
|
|
151
|
+
def check_npm() -> bool:
|
|
152
|
+
"""Check if npm is available."""
|
|
153
|
+
# On Windows, npm is a .cmd file
|
|
154
|
+
commands_to_try = ["npm", "npm.cmd"] if sys.platform == "win32" else ["npm"]
|
|
155
|
+
|
|
156
|
+
for cmd in commands_to_try:
|
|
157
|
+
try:
|
|
158
|
+
result = subprocess.run(
|
|
159
|
+
[cmd, "--version"],
|
|
160
|
+
capture_output=True,
|
|
161
|
+
text=True,
|
|
162
|
+
timeout=5,
|
|
163
|
+
shell=(sys.platform == "win32") # Use shell on Windows
|
|
164
|
+
)
|
|
165
|
+
if result.returncode == 0:
|
|
166
|
+
print_success(f"npm {result.stdout.strip()} detected")
|
|
167
|
+
return True
|
|
168
|
+
except Exception:
|
|
169
|
+
continue
|
|
170
|
+
return False
|
|
171
|
+
|
|
172
|
+
|
|
173
|
+
def check_claude_code() -> tuple[bool, Optional[str]]:
|
|
174
|
+
"""Check if Claude Code CLI is installed."""
|
|
175
|
+
# Try different possible command names
|
|
176
|
+
# On Windows, these may be .cmd files
|
|
177
|
+
if sys.platform == "win32":
|
|
178
|
+
commands_to_try = ["claude", "claude.cmd", "claude-code", "claude-code.cmd"]
|
|
179
|
+
else:
|
|
180
|
+
commands_to_try = ["claude", "claude-code"]
|
|
181
|
+
|
|
182
|
+
for cmd in commands_to_try:
|
|
183
|
+
try:
|
|
184
|
+
result = subprocess.run(
|
|
185
|
+
[cmd, "--version"],
|
|
186
|
+
capture_output=True,
|
|
187
|
+
text=True,
|
|
188
|
+
timeout=10,
|
|
189
|
+
shell=(sys.platform == "win32") # Use shell on Windows
|
|
190
|
+
)
|
|
191
|
+
if result.returncode == 0:
|
|
192
|
+
version = result.stdout.strip().split('\n')[0]
|
|
193
|
+
print_success(f"Claude Code detected: {version}")
|
|
194
|
+
return True, version
|
|
195
|
+
except FileNotFoundError:
|
|
196
|
+
continue
|
|
197
|
+
except subprocess.TimeoutExpired:
|
|
198
|
+
continue
|
|
199
|
+
except Exception:
|
|
200
|
+
continue
|
|
201
|
+
|
|
202
|
+
# Check if .claude directory exists (indicates Claude Code was used)
|
|
203
|
+
claude_dir = get_claude_settings_dir()
|
|
204
|
+
if claude_dir.exists():
|
|
205
|
+
print_success("Claude Code settings directory found (~/.claude)")
|
|
206
|
+
return True, "directory exists"
|
|
207
|
+
|
|
208
|
+
print_warning("Claude Code not detected")
|
|
209
|
+
return False, None
|
|
210
|
+
|
|
211
|
+
|
|
212
|
+
def install_claude_code() -> bool:
|
|
213
|
+
"""Attempt to install Claude Code via npm."""
|
|
214
|
+
print("\nClaude Code is not installed. Installing via npm...")
|
|
215
|
+
|
|
216
|
+
# On Windows, use npm.cmd via shell
|
|
217
|
+
npm_cmd = "npm" if sys.platform != "win32" else "npm.cmd"
|
|
218
|
+
|
|
219
|
+
try:
|
|
220
|
+
result = subprocess.run(
|
|
221
|
+
[npm_cmd, "install", "-g", "@anthropic-ai/claude-code"],
|
|
222
|
+
capture_output=True,
|
|
223
|
+
text=True,
|
|
224
|
+
timeout=120,
|
|
225
|
+
shell=(sys.platform == "win32")
|
|
226
|
+
)
|
|
227
|
+
if result.returncode == 0:
|
|
228
|
+
print_success("Claude Code installed successfully!")
|
|
229
|
+
print(" Run 'claude' to start Claude Code")
|
|
230
|
+
return True
|
|
231
|
+
else:
|
|
232
|
+
print_error(f"npm install failed: {result.stderr}")
|
|
233
|
+
return False
|
|
234
|
+
except subprocess.TimeoutExpired:
|
|
235
|
+
print_error("Installation timed out")
|
|
236
|
+
return False
|
|
237
|
+
except Exception as e:
|
|
238
|
+
print_error(f"Installation failed: {e}")
|
|
239
|
+
return False
|
|
240
|
+
|
|
241
|
+
|
|
242
|
+
def print_installation_instructions():
|
|
243
|
+
"""Print manual installation instructions for missing dependencies."""
|
|
244
|
+
print("\n" + "="*60)
|
|
245
|
+
print(" INSTALLATION REQUIRED")
|
|
246
|
+
print("="*60)
|
|
247
|
+
print("""
|
|
248
|
+
To use Claude Memory Agent, you need:
|
|
249
|
+
|
|
250
|
+
1. NODE.JS (required for Claude Code)
|
|
251
|
+
Download from: https://nodejs.org/
|
|
252
|
+
- Windows: Download and run the installer
|
|
253
|
+
- Mac: brew install node
|
|
254
|
+
- Linux: sudo apt install nodejs npm
|
|
255
|
+
|
|
256
|
+
2. CLAUDE CODE (the AI coding assistant)
|
|
257
|
+
After installing Node.js, run:
|
|
258
|
+
npm install -g @anthropic-ai/claude-code
|
|
259
|
+
|
|
260
|
+
3. OLLAMA (for embeddings - optional but recommended)
|
|
261
|
+
Download from: https://ollama.ai/
|
|
262
|
+
Then run: ollama pull nomic-embed-text
|
|
263
|
+
|
|
264
|
+
After installing the prerequisites, run this installer again:
|
|
265
|
+
python install.py
|
|
266
|
+
""")
|
|
267
|
+
|
|
268
|
+
|
|
269
|
+
def check_ollama() -> bool:
|
|
270
|
+
"""Check if Ollama is installed and running."""
|
|
271
|
+
try:
|
|
272
|
+
import requests
|
|
273
|
+
r = requests.get("http://localhost:11434/api/tags", timeout=2)
|
|
274
|
+
if r.status_code == 200:
|
|
275
|
+
print_success("Ollama is running")
|
|
276
|
+
return True
|
|
277
|
+
except Exception:
|
|
278
|
+
pass
|
|
279
|
+
print_warning("Ollama not detected - embeddings will be disabled until Ollama is running")
|
|
280
|
+
return False
|
|
281
|
+
|
|
282
|
+
|
|
283
|
+
def check_ollama_model(model: str = "nomic-embed-text") -> bool:
|
|
284
|
+
"""Check if the embedding model is available in Ollama."""
|
|
285
|
+
try:
|
|
286
|
+
import requests
|
|
287
|
+
r = requests.get("http://localhost:11434/api/tags", timeout=2)
|
|
288
|
+
if r.status_code == 200:
|
|
289
|
+
models = r.json().get("models", [])
|
|
290
|
+
model_names = [m.get("name", "").split(":")[0] for m in models]
|
|
291
|
+
if model in model_names:
|
|
292
|
+
print_success(f"Embedding model '{model}' is available")
|
|
293
|
+
return True
|
|
294
|
+
print_warning(f"Model '{model}' not found. Run: ollama pull {model}")
|
|
295
|
+
except Exception:
|
|
296
|
+
pass
|
|
297
|
+
return False
|
|
298
|
+
|
|
299
|
+
|
|
300
|
+
# =============================================================================
|
|
301
|
+
# INSTALLATION STEPS
|
|
302
|
+
# =============================================================================
|
|
303
|
+
|
|
304
|
+
def install_dependencies() -> bool:
|
|
305
|
+
"""Install Python dependencies from requirements.txt."""
|
|
306
|
+
requirements_file = AGENT_DIR / "requirements.txt"
|
|
307
|
+
if not requirements_file.exists():
|
|
308
|
+
print_warning("requirements.txt not found, skipping dependency installation")
|
|
309
|
+
return True
|
|
310
|
+
|
|
311
|
+
print("Installing Python dependencies...")
|
|
312
|
+
try:
|
|
313
|
+
subprocess.run(
|
|
314
|
+
[sys.executable, "-m", "pip", "install", "-r", str(requirements_file), "-q"],
|
|
315
|
+
check=True,
|
|
316
|
+
capture_output=True
|
|
317
|
+
)
|
|
318
|
+
print_success("Dependencies installed")
|
|
319
|
+
return True
|
|
320
|
+
except subprocess.CalledProcessError as e:
|
|
321
|
+
print_error(f"Failed to install dependencies: {e.stderr.decode()}")
|
|
322
|
+
return False
|
|
323
|
+
|
|
324
|
+
|
|
325
|
+
def create_env_file(config: Dict[str, str], force: bool = False) -> bool:
|
|
326
|
+
"""Create the .env configuration file."""
|
|
327
|
+
env_file = AGENT_DIR / ".env"
|
|
328
|
+
|
|
329
|
+
if env_file.exists() and not force:
|
|
330
|
+
if not prompt_yes_no(".env file already exists. Overwrite?", default=False):
|
|
331
|
+
print_success("Keeping existing .env file")
|
|
332
|
+
return True
|
|
333
|
+
|
|
334
|
+
# Build .env content
|
|
335
|
+
lines = [
|
|
336
|
+
"# Claude Memory Agent Configuration",
|
|
337
|
+
"# Generated by install.py",
|
|
338
|
+
f"# Installation date: {__import__('datetime').datetime.now().isoformat()}",
|
|
339
|
+
"",
|
|
340
|
+
"# Server Configuration",
|
|
341
|
+
f"HOST={config['HOST']}",
|
|
342
|
+
f"PORT={config['PORT']}",
|
|
343
|
+
f"MEMORY_AGENT_URL={config['MEMORY_AGENT_URL']}",
|
|
344
|
+
"",
|
|
345
|
+
"# Ollama Configuration",
|
|
346
|
+
f"OLLAMA_HOST={config['OLLAMA_HOST']}",
|
|
347
|
+
f"EMBEDDING_MODEL={config['EMBEDDING_MODEL']}",
|
|
348
|
+
"",
|
|
349
|
+
"# Database Configuration",
|
|
350
|
+
f"DATABASE_PATH={AGENT_DIR / 'memories.db'}",
|
|
351
|
+
f"USE_VECTOR_INDEX={config['USE_VECTOR_INDEX']}",
|
|
352
|
+
f"DB_POOL_SIZE={config['DB_POOL_SIZE']}",
|
|
353
|
+
f"DB_TIMEOUT={config['DB_TIMEOUT']}",
|
|
354
|
+
"",
|
|
355
|
+
"# Logging",
|
|
356
|
+
f"LOG_LEVEL={config['LOG_LEVEL']}",
|
|
357
|
+
"",
|
|
358
|
+
"# Authentication (disabled by default for local use)",
|
|
359
|
+
f"AUTH_ENABLED={config['AUTH_ENABLED']}",
|
|
360
|
+
]
|
|
361
|
+
|
|
362
|
+
try:
|
|
363
|
+
env_file.write_text("\n".join(lines) + "\n")
|
|
364
|
+
print_success(f"Created .env file at {env_file}")
|
|
365
|
+
return True
|
|
366
|
+
except Exception as e:
|
|
367
|
+
print_error(f"Failed to create .env file: {e}")
|
|
368
|
+
return False
|
|
369
|
+
|
|
370
|
+
|
|
371
|
+
def create_startup_script() -> bool:
|
|
372
|
+
"""Create platform-specific startup script."""
|
|
373
|
+
if sys.platform == "win32":
|
|
374
|
+
return create_windows_startup_script()
|
|
375
|
+
else:
|
|
376
|
+
return create_unix_startup_script()
|
|
377
|
+
|
|
378
|
+
|
|
379
|
+
def create_windows_startup_script() -> bool:
|
|
380
|
+
"""Create Windows VBS startup script with auto-detected paths."""
|
|
381
|
+
vbs_file = AGENT_DIR / "start-memory-agent.vbs"
|
|
382
|
+
|
|
383
|
+
# Use forward slashes for VBS string, then replace
|
|
384
|
+
agent_dir_str = str(AGENT_DIR).replace("\\", "\\\\")
|
|
385
|
+
|
|
386
|
+
content = f'''' Start Memory Agent silently in background
|
|
387
|
+
' Auto-generated by install.py
|
|
388
|
+
|
|
389
|
+
Set WshShell = CreateObject("WScript.Shell")
|
|
390
|
+
Set fso = CreateObject("Scripting.FileSystemObject")
|
|
391
|
+
|
|
392
|
+
' Get the directory where this script is located
|
|
393
|
+
scriptPath = WScript.ScriptFullName
|
|
394
|
+
agentDir = fso.GetParentFolderName(scriptPath)
|
|
395
|
+
|
|
396
|
+
' Alternatively, use the configured path (uncomment if needed):
|
|
397
|
+
' agentDir = "{agent_dir_str}"
|
|
398
|
+
|
|
399
|
+
pythonCmd = "python """ & agentDir & "\\main.py"""
|
|
400
|
+
|
|
401
|
+
WshShell.CurrentDirectory = agentDir
|
|
402
|
+
WshShell.Run "cmd /c " & pythonCmd, 0, False
|
|
403
|
+
'''
|
|
404
|
+
|
|
405
|
+
try:
|
|
406
|
+
vbs_file.write_text(content)
|
|
407
|
+
print_success(f"Created Windows startup script: {vbs_file}")
|
|
408
|
+
return True
|
|
409
|
+
except Exception as e:
|
|
410
|
+
print_error(f"Failed to create startup script: {e}")
|
|
411
|
+
return False
|
|
412
|
+
|
|
413
|
+
|
|
414
|
+
def create_unix_startup_script() -> bool:
|
|
415
|
+
"""Create Unix/Mac startup script."""
|
|
416
|
+
sh_file = AGENT_DIR / "start-memory-agent.sh"
|
|
417
|
+
|
|
418
|
+
content = f'''#!/bin/bash
|
|
419
|
+
# Start Memory Agent in background
|
|
420
|
+
# Auto-generated by install.py
|
|
421
|
+
|
|
422
|
+
# Get the directory where this script is located
|
|
423
|
+
SCRIPT_DIR="$(cd "$(dirname "${{BASH_SOURCE[0]}}")" && pwd)"
|
|
424
|
+
|
|
425
|
+
cd "$SCRIPT_DIR"
|
|
426
|
+
nohup python main.py > memory-agent.log 2>&1 &
|
|
427
|
+
echo "Memory Agent started (PID: $!)"
|
|
428
|
+
'''
|
|
429
|
+
|
|
430
|
+
try:
|
|
431
|
+
sh_file.write_text(content)
|
|
432
|
+
sh_file.chmod(0o755)
|
|
433
|
+
print_success(f"Created Unix startup script: {sh_file}")
|
|
434
|
+
return True
|
|
435
|
+
except Exception as e:
|
|
436
|
+
print_error(f"Failed to create startup script: {e}")
|
|
437
|
+
return False
|
|
438
|
+
|
|
439
|
+
|
|
440
|
+
def configure_claude_mcp(config: Dict[str, str]) -> bool:
|
|
441
|
+
"""Configure Claude Code MCP settings."""
|
|
442
|
+
settings_file = get_claude_settings_file()
|
|
443
|
+
settings_dir = get_claude_settings_dir()
|
|
444
|
+
|
|
445
|
+
# Ensure settings directory exists
|
|
446
|
+
settings_dir.mkdir(parents=True, exist_ok=True)
|
|
447
|
+
|
|
448
|
+
# Load existing settings or create new
|
|
449
|
+
if settings_file.exists():
|
|
450
|
+
try:
|
|
451
|
+
settings = json.loads(settings_file.read_text())
|
|
452
|
+
except json.JSONDecodeError:
|
|
453
|
+
print_warning("Existing settings.json is invalid, creating backup")
|
|
454
|
+
shutil.copy(settings_file, settings_file.with_suffix(".json.bak"))
|
|
455
|
+
settings = {}
|
|
456
|
+
else:
|
|
457
|
+
settings = {}
|
|
458
|
+
|
|
459
|
+
# Ensure mcpServers section exists
|
|
460
|
+
if "mcpServers" not in settings:
|
|
461
|
+
settings["mcpServers"] = {}
|
|
462
|
+
|
|
463
|
+
# Add/update claude-memory server configuration
|
|
464
|
+
settings["mcpServers"]["claude-memory"] = {
|
|
465
|
+
"command": sys.executable,
|
|
466
|
+
"args": [str(AGENT_DIR / "main.py")],
|
|
467
|
+
"env": {
|
|
468
|
+
"MEMORY_AGENT_URL": config["MEMORY_AGENT_URL"],
|
|
469
|
+
"PORT": config["PORT"],
|
|
470
|
+
}
|
|
471
|
+
}
|
|
472
|
+
|
|
473
|
+
try:
|
|
474
|
+
settings_file.write_text(json.dumps(settings, indent=2))
|
|
475
|
+
print_success(f"Configured Claude Code MCP settings: {settings_file}")
|
|
476
|
+
return True
|
|
477
|
+
except Exception as e:
|
|
478
|
+
print_error(f"Failed to configure MCP settings: {e}")
|
|
479
|
+
return False
|
|
480
|
+
|
|
481
|
+
|
|
482
|
+
def setup_hooks(config: Dict[str, str]) -> bool:
|
|
483
|
+
"""Set up Claude Code hooks for auto-start and context injection."""
|
|
484
|
+
hooks_dir = get_hooks_dir()
|
|
485
|
+
hooks_dir.mkdir(parents=True, exist_ok=True)
|
|
486
|
+
|
|
487
|
+
source_hooks_dir = AGENT_DIR / "hooks"
|
|
488
|
+
if not source_hooks_dir.exists():
|
|
489
|
+
print_warning("Hooks directory not found in agent, skipping hook setup")
|
|
490
|
+
return True
|
|
491
|
+
|
|
492
|
+
# Hooks to install
|
|
493
|
+
hooks_to_install = [
|
|
494
|
+
"session_start.py",
|
|
495
|
+
"session_end.py",
|
|
496
|
+
"grounding-hook.py",
|
|
497
|
+
]
|
|
498
|
+
|
|
499
|
+
installed = 0
|
|
500
|
+
for hook_name in hooks_to_install:
|
|
501
|
+
source = source_hooks_dir / hook_name
|
|
502
|
+
if not source.exists():
|
|
503
|
+
continue
|
|
504
|
+
|
|
505
|
+
dest = hooks_dir / hook_name
|
|
506
|
+
|
|
507
|
+
# Read source and update MEMORY_AGENT_URL default
|
|
508
|
+
content = source.read_text()
|
|
509
|
+
|
|
510
|
+
# Copy to hooks directory
|
|
511
|
+
try:
|
|
512
|
+
dest.write_text(content)
|
|
513
|
+
installed += 1
|
|
514
|
+
except Exception as e:
|
|
515
|
+
print_warning(f"Failed to install hook {hook_name}: {e}")
|
|
516
|
+
|
|
517
|
+
if installed > 0:
|
|
518
|
+
print_success(f"Installed {installed} hooks to {hooks_dir}")
|
|
519
|
+
|
|
520
|
+
return True
|
|
521
|
+
|
|
522
|
+
|
|
523
|
+
def configure_hooks_json() -> bool:
|
|
524
|
+
"""Configure hooks.json to enable the hooks."""
|
|
525
|
+
hooks_file = get_claude_settings_dir() / "hooks.json"
|
|
526
|
+
|
|
527
|
+
# Default hooks configuration
|
|
528
|
+
hooks_config = {
|
|
529
|
+
"hooks": {
|
|
530
|
+
"UserPromptSubmit": [
|
|
531
|
+
{
|
|
532
|
+
"command": f"{sys.executable} {get_hooks_dir() / 'session_start.py'}",
|
|
533
|
+
"description": "Initialize memory session",
|
|
534
|
+
"timeout": 5000
|
|
535
|
+
},
|
|
536
|
+
{
|
|
537
|
+
"command": f"{sys.executable} {get_hooks_dir() / 'grounding-hook.py'}",
|
|
538
|
+
"description": "Inject grounding context",
|
|
539
|
+
"timeout": 3000
|
|
540
|
+
}
|
|
541
|
+
],
|
|
542
|
+
"SessionEnd": [
|
|
543
|
+
{
|
|
544
|
+
"command": f"{sys.executable} {get_hooks_dir() / 'session_end.py'}",
|
|
545
|
+
"description": "Save session summary",
|
|
546
|
+
"timeout": 10000
|
|
547
|
+
}
|
|
548
|
+
]
|
|
549
|
+
}
|
|
550
|
+
}
|
|
551
|
+
|
|
552
|
+
# Merge with existing if present
|
|
553
|
+
if hooks_file.exists():
|
|
554
|
+
try:
|
|
555
|
+
existing = json.loads(hooks_file.read_text())
|
|
556
|
+
# Don't overwrite if user has customized
|
|
557
|
+
if prompt_yes_no("hooks.json exists. Update with memory agent hooks?", default=True):
|
|
558
|
+
if "hooks" not in existing:
|
|
559
|
+
existing["hooks"] = {}
|
|
560
|
+
existing["hooks"].update(hooks_config["hooks"])
|
|
561
|
+
hooks_config = existing
|
|
562
|
+
else:
|
|
563
|
+
print_success("Keeping existing hooks.json")
|
|
564
|
+
return True
|
|
565
|
+
except json.JSONDecodeError:
|
|
566
|
+
pass
|
|
567
|
+
|
|
568
|
+
try:
|
|
569
|
+
hooks_file.write_text(json.dumps(hooks_config, indent=2))
|
|
570
|
+
print_success(f"Configured hooks: {hooks_file}")
|
|
571
|
+
return True
|
|
572
|
+
except Exception as e:
|
|
573
|
+
print_error(f"Failed to configure hooks: {e}")
|
|
574
|
+
return False
|
|
575
|
+
|
|
576
|
+
|
|
577
|
+
def fix_agent_card_port() -> bool:
|
|
578
|
+
"""Fix the port in agent_card.py from 8100 to 8102."""
|
|
579
|
+
agent_card_file = AGENT_DIR / "agent_card.py"
|
|
580
|
+
|
|
581
|
+
if not agent_card_file.exists():
|
|
582
|
+
return True
|
|
583
|
+
|
|
584
|
+
content = agent_card_file.read_text()
|
|
585
|
+
if '"url": "http://localhost:8100"' in content:
|
|
586
|
+
content = content.replace(
|
|
587
|
+
'"url": "http://localhost:8100"',
|
|
588
|
+
'"url": "http://localhost:8102"'
|
|
589
|
+
)
|
|
590
|
+
agent_card_file.write_text(content)
|
|
591
|
+
print_success("Fixed agent_card.py port (8100 -> 8102)")
|
|
592
|
+
|
|
593
|
+
return True
|
|
594
|
+
|
|
595
|
+
|
|
596
|
+
def fix_dashboard_urls() -> bool:
|
|
597
|
+
"""Make dashboard.html use dynamic URLs."""
|
|
598
|
+
dashboard_file = AGENT_DIR / "dashboard.html"
|
|
599
|
+
|
|
600
|
+
if not dashboard_file.exists():
|
|
601
|
+
return True
|
|
602
|
+
|
|
603
|
+
content = dashboard_file.read_text()
|
|
604
|
+
|
|
605
|
+
# Replace hardcoded URLs with dynamic detection
|
|
606
|
+
old_js = "const API_URL = 'http://localhost:8102';\n const WS_URL = 'ws://localhost:8102/ws';"
|
|
607
|
+
new_js = """// Auto-detect server URL from current location
|
|
608
|
+
const API_URL = window.location.origin || 'http://localhost:8102';
|
|
609
|
+
const WS_URL = (window.location.protocol === 'https:' ? 'wss:' : 'ws:') + '//' + (window.location.host || 'localhost:8102') + '/ws';"""
|
|
610
|
+
|
|
611
|
+
if old_js in content:
|
|
612
|
+
content = content.replace(old_js, new_js)
|
|
613
|
+
dashboard_file.write_text(content)
|
|
614
|
+
print_success("Updated dashboard.html to use dynamic URLs")
|
|
615
|
+
|
|
616
|
+
return True
|
|
617
|
+
|
|
618
|
+
|
|
619
|
+
def fix_start_daemon_url(config: Dict[str, str]) -> bool:
|
|
620
|
+
"""Fix hardcoded URL in start_daemon.py."""
|
|
621
|
+
daemon_file = AGENT_DIR / "start_daemon.py"
|
|
622
|
+
|
|
623
|
+
if not daemon_file.exists():
|
|
624
|
+
return True
|
|
625
|
+
|
|
626
|
+
content = daemon_file.read_text()
|
|
627
|
+
|
|
628
|
+
# Replace hardcoded health check URL with environment variable
|
|
629
|
+
old_line = 'r = requests.get("http://localhost:8102/health", timeout=2)'
|
|
630
|
+
new_line = f'r = requests.get(os.getenv("MEMORY_AGENT_URL", "http://localhost:8102") + "/health", timeout=2)'
|
|
631
|
+
|
|
632
|
+
if old_line in content:
|
|
633
|
+
content = content.replace(old_line, new_line)
|
|
634
|
+
daemon_file.write_text(content)
|
|
635
|
+
print_success("Updated start_daemon.py to use environment variable")
|
|
636
|
+
|
|
637
|
+
return True
|
|
638
|
+
|
|
639
|
+
|
|
640
|
+
def verify_installation() -> bool:
|
|
641
|
+
"""Verify the installation is working."""
|
|
642
|
+
print("\nVerifying installation...")
|
|
643
|
+
|
|
644
|
+
# Check .env exists
|
|
645
|
+
if not (AGENT_DIR / ".env").exists():
|
|
646
|
+
print_warning(".env file not created")
|
|
647
|
+
return False
|
|
648
|
+
|
|
649
|
+
# Try to import main module
|
|
650
|
+
try:
|
|
651
|
+
sys.path.insert(0, str(AGENT_DIR))
|
|
652
|
+
from dotenv import load_dotenv
|
|
653
|
+
load_dotenv(AGENT_DIR / ".env")
|
|
654
|
+
print_success("Configuration loaded successfully")
|
|
655
|
+
except Exception as e:
|
|
656
|
+
print_warning(f"Could not load configuration: {e}")
|
|
657
|
+
|
|
658
|
+
return True
|
|
659
|
+
|
|
660
|
+
|
|
661
|
+
def print_post_install_instructions(config: Dict[str, str]):
|
|
662
|
+
"""Print instructions for after installation."""
|
|
663
|
+
print_header("Installation Complete!")
|
|
664
|
+
|
|
665
|
+
print("Next steps:")
|
|
666
|
+
print("")
|
|
667
|
+
print("1. Make sure Ollama is running with the embedding model:")
|
|
668
|
+
print(f" ollama pull {config['EMBEDDING_MODEL']}")
|
|
669
|
+
print(f" ollama serve")
|
|
670
|
+
print("")
|
|
671
|
+
print("2. Start the Memory Agent:")
|
|
672
|
+
print(f" cd \"{AGENT_DIR}\"")
|
|
673
|
+
print(f" python main.py")
|
|
674
|
+
print("")
|
|
675
|
+
print("3. Or use the startup script:")
|
|
676
|
+
if sys.platform == "win32":
|
|
677
|
+
print(f" Double-click: start-memory-agent.vbs")
|
|
678
|
+
else:
|
|
679
|
+
print(f" ./start-memory-agent.sh")
|
|
680
|
+
print("")
|
|
681
|
+
print("4. Open the dashboard in your browser:")
|
|
682
|
+
print(f" {config['MEMORY_AGENT_URL']}/dashboard")
|
|
683
|
+
print("")
|
|
684
|
+
print("5. Restart Claude Code to load the MCP configuration")
|
|
685
|
+
print("")
|
|
686
|
+
print(f"Configuration file: {AGENT_DIR / '.env'}")
|
|
687
|
+
print(f"Claude settings: {get_claude_settings_file()}")
|
|
688
|
+
|
|
689
|
+
|
|
690
|
+
# =============================================================================
|
|
691
|
+
# UNINSTALL
|
|
692
|
+
# =============================================================================
|
|
693
|
+
|
|
694
|
+
def uninstall() -> bool:
|
|
695
|
+
"""Remove Claude Code integration."""
|
|
696
|
+
print_header("Uninstalling Claude Memory Agent Integration")
|
|
697
|
+
|
|
698
|
+
# Remove MCP configuration
|
|
699
|
+
settings_file = get_claude_settings_file()
|
|
700
|
+
if settings_file.exists():
|
|
701
|
+
try:
|
|
702
|
+
settings = json.loads(settings_file.read_text())
|
|
703
|
+
if "mcpServers" in settings and "claude-memory" in settings["mcpServers"]:
|
|
704
|
+
del settings["mcpServers"]["claude-memory"]
|
|
705
|
+
settings_file.write_text(json.dumps(settings, indent=2))
|
|
706
|
+
print_success("Removed MCP configuration")
|
|
707
|
+
except Exception as e:
|
|
708
|
+
print_warning(f"Could not update settings: {e}")
|
|
709
|
+
|
|
710
|
+
# Remove hooks
|
|
711
|
+
hooks_dir = get_hooks_dir()
|
|
712
|
+
hooks_to_remove = ["session_start.py", "session_end.py", "grounding-hook.py"]
|
|
713
|
+
for hook in hooks_to_remove:
|
|
714
|
+
hook_file = hooks_dir / hook
|
|
715
|
+
if hook_file.exists():
|
|
716
|
+
hook_file.unlink()
|
|
717
|
+
print_success(f"Removed hook: {hook}")
|
|
718
|
+
|
|
719
|
+
print("\nUninstall complete. The .env file and database are preserved.")
|
|
720
|
+
print("To fully remove, delete the memory-agent directory.")
|
|
721
|
+
return True
|
|
722
|
+
|
|
723
|
+
|
|
724
|
+
# =============================================================================
|
|
725
|
+
# MAIN
|
|
726
|
+
# =============================================================================
|
|
727
|
+
|
|
728
|
+
def main():
|
|
729
|
+
parser = argparse.ArgumentParser(
|
|
730
|
+
description="Install and configure Claude Memory Agent"
|
|
731
|
+
)
|
|
732
|
+
parser.add_argument(
|
|
733
|
+
"--auto",
|
|
734
|
+
action="store_true",
|
|
735
|
+
help="Auto-install with default settings"
|
|
736
|
+
)
|
|
737
|
+
parser.add_argument(
|
|
738
|
+
"--uninstall",
|
|
739
|
+
action="store_true",
|
|
740
|
+
help="Remove Claude Code integration"
|
|
741
|
+
)
|
|
742
|
+
parser.add_argument(
|
|
743
|
+
"--port",
|
|
744
|
+
type=int,
|
|
745
|
+
default=8102,
|
|
746
|
+
help="Port for the memory agent (default: 8102)"
|
|
747
|
+
)
|
|
748
|
+
parser.add_argument(
|
|
749
|
+
"--skip-deps",
|
|
750
|
+
action="store_true",
|
|
751
|
+
help="Skip installing Python dependencies"
|
|
752
|
+
)
|
|
753
|
+
parser.add_argument(
|
|
754
|
+
"--skip-claude-check",
|
|
755
|
+
action="store_true",
|
|
756
|
+
help="Skip Claude Code installation check (for standalone use)"
|
|
757
|
+
)
|
|
758
|
+
|
|
759
|
+
args = parser.parse_args()
|
|
760
|
+
|
|
761
|
+
if args.uninstall:
|
|
762
|
+
return 0 if uninstall() else 1
|
|
763
|
+
|
|
764
|
+
print_header("Claude Memory Agent Installation")
|
|
765
|
+
print(f"Agent directory: {AGENT_DIR}")
|
|
766
|
+
print(f"Platform: {sys.platform}")
|
|
767
|
+
|
|
768
|
+
# Step 1: Check prerequisites
|
|
769
|
+
total_steps = 9
|
|
770
|
+
print_step(1, total_steps, "Checking prerequisites...")
|
|
771
|
+
|
|
772
|
+
if not check_python_version():
|
|
773
|
+
return 1
|
|
774
|
+
|
|
775
|
+
# Check Node.js and Claude Code (unless skipped)
|
|
776
|
+
claude_ok = False
|
|
777
|
+
if not args.skip_claude_check:
|
|
778
|
+
nodejs_ok, nodejs_version = check_nodejs()
|
|
779
|
+
npm_ok = False
|
|
780
|
+
if nodejs_ok:
|
|
781
|
+
npm_ok = check_npm()
|
|
782
|
+
|
|
783
|
+
# Check Claude Code
|
|
784
|
+
claude_ok, claude_version = check_claude_code()
|
|
785
|
+
|
|
786
|
+
# If Claude Code not found, check if we can install it
|
|
787
|
+
if not claude_ok:
|
|
788
|
+
if not nodejs_ok:
|
|
789
|
+
# Neither Node.js nor Claude Code - show instructions and exit
|
|
790
|
+
print_installation_instructions()
|
|
791
|
+
return 1
|
|
792
|
+
elif npm_ok:
|
|
793
|
+
# Node.js available but Claude Code not installed
|
|
794
|
+
if args.auto or prompt_yes_no("Claude Code not found. Install it now?"):
|
|
795
|
+
if not install_claude_code():
|
|
796
|
+
print_error("Could not install Claude Code automatically.")
|
|
797
|
+
print("Please install manually: npm install -g @anthropic-ai/claude-code")
|
|
798
|
+
if not prompt_yes_no("Continue anyway (memory agent only)?", default=False):
|
|
799
|
+
return 1
|
|
800
|
+
else:
|
|
801
|
+
claude_ok = True
|
|
802
|
+
else:
|
|
803
|
+
print_warning("Skipping Claude Code installation")
|
|
804
|
+
print(" The memory agent will work, but Claude Code integration requires Claude Code")
|
|
805
|
+
else:
|
|
806
|
+
print_warning("npm not found - cannot auto-install Claude Code")
|
|
807
|
+
print(" Install Claude Code manually: npm install -g @anthropic-ai/claude-code")
|
|
808
|
+
else:
|
|
809
|
+
print_success("Skipping Claude Code check (--skip-claude-check)")
|
|
810
|
+
claude_ok = True # Assume it's OK for standalone mode
|
|
811
|
+
|
|
812
|
+
# Check Ollama
|
|
813
|
+
ollama_ok = check_ollama()
|
|
814
|
+
|
|
815
|
+
# Step 2: Configure settings
|
|
816
|
+
print_step(2, total_steps, "Configuring settings...")
|
|
817
|
+
|
|
818
|
+
config = DEFAULT_CONFIG.copy()
|
|
819
|
+
config["PORT"] = str(args.port)
|
|
820
|
+
config["MEMORY_AGENT_URL"] = f"http://localhost:{args.port}"
|
|
821
|
+
|
|
822
|
+
if not args.auto:
|
|
823
|
+
if not ollama_ok:
|
|
824
|
+
config["OLLAMA_HOST"] = prompt_value(
|
|
825
|
+
"Ollama host URL",
|
|
826
|
+
config["OLLAMA_HOST"]
|
|
827
|
+
)
|
|
828
|
+
|
|
829
|
+
if prompt_yes_no("Use default embedding model (nomic-embed-text)?"):
|
|
830
|
+
pass
|
|
831
|
+
else:
|
|
832
|
+
config["EMBEDDING_MODEL"] = prompt_value(
|
|
833
|
+
"Embedding model name",
|
|
834
|
+
config["EMBEDDING_MODEL"]
|
|
835
|
+
)
|
|
836
|
+
|
|
837
|
+
if ollama_ok:
|
|
838
|
+
check_ollama_model(config["EMBEDDING_MODEL"])
|
|
839
|
+
|
|
840
|
+
# Step 3: Install dependencies
|
|
841
|
+
print_step(3, total_steps, "Installing dependencies...")
|
|
842
|
+
if not args.skip_deps:
|
|
843
|
+
install_dependencies()
|
|
844
|
+
else:
|
|
845
|
+
print_success("Skipped dependency installation")
|
|
846
|
+
|
|
847
|
+
# Step 4: Create .env file
|
|
848
|
+
print_step(4, total_steps, "Creating configuration file...")
|
|
849
|
+
if not create_env_file(config, force=args.auto):
|
|
850
|
+
return 1
|
|
851
|
+
|
|
852
|
+
# Step 5: Fix hardcoded values
|
|
853
|
+
print_step(5, total_steps, "Fixing hardcoded values...")
|
|
854
|
+
fix_agent_card_port()
|
|
855
|
+
fix_dashboard_urls()
|
|
856
|
+
fix_start_daemon_url(config)
|
|
857
|
+
|
|
858
|
+
# Step 6: Create startup script
|
|
859
|
+
print_step(6, total_steps, "Creating startup script...")
|
|
860
|
+
create_startup_script()
|
|
861
|
+
|
|
862
|
+
# Step 7: Configure Claude Code (only if Claude Code is available)
|
|
863
|
+
print_step(7, total_steps, "Configuring Claude Code integration...")
|
|
864
|
+
|
|
865
|
+
if claude_ok:
|
|
866
|
+
if args.auto or prompt_yes_no("Configure Claude Code MCP settings?"):
|
|
867
|
+
configure_claude_mcp(config)
|
|
868
|
+
|
|
869
|
+
if args.auto or prompt_yes_no("Install Claude Code hooks?"):
|
|
870
|
+
setup_hooks(config)
|
|
871
|
+
configure_hooks_json()
|
|
872
|
+
else:
|
|
873
|
+
print_warning("Skipping Claude Code configuration (Claude Code not installed)")
|
|
874
|
+
print(" Run 'python install.py' again after installing Claude Code")
|
|
875
|
+
|
|
876
|
+
# Step 8: Verify
|
|
877
|
+
print_step(8, total_steps, "Verifying installation...")
|
|
878
|
+
verify_installation()
|
|
879
|
+
|
|
880
|
+
# Done!
|
|
881
|
+
print_post_install_instructions(config)
|
|
882
|
+
|
|
883
|
+
return 0
|
|
884
|
+
|
|
885
|
+
|
|
886
|
+
if __name__ == "__main__":
|
|
887
|
+
sys.exit(main())
|