Compare commits

...

14 Commits

Author SHA1 Message Date
Alex Newman 19af455c57 chore: bump version to 7.1.2
🐛 Bug Fixes

**Windows Installation**
- Fixed Bun PATH detection on Windows after fresh install
- Added fallback to check common install paths before PATH reload
- Improved smart-install.js to use full Bun path when not in PATH
- Added proper path quoting for Windows usernames with spaces

**Worker Startup**
- Fixed worker connection failures in Stop hook
- Added health check retry loop (5 attempts, 500ms intervals)
- Worker now waits up to 2.5s for responsiveness before returning
- Improved error detection for Bun's ConnectionRefused error format

🎯 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
2025-12-12 22:50:53 -05:00
Alex Newman b5807aed2e docs: regenerate CHANGELOG from GitHub releases 2025-12-12 22:41:39 -05:00
Alex Newman c6fd984cc1 chore: bump version to 7.1.1
- Updated all version files
- Updated CHANGELOG with v7.1.1 release notes
- Critical fixes: Windows Bun auto-install, path quoting
- New feature: Automatic worker restart on version updates
2025-12-12 22:40:48 -05:00
Alex Newman 490ba182d5 feat: automatic worker restart on version updates
Critical improvement for seamless upgrades across all versions.

Changes:
1. Added /api/version endpoint to worker service
   - Returns current worker version from package.json

2. Added version checking in worker-utils.ts
   - getPluginVersion() - reads plugin's package.json version
   - getWorkerVersion() - fetches version from worker API
   - ensureWorkerVersionMatches() - compares and restarts if needed

3. Modified ensureWorkerRunning()
   - Now calls ensureWorkerVersionMatches() after health check
   - Automatically restarts worker when version mismatch detected
   - Logs version mismatch for debugging

Impact:
- Users no longer need to manually restart worker after upgrades
- Eliminates connection errors from running old worker code
- Critical for v7.1.1 (Bun auto-install) and all future releases
- Fixes the issue where PR #236 changes weren't applied until manual restart

Testing:
- Version endpoint working: returns {"version":"7.1.0"}
- Worker health check: passing
- Auto-restart logic: triggers on version mismatch

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
2025-12-12 22:39:24 -05:00
Alex Newman 4baed97bd0 fix(windows): restore Bun auto-installation in smart-install.js
CRITICAL FIX: v7.1.0 Bun auto-install broken on Windows 11

Problem:
- hooks.json calls `bun smart-install.js` but if Bun isn't installed,
  command fails immediately with "bun is not recognized"
- smart-install.js never runs, so Bun never gets installed
- Chicken-and-egg problem

Root Cause:
- v7.1.0 removed Bun/uv auto-installation logic from smart-install.js
- Assumed Bun would already be available
- Breaks fresh installations on all platforms

Solution:
1. Changed SessionStart hook to use `node` for smart-install.js
   (Node.js always available in Claude Code)
2. Restored Bun auto-installation logic:
   - isBunInstalled() - check if Bun is in PATH
   - installBun() - auto-install via PowerShell (Windows) or curl (Unix/macOS)
   - Also restored uv auto-installation for Chroma

After Fix:
- smart-install.js runs with node (always available)
- Detects if Bun is missing and auto-installs it
- Subsequent hooks use bun successfully
- Works on fresh Windows installations

Fixes: User report from Discord - Windows 11 'bun' is not recognized

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
2025-12-12 22:33:03 -05:00
Alex Newman f41824fa59 fix: quote all CLAUDE_PLUGIN_ROOT paths in hooks.json for Windows usernames with spaces
Wraps all ${CLAUDE_PLUGIN_ROOT} variable expansions with quotes to handle
Windows paths containing spaces (e.g., C:\Users\John Doe\.claude\...).

Without quotes, the shell splits the path at spaces, causing Node.js to
interpret the first segment as JavaScript and throw SyntaxError: Unexpected
token ':'.

This fix updates the Bun migration (v7.1.0) to include proper quoting,
superseding PR #212 which was based on pre-migration code.

Fixes #212

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
2025-12-12 22:28:19 -05:00
Alex Newman 80ba7633e5 docs: update CHANGELOG.md for localhost-only binding security fix 2025-12-12 22:18:32 -05:00
Alex Newman d14266d70a feat(security): default worker to localhost-only binding (#236)
BREAKING CHANGE: Worker now binds to 127.0.0.1 by default instead of 0.0.0.0

This fixes a critical security issue where the worker service was exposing
all API endpoints to the network without authentication.

Changes:
- Default worker binding changed from 0.0.0.0 to 127.0.0.1 (localhost-only)
- Added CLAUDE_MEM_WORKER_HOST configuration setting
- Added host validation in settings API
- Updated UI to allow configuring bind address
- Updated documentation in README.md and CLAUDE.md

For users who need remote access (e.g., server deployments),
set CLAUDE_MEM_WORKER_HOST=0.0.0.0 in ~/.claude-mem/settings.json

All 42 tests pass. Build successful.

Co-authored-by: 7Sageer <12210216@mail.sustech.edu.cn>
2025-12-12 22:17:56 -05:00
Alex Newman 1cd545c36c Merge main into feature/localhost-only-binding - rebuild plugin files 2025-12-12 22:17:19 -05:00
Alex Newman 901af0b7f7 docs: update metadata and descriptions across multiple documentation files 2025-12-12 21:47:58 -05:00
Alex Newman 6815cc55b8 docs: add Mintlify documentation section to CLAUDE.md
🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
2025-12-12 21:26:51 -05:00
Alex Newman 12603a1a5c docs: add PM2 to Bun migration documentation to Mintlify
Add comprehensive technical documentation explaining the v7.1.0 migration from PM2 to Bun-based process management and better-sqlite3 to bun:sqlite database driver.

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
2025-12-12 21:22:00 -05:00
Alex Newman 15d05b5ac7 docs: update CHANGELOG.md for v7.1.0
🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
2025-12-12 21:13:23 -05:00
7Sageer b8a9f366e7 feat(security): default worker to localhost-only binding
BREAKING: Worker now binds to 127.0.0.1 by default.
Set CLAUDE_MEM_WORKER_HOST=0.0.0.0 for remote access.
2025-12-11 22:01:31 +08:00
40 changed files with 1409 additions and 408 deletions
+1 -1
View File
@@ -10,7 +10,7 @@
"plugins": [
{
"name": "claude-mem",
"version": "7.1.0",
"version": "7.1.2",
"source": "./plugin",
"description": "Persistent memory system for Claude Code - context compression across sessions"
}
+167
View File
@@ -0,0 +1,167 @@
# Action Plan: Issues & PRs Cleanup
Generated: 2025-12-12
## Phase 1: Immediate Cleanup (Today)
### Close Obsolete PRs
- [ ] **#255** - Close PR "Fix PM2 worker MODULE_NOT_FOUND"
- Reason: v7.1.0 removed PM2 entirely, this fix is no longer relevant
- Comment: Explain that v7.1.0 migration to Bun eliminated PM2 dependency
- [ ] **#206** - Close or request update on "Harden worker startup"
- Reason: Contains PM2-specific code that no longer exists
- Comment: Ask author if they want to update for Bun architecture, otherwise close as obsolete
### Close/Update Fixed Issues
- [ ] **#213** - Comment and close "Windows endless process spawning"
- Reason: v7.1.0 Bun migration eliminated PM2 process management
- Comment: Ask user to verify fix on v7.1.0, explain PM2 removal resolved issue
- [ ] **#229** - Close as duplicate
- Reason: Duplicate of #227 (upstream Claude Code bug)
- Comment: Direct to #227 for full details and workaround
- [ ] **#211** - Answer and close "Cursor IDE support question"
- Reason: Product question, not a bug report
- Comment: Explain focus is Claude Code, but plugin architecture may allow future expansion
### Critical Bug Follow-Up
- [ ] **#254** - Follow up on "Worker API fetch failed"
- Current status: Asked about PM2 logs (pre-v7.1.0 comment)
- Action: Update comment asking:
- What version of claude-mem are you running?
- If pre-v7.1.0: Please upgrade to v7.1.0 which fixes PM2 issues
- If v7.1.0+: Run troubleshoot skill and share logs
## Phase 2: High-Priority Merges (This Week)
### Security & Critical Fixes
- [ ] **#236** - Review and merge "Localhost-only binding" 🔒 PRIORITY
- Impact: Security improvement (fixes network exposure)
- Status: 156 additions, all tests pass (42/42)
- Action: Final review, merge, update CHANGELOG
- [ ] **#212** - Review and merge "Windows path quoting fix"
- Impact: Fixes Windows usernames with spaces
- Status: 6 lines changed, minimal risk
- Action: Quick cross-platform test, merge
### Major Features (Maintainer-Authored)
- [ ] **#225** - Review and merge "Export/Import scripts"
- Impact: Enables backup/restore, partially addresses #233
- Status: 927 additions, extensively tested by maintainer
- Action: Final review, merge, update docs
- [ ] **#250** - Review and merge "README translations"
- Impact: International user onboarding (22 languages)
- Status: 10,209 additions (massive but low-risk)
- Action: Spot-check a few translations, merge
### User-Requested Features
- [ ] **#252** - Test and merge "Execution traces" (addresses #194)
- Impact: Shows tools/skills/MCPs in UI bubbles
- Status: 383 additions, comprehensive implementation
- Action: Test database migration, API endpoints, UI display
- [ ] **#251** - Test and merge "Plan file context" (addresses #180)
- Impact: Injects last plan file into context
- Status: 85 additions, follows existing patterns
- Action: Test with real plan files, verify toggle works
## Phase 3: Review & Consider (Next Week)
### Quality Enhancements
- [ ] **#230** - Review "Multi-language support" (addresses #228)
- Impact: Observations/summaries in user's language
- Status: 157 additions, Korean screenshot provided
- Action: Review prompt changes carefully, test with multiple languages
- [ ] **#226** - Review "CLAUDE_CONFIG_DIR support"
- Impact: Supports non-standard Claude installations
- Status: 10 additions, minimal change
- Action: Test with custom config directory, merge if working
### Developer Experience
- [ ] **#216** - Review "Makefile shortcuts"
- Impact: DX improvement for contributors
- Status: 1,085 additions
- Priority: Low (not urgent)
- Action: Review when time permits
## Phase 4: Issue Follow-Ups (Ongoing)
### Awaiting User Verification
- [ ] **#209** - Follow up if no response on Windows worker startup
- Status: Already commented asking for v7.1.0 verification
- Action: Close if verified fixed, or investigate if still broken
- [ ] **#231** - Follow up if no response on module resolution
- Status: Already commented asking for v7.1.0 verification
- Action: Close if verified fixed, or investigate if still broken
### Upstream Bugs (Keep Open)
- [ ] **#227** - Keep open as documented upstream bug
- Reason: Claude Code CLI uses invalid Windows paths
- Action: No action needed, workaround documented
### Active Bugs (Investigate)
- [ ] **#208** - Investigate "Windows console windows appearing"
- Priority: Medium (cosmetic but annoying)
- Action: Reproduce on Windows, identify root cause
## Phase 5: Future Feature Planning
### Feature Requests Without PRs
- [ ] **#240** - Plan "Move MCP scaffolding to separate file"
- Type: Internal refactoring
- Priority: Low
- Action: Design approach when time permits
- [ ] **#239** - Plan "Track git branch as metadata"
- Type: Context enhancement
- Priority: Medium
- Action: Design schema changes, discuss approach
- [ ] **#215** - Plan "PreCompact event hook"
- Type: Power user feature
- Priority: Low
- Action: Evaluate use cases, design API
- [ ] **#233** - Plan "Multi-device sync" (partial solution exists)
- Type: Major feature
- Note: PR #225 provides export/import, full sync is more complex
- Action: Determine if export/import is sufficient, or plan cloud sync
## Summary
### Quick Wins (Do Today)
- Close 2 obsolete PRs (#255, #206)
- Close 3 resolved/duplicate issues (#213, #229, #211)
- Follow up on critical bug (#254)
### High-Impact Merges (This Week)
- Merge security fix (#236)
- Merge 2 simple fixes (#212, #225)
- Merge 2 major features (#250, #252, #251)
### Expected Impact
- **Security**: Localhost-only by default
- **Functionality**: Export/import, execution traces, plan context
- **UX**: Multi-language support, Windows fixes
- **Clarity**: Clean backlog, remove PM2 confusion
---
**Next Review**: After Phase 2 completion, reassess remaining items
+62
View File
@@ -4,6 +4,68 @@ All notable changes to this project will be documented in this file.
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/).
## [7.1.1] - 2025-12-13
## 🚨 Critical Fixes
### Windows 11 Bun Auto-Install Fixed
- **Problem**: v7.1.0 had a chicken-and-egg bug where `bun smart-install.js` failed if Bun wasn't installed
- **Solution**: SessionStart hook now uses `node` (always available) for smart-install.js
- **Impact**: Fresh Windows installations now work out-of-box
### Path Quoting for Windows
- Fixed `hooks.json` to quote all paths
- Prevents SyntaxError for usernames with spaces (e.g., "C:\Users\John Doe\")
## ✨ New Feature
### Automatic Worker Restart on Version Updates
- Worker now automatically restarts when plugin version changes
- No more manual `npm run worker:restart` needed after upgrades
- Eliminates connection errors from running old worker code
## 📝 Notes
- **No manual actions required** - worker auto-restarts on next session start
- All future upgrades will automatically restart the worker
- Fresh installs on Windows 11 work correctly
## 🔗 Links
- [Full Changelog](https://github.com/thedotmack/claude-mem/blob/main/CHANGELOG.md#711---2025-12-12)
- [Documentation](https://docs.claude-mem.ai)
## [7.1.0] - 2025-12-13
## Major Architectural Migration
This release completely replaces PM2 with native Bun-based process management and migrates from better-sqlite3 to bun:sqlite.
### Key Changes
**Process Management**
- Replace PM2 with custom Bun-based ProcessManager
- PID file-based process tracking
- Automatic legacy PM2 process cleanup on all platforms
**Database Driver**
- Migrate from better-sqlite3 npm package to bun:sqlite runtime module
- Zero native compilation required
- Same API compatibility
**Auto-Installation**
- Bun runtime auto-installed if missing
- uv (Python package manager) auto-installed for Chroma vector search
- Smart installer with platform-specific methods (curl/PowerShell)
### Migration
**Automatic**: First hook trigger after update performs one-time PM2 cleanup and transitions to new architecture. No user action required.
### Documentation
Complete technical documentation in `docs/PM2-TO-BUN-MIGRATION.md`
## [7.0.11] - 2025-12-12
Patch release adding feature/bun-executable to experimental branch selector for testing Bun runtime integration.
+9 -1
View File
@@ -6,7 +6,7 @@
Claude-mem is a Claude Code plugin providing persistent memory across sessions. It captures tool usage, compresses observations using the Claude Agent SDK, and injects relevant context into future sessions.
**Current Version**: 7.1.0
**Current Version**: 7.1.2
## Architecture
@@ -50,6 +50,7 @@ Settings are managed in `~/.claude-mem/settings.json`. The file is auto-created
- `CLAUDE_MEM_MODEL` - Model for observations/summaries (default: claude-haiku-4-5)
- `CLAUDE_MEM_CONTEXT_OBSERVATIONS` - Observations injected at SessionStart (default: 50)
- `CLAUDE_MEM_WORKER_PORT` - Worker service port (default: 37777)
- `CLAUDE_MEM_WORKER_HOST` - Worker bind address (default: 127.0.0.1, use 0.0.0.0 for remote access)
**System Configuration:**
- `CLAUDE_MEM_DATA_DIR` - Data directory location (default: ~/.claude-mem)
@@ -92,3 +93,10 @@ npm run worker:logs # View worker logs
**Viewer UI**: http://localhost:37777
**Worker Logs**: `~/.claude-mem/logs/worker-YYYY-MM-DD.log`
## Documentation
**Public Docs**: https://docs.claude-mem.ai (Mintlify)
**Source**: `docs/public/` - MDX files, edit `docs.json` for navigation
**Deploy**: Auto-deploys from GitHub on push to main
**Local Dev**: `cd docs/public && npx mintlify dev`
+1
View File
@@ -326,6 +326,7 @@ Settings are managed in `~/.claude-mem/settings.json`. The file is auto-created
|---------|---------|-------------|
| `CLAUDE_MEM_MODEL` | `claude-haiku-4-5` | AI model for observations |
| `CLAUDE_MEM_WORKER_PORT` | `37777` | Worker service port |
| `CLAUDE_MEM_WORKER_HOST` | `127.0.0.1` | Worker bind address (use `0.0.0.0` for remote access) |
| `CLAUDE_MEM_DATA_DIR` | `~/.claude-mem` | Data directory location |
| `CLAUDE_MEM_LOG_LEVEL` | `INFO` | Log verbosity (DEBUG, INFO, WARN, ERROR, SILENT) |
| `CLAUDE_MEM_PYTHON_VERSION` | `3.13` | Python version for chroma-mcp |
+6 -1
View File
@@ -1,4 +1,9 @@
# Architecture Evolution: The Journey from v3 to v5
---
title: "Architecture Evolution"
description: "How claude-mem evolved from v3 to v5+"
---
# Architecture Evolution
## The Problem We Solved
+2 -2
View File
@@ -9,9 +9,9 @@ Claude-Mem uses SQLite 3 with the bun:sqlite native module for persistent storag
## Database Location
- **Current**: `~/.claude-mem/claude-mem.db`
**Path**: `~/.claude-mem/claude-mem.db`
**Note**: Despite the README claiming v4.0.0+ moved the database to `${CLAUDE_PLUGIN_ROOT}/data/`, the actual implementation still uses `~/.claude-mem/`.
The database uses SQLite's WAL (Write-Ahead Logging) mode for concurrent reads/writes.
## Database Implementation
@@ -0,0 +1,551 @@
---
title: "PM2 to Bun Migration"
description: "Complete technical documentation for the process management and database driver migration in v7.1.0"
---
# PM2 to Bun Migration: Complete Technical Documentation
**Version**: 7.1.0
**Date**: December 2025
**Migration Type**: Process Management (PM2 → Bun) + Database Driver (better-sqlite3 → bun:sqlite)
## Executive Summary
Claude-mem version 7.1.0 introduces two major architectural migrations:
1. **Process Management**: PM2 → Custom Bun-based ProcessManager
2. **Database Driver**: better-sqlite3 npm package → bun:sqlite runtime module
Both migrations are **automatic** and **transparent** to end users. The first time a hook fires after updating to 7.1.0+, the system performs a one-time cleanup of legacy PM2 processes and transitions to the new architecture.
### Key Benefits
- **Simplified Dependencies**: Removes PM2 and better-sqlite3 npm packages
- **Improved Cross-Platform Support**: Better Windows compatibility
- **Faster Installation**: No native module compilation required
- **Built-in Runtime**: Leverages Bun's built-in process management and SQLite
- **Reduced Complexity**: Custom ProcessManager is simpler than PM2 integration
### Migration Impact
- **Data Preservation**: User data, settings, and database remain unchanged
- **Automatic Cleanup**: Old PM2 processes automatically terminated (all platforms)
- **No User Action Required**: Migration happens automatically on first hook trigger
- **Backward Compatible**: SQLite database format unchanged (only driver changed)
## Architecture Comparison
### Old System (PM2-based)
<AccordionGroup>
<Accordion title="Process Management (PM2)">
**Component**: PM2 (Process Manager 2)
- **Package**: `pm2` npm dependency
- **Process Name**: `claude-mem-worker`
- **Management**: External PM2 daemon manages lifecycle
- **Discovery**: `pm2 list`, `pm2 describe` commands
- **Auto-restart**: PM2 automatically restarts on crash
- **Logs**: `~/.pm2/logs/claude-mem-worker-*.log`
- **PID File**: `~/.pm2/pids/claude-mem-worker.pid`
**Lifecycle Commands**:
```bash
pm2 start <script> # Start worker
pm2 stop claude-mem-worker # Stop worker
pm2 restart claude-mem-worker # Restart worker
pm2 delete claude-mem-worker # Remove from PM2
pm2 logs claude-mem-worker # View logs
```
**Pain Points**:
- Additional npm dependency required
- PM2 daemon must be running
- Potential conflicts with other PM2 processes
- Windows compatibility issues
- Complex configuration for simple use case
</Accordion>
<Accordion title="Database Driver (better-sqlite3)">
**Component**: better-sqlite3
- **Package**: `better-sqlite3` npm package (native module)
- **Installation**: Requires native compilation (node-gyp)
- **Windows**: Requires Visual Studio build tools + Python
- **Import**: `import Database from 'better-sqlite3'`
**Installation Requirements**:
- Node.js development headers
- C++ compiler (gcc/clang on Mac/Linux, MSVC on Windows)
- Python (for node-gyp)
- Windows: Visual Studio Build Tools
</Accordion>
</AccordionGroup>
### New System (Bun-based)
<AccordionGroup>
<Accordion title="Process Management (Custom ProcessManager)">
**Component**: Custom ProcessManager (`src/services/process/ProcessManager.ts`)
- **Package**: Built-in Bun APIs (no external dependency)
- **Process Spawn**: `Bun.spawn()` with detached mode
- **Management**: Direct process control via PID file
- **Discovery**: PID file + process existence check + HTTP health check
- **Auto-restart**: Hook-triggered restart on failure detection
- **Logs**: `~/.claude-mem/logs/worker-YYYY-MM-DD.log`
- **PID File**: `~/.claude-mem/.worker.pid`
- **Port File**: `~/.claude-mem/.worker.port` (new)
**Lifecycle Commands**:
```bash
npm run worker:start # Start worker
npm run worker:stop # Stop worker
npm run worker:restart # Restart worker
npm run worker:status # Check status
npm run worker:logs # View logs
```
**Core Mechanisms**:
1. **PID File Management**:
- File: `~/.claude-mem/.worker.pid`
- Content: Process ID (e.g., "35557")
- Validation: Process existence via `kill(pid, 0)` signal
2. **Port File Management**:
- File: `~/.claude-mem/.worker.port`
- Content: Two lines (port number, PID)
- Purpose: Track port binding and validate PID match
3. **Health Checking**:
- Layer 1: PID file exists?
- Layer 2: Process alive? (`kill(pid, 0)`)
- Layer 3: HTTP health check (`GET /health`)
- All three must pass for "healthy" status
**Advantages**:
- No external dependencies
- Simpler codebase (direct control)
- Better error handling and validation
- Platform-agnostic (Bun handles platform differences)
</Accordion>
<Accordion title="Database Driver (bun:sqlite)">
**Component**: bun:sqlite
- **Package**: Built into Bun runtime (no npm package)
- **Installation**: None required (comes with Bun ≥1.0)
- **Platform**: Works anywhere Bun works
- **Import**: `import { Database } from 'bun:sqlite'`
- **API**: Similar to better-sqlite3 (synchronous)
**Installation Requirements**:
- Bun ≥1.0 (automatically installed if missing)
- No native compilation required
- No platform-specific build tools needed
**Compatibility**:
- SQLite database format: **Unchanged**
- Database file: `~/.claude-mem/claude-mem.db` (same location)
- Query syntax: **Identical** (both use SQLite SQL)
</Accordion>
</AccordionGroup>
## Migration Mechanics
### One-Time PM2 Cleanup
The migration system uses a marker-based approach to perform PM2 cleanup exactly once.
**Implementation**: `src/shared/worker-utils.ts:73-86`
```typescript
// Clean up legacy PM2 (one-time migration)
const pm2MigratedMarker = join(DATA_DIR, '.pm2-migrated');
if (!existsSync(pm2MigratedMarker)) {
try {
spawnSync('pm2', ['delete', 'claude-mem-worker'], { stdio: 'ignore' });
// Mark migration as complete
writeFileSync(pm2MigratedMarker, new Date().toISOString(), 'utf-8');
logger.debug('SYSTEM', 'PM2 cleanup completed and marked');
} catch {
// PM2 not installed or process doesn't exist - still mark as migrated
writeFileSync(pm2MigratedMarker, new Date().toISOString(), 'utf-8');
}
}
```
### Migration Trigger Points
<Steps>
<Step title="Hook Execution">
SessionStart, UserPromptSubmit, or PostToolUse hooks execute using new 7.1.0 code
</Step>
<Step title="Worker Status Check">
`ensureWorkerRunning()` checks if `~/.claude-mem/.worker.pid` exists (it doesn't for first run after update)
</Step>
<Step title="Start Worker Decision">
Worker not running → Call `startWorker()`
</Step>
<Step title="Migration Check">
Check if `~/.claude-mem/.pm2-migrated` exists
</Step>
<Step title="PM2 Cleanup">
Execute `pm2 delete claude-mem-worker` (errors ignored), create marker file
</Step>
<Step title="New Worker Start">
Spawn new Bun-managed worker process with PID and port files
</Step>
</Steps>
### Marker File
**Location**: `~/.claude-mem/.pm2-migrated`
**Content**: ISO 8601 timestamp
```
2025-12-13T00:18:39.673Z
```
**Purpose**:
- One-time migration flag
- Prevents repeated PM2 cleanup on every start
- Persists across restarts and reboots
**Lifecycle**:
- Created: First hook trigger after update to 7.1.0+ (all platforms)
- Updated: Never
- Deleted: Never (user could manually delete to force re-migration)
## User Experience Timeline
### First Session After Update
<Note>
This is the critical migration moment. The process takes approximately 2-5 seconds.
</Note>
**Step-by-Step Execution**:
1. **Hook fires** (SessionStart most common)
2. **Worker status check**: No PID file → worker not running
3. **Migration check**: No marker file → run PM2 cleanup
4. **PM2 cleanup**: `pm2 delete claude-mem-worker` (old worker terminated)
5. **Marker creation**: `~/.claude-mem/.pm2-migrated` with timestamp
6. **New worker start**: Bun process spawned, PID/port files created
7. **Verification**: Process check + HTTP health check
8. **Hook completes**: Claude Code session starts normally
**User Observable Behavior**:
- Slight delay on first startup (PM2 cleanup + new worker spawn)
- No error messages (cleanup failures silently handled)
- Worker appears running via `npm run worker:status`
- Old PM2 worker no longer in `pm2 list`
### Subsequent Sessions
After migration completes, every hook trigger follows the fast path:
1. PID file exists? **YES**
2. Process alive? **YES**
3. HTTP health check? **SUCCESS**
4. Result: Worker already running, done (~50ms)
No migration logic runs on subsequent sessions.
## Platform-Specific Behavior
### Platform Comparison
| Feature | macOS | Linux | Windows |
|---------|-------|-------|---------|
| PM2 Cleanup | Attempted | Attempted | Attempted |
| Marker File | Created | Created | Created |
| Process Signals | POSIX (native) | POSIX (native) | Bun abstraction |
| Bun Support | Full | Full | Full |
| PID File | Yes | Yes | Yes |
| Port File | Yes | Yes | Yes |
| Health Check | HTTP | HTTP | HTTP |
| Migration Delay | ~2-5s first time | ~2-5s first time | ~2-5s first time |
### Platform Notes
<Tabs>
<Tab title="macOS">
- POSIX signal handling works natively
- Bun fully supported
- No platform-specific workarounds needed
</Tab>
<Tab title="Linux">
- Identical behavior to macOS
- POSIX signal handling
- Works on Ubuntu, Debian, RHEL, CentOS, Arch
- Alpine may require glibc (not musl)
</Tab>
<Tab title="Windows">
- PM2 cleanup now runs (safe due to try/catch)
- Bun abstracts signal handling differences
- Path module handles Windows separators
- File locking handled by SQLite
</Tab>
</Tabs>
## Observable Changes
### Command Changes
| Old (PM2) | New (Bun) | Notes |
|-----------|-----------|-------|
| `pm2 list` | `npm run worker:status` | Shows worker status |
| `pm2 start <script>` | `npm run worker:start` | Start worker |
| `pm2 stop claude-mem-worker` | `npm run worker:stop` | Stop worker |
| `pm2 restart claude-mem-worker` | `npm run worker:restart` | Restart worker |
| `pm2 delete claude-mem-worker` | `npm run worker:stop` | Remove worker |
| `pm2 logs claude-mem-worker` | `npm run worker:logs` | View logs |
| `pm2 describe claude-mem-worker` | `npm run worker:status` | Detailed status |
| `pm2 monit` | No equivalent | PM2-specific monitoring |
### File Location Changes
**Logs**:
```
Old: ~/.pm2/logs/claude-mem-worker-out.log
~/.pm2/logs/claude-mem-worker-error.log
New: ~/.claude-mem/logs/worker-YYYY-MM-DD.log
```
**PID Files**:
```
Old: ~/.pm2/pids/claude-mem-worker.pid
New: ~/.claude-mem/.worker.pid
```
**Process State**:
```
Old: PM2 daemon memory (pm2 save)
New: ~/.claude-mem/.worker.pid
~/.claude-mem/.worker.port
~/.claude-mem/.pm2-migrated (all platforms)
```
**Database** (unchanged):
```
Same: ~/.claude-mem/claude-mem.db
```
### User-Visible Changes
**Before Update**:
```bash
$ pm2 list
┌────┬────────────────────┬─────────┬─────────┬──────────┐
│ id │ name │ status │ restart │ uptime │
├────┼────────────────────┼─────────┼─────────┼──────────┤
│ 0 │ claude-mem-worker │ online │ 0 │ 2d 5h │
└────┴────────────────────┴─────────┴─────────┴──────────┘
```
**After Update**:
```bash
$ pm2 list
# Empty - worker no longer managed by PM2
$ npm run worker:status
Worker is running
PID: 35557
Port: 37777
Uptime: 2h 15m
```
### Orphaned Files
After migration, these PM2 files may remain (safe to delete):
```
~/.pm2/ # Entire PM2 directory
~/.pm2/logs/ # Old logs
~/.pm2/pids/ # Old PID files
~/.pm2/pm2.log # PM2 daemon log
~/.pm2/dump.pm2 # PM2 process dump
```
**Cleanup (optional)**:
```bash
# Remove PM2 entirely (if not used for other processes)
pm2 kill
rm -rf ~/.pm2
# Or just remove claude-mem logs
rm -f ~/.pm2/logs/claude-mem-worker-*.log
rm -f ~/.pm2/pids/claude-mem-worker.pid
```
## File System State
### State Directory Structure
**Before Migration** (PM2 system):
```
~/.claude-mem/
├── claude-mem.db # Database (unchanged)
├── chroma/ # Vector embeddings (unchanged)
├── logs/ # Application logs (unchanged)
└── settings.json # User settings (unchanged)
~/.pm2/
├── logs/
│ ├── claude-mem-worker-out.log
│ └── claude-mem-worker-error.log
├── pids/
│ └── claude-mem-worker.pid
└── pm2.log
```
**After Migration** (Bun system):
```
~/.claude-mem/
├── claude-mem.db # Database (same file)
├── chroma/ # Vector embeddings (unchanged)
├── logs/
│ └── worker-2025-12-13.log # New log format
├── settings.json # User settings (unchanged)
├── .worker.pid # NEW: Process ID
├── .worker.port # NEW: Port + PID
└── .pm2-migrated # NEW: Migration marker (all platforms)
~/.pm2/ # Orphaned (safe to delete)
├── logs/ # Old logs (no longer written)
├── pids/ # Old PID (no longer updated)
└── pm2.log # PM2 daemon log (not used)
```
## Edge Cases and Troubleshooting
### Scenario 1: Migration Fails (PM2 Still Running)
<Warning>
This is rare but can happen if PM2 has watch mode enabled or the process is manually restarted.
</Warning>
**Symptoms**:
- `pm2 list` still shows `claude-mem-worker`
- Port conflict errors in logs
- Worker fails to start
**Resolution**:
```bash
# Manual cleanup
pm2 delete claude-mem-worker
pm2 save # Persist the deletion
# Force re-migration (optional)
rm ~/.claude-mem/.pm2-migrated
# Restart worker
npm run worker:restart
```
### Scenario 2: Stale PID File (Process Dead)
**Symptoms**:
- `npm run worker:status` shows "not running"
- `.worker.pid` file exists
- Process ID doesn't exist
**Automatic Recovery**: Next hook trigger detects dead process and starts a fresh worker.
**Manual Resolution**:
```bash
rm ~/.claude-mem/.worker.pid
rm ~/.claude-mem/.worker.port
npm run worker:start
```
### Scenario 3: Port Already in Use
**Error**: `EADDRINUSE: address already in use`
**Resolution**:
```bash
# Check what's using the port
lsof -i :37777
# Kill the process
kill -9 <PID>
# Restart worker
npm run worker:restart
```
### Common Error Messages
| Error | Cause | Resolution |
|-------|-------|------------|
| `EADDRINUSE` | Port already in use | `lsof -i :37777` then kill conflicting process |
| `No such process` | Stale PID file | Automatic cleanup on next hook trigger |
| `pm2: command not found` | PM2 not installed | None needed (error is caught and ignored) |
| `Invalid port X` | Port validation failed | Update `CLAUDE_MEM_WORKER_PORT` in settings |
## Developer Notes
### Testing the Migration
```bash
# 1. Install old version (with PM2)
git checkout <pre-7.1.0-tag>
npm install && npm run build && npm run sync-marketplace
# 2. Start PM2 worker
pm2 start plugin/scripts/worker-cli.js --name claude-mem-worker
# 3. Update to new version
git checkout main
npm install && npm run build && npm run sync-marketplace
# 4. Trigger hook
node plugin/scripts/session-start-hook.js
# 5. Verify migration
pm2 list # Should NOT show claude-mem-worker
cat ~/.claude-mem/.pm2-migrated # Should exist
npm run worker:status # Should show Bun worker running
```
### Architecture Decisions
**Why Custom ProcessManager Instead of PM2?**
1. **Simplicity**: Direct control, no external daemon
2. **Dependencies**: Remove npm dependency
3. **Cross-platform**: Bun handles platform differences
4. **Bundle Size**: Reduce plugin package size
5. **Control**: Fine-grained error handling and validation
**Why One-Time Marker Instead of Always Running PM2 Delete?**
1. **Performance**: Avoid unnecessary process spawning
2. **Idempotency**: Migration runs exactly once
3. **Debugging**: Timestamp shows when migration occurred
4. **Simplicity**: Clear migration state
**Why Run PM2 Cleanup on All Platforms?**
1. **Quality Migration**: Clean up orphaned processes
2. **Consistency**: Same behavior across all platforms
3. **Safety**: Error handling already in place (try/catch)
4. **No Downside**: If PM2 not installed, error is caught and ignored
## Summary
The migration from PM2 to Bun-based ProcessManager is a **one-time, automatic, transparent** transition that:
1. **Removes external dependencies** (PM2, better-sqlite3)
2. **Simplifies architecture** (direct process control)
3. **Improves cross-platform support** (especially Windows)
4. **Preserves user data** (database, settings, logs unchanged)
5. **Requires no user action** (automatic on first hook trigger)
**Key Migration Moment**: First hook trigger after update to 7.1.0+
**Duration**: ~2-5 seconds (one-time delay)
**Impact**: Seamless transition, user-invisible
**Rollback**: Not needed (migration is forward-only, safe)
For most users, the migration will be completely transparent - they'll see no errors, no data loss, and experience improved reliability and simpler troubleshooting going forward.
+6 -1
View File
@@ -1,4 +1,9 @@
# Context Engineering for AI Agents: Best Practices Cheat Sheet
---
title: "Context Engineering"
description: "Best practices for curating optimal token sets for AI agents"
---
# Context Engineering for AI Agents
## Core Principle
**Find the smallest possible set of high-signal tokens that maximize the likelihood of your desired outcome.**
+2 -1
View File
@@ -70,7 +70,8 @@
"architecture/hooks",
"architecture/worker-service",
"architecture/database",
"architecture/search-architecture"
"architecture/search-architecture",
"architecture/pm2-to-bun-migration"
]
}
]
+14 -14
View File
@@ -69,12 +69,14 @@ cat plugin/hooks/hooks.json
#### 3. Data Directory Location
v4.0.0+ stores data in `${CLAUDE_PLUGIN_ROOT}/data/`:
- Database: `${CLAUDE_PLUGIN_ROOT}/data/claude-mem.db`
- Worker port file: `${CLAUDE_PLUGIN_ROOT}/data/worker.port`
- Logs: `${CLAUDE_PLUGIN_ROOT}/data/logs/`
Data is stored in `~/.claude-mem/`:
- Database: `~/.claude-mem/claude-mem.db`
- PID file: `~/.claude-mem/.worker.pid`
- Port file: `~/.claude-mem/.worker.port`
- Logs: `~/.claude-mem/logs/worker-YYYY-MM-DD.log`
- Settings: `~/.claude-mem/settings.json`
For development/testing, you can override:
Override with environment variable:
```bash
export CLAUDE_MEM_DATA_DIR=/custom/path
```
@@ -91,19 +93,17 @@ npm run worker:logs
npm run test:context
```
## Upgrading from v3.x
## Upgrading
**BREAKING CHANGES - Please Read:**
Upgrades are automatic when updating via the plugin marketplace. Key changes in recent versions:
v4.0.0 introduces breaking changes:
**v7.1.0**: PM2 replaced with native Bun process management. Migration is automatic on first hook trigger.
- **Data Location Changed**: Database moved from `~/.claude-mem/` to `${CLAUDE_PLUGIN_ROOT}/data/` (inside plugin directory)
- **Fresh Start Required**: No automatic migration from v3.x. You must start with a clean database
- **Worker Auto-Starts**: Worker service now starts automatically - no manual `npm run worker:start` needed
- **MCP Search Server**: 7 new search tools with full-text search and citations
- **Enhanced Architecture**: Improved plugin integration and data organization
**v7.0.0+**: 11 configuration settings, dual-tag privacy system.
See [CHANGELOG](https://github.com/thedotmack/claude-mem/blob/main/CHANGELOG.md) for complete details.
**v5.4.0+**: Skill-based search replaces MCP tools, saving ~2,250 tokens per session.
See [CHANGELOG](https://github.com/thedotmack/claude-mem/blob/main/CHANGELOG.md) for complete version history.
## Next Steps
+11 -12
View File
@@ -74,21 +74,20 @@ See [Architecture Overview](architecture/overview) for details.
## What's New
**v6.4.9 - Context Configuration Settings:**
- 11 new settings for fine-grained control over context injection
- Configure token economics display, observation filtering by type/concept
**v7.1.0 - Bun Migration:**
- Replaced PM2 with native Bun process management
- Switched from better-sqlite3 to bun:sqlite for faster database access
- Automatic one-time migration on first hook trigger
- Simplified cross-platform support
**v6.4.0 - Dual-Tag Privacy System:**
- `<private>` tags for user-controlled privacy - wrap sensitive content to exclude from storage
- Edge processing ensures private content never reaches database
**v6.3.0 - Version Channel:**
- Switch between stable and beta versions from the web viewer UI
**v7.0.0 - Context Configuration:**
- 11 settings for fine-grained control over context injection
- Dual-tag privacy system (`<private>` tags)
**Previous Highlights:**
- **v6.0.0**: Major session management & transcript processing improvements
- **v5.5.0**: mem-search skill enhancement with 100% effectiveness rate
- **v5.4.0**: Skill-based search architecture (~2,250 tokens saved per session)
- **v5.5.0**: mem-search skill with 100% effectiveness rate
- **v5.4.0**: Skill-based search (~2,250 tokens saved per session)
- **v5.1.0**: Web viewer UI at http://localhost:37777
## Next Steps
+1 -1
View File
@@ -1,6 +1,6 @@
---
title: "Private Tags"
description: "Control what gets stored in memory with <private> tags"
description: "Control what gets stored in memory with privacy tags"
---
# Private Tags
-8
View File
@@ -1629,7 +1629,6 @@
"integrity": "sha512-RFA/bURkcKzx/X9oumPG9Vp3D3JUgus/d0b67KB0t5S/raciymilkOa66olh78MUI92QLbEJevO7rvqU/kjwKA==",
"dev": true,
"license": "MIT",
"peer": true,
"dependencies": {
"@types/prop-types": "*",
"csstype": "^3.0.2"
@@ -2233,7 +2232,6 @@
"resolved": "https://registry.npmjs.org/express/-/express-4.21.2.tgz",
"integrity": "sha512-28HqgMZAmih1Czt9ny7qr6ek2qddF4FclbMzwhCREB6OFfH+rXAnuNCwo1/wFvrtbgsQDb4kSbX9de9lFbrXnA==",
"license": "MIT",
"peer": true,
"dependencies": {
"accepts": "~1.3.8",
"array-flatten": "1.1.1",
@@ -2957,7 +2955,6 @@
"resolved": "https://registry.npmjs.org/react/-/react-18.3.1.tgz",
"integrity": "sha512-wS+hAgJShR0KhEvPJArfuPVN1+Hz1t0Y6n5jLrGQbkb4urgPE/0Rve+1kMB1v/oWgHgm4WIcV+i7F2pTVj+2iQ==",
"license": "MIT",
"peer": true,
"dependencies": {
"loose-envify": "^1.1.0"
},
@@ -3441,7 +3438,6 @@
"integrity": "sha512-5gTmgEY/sqK6gFXLIsQNH19lWb4ebPDLA4SdLP7dsWkIXHWlG66oPuVvXSGFPppYZz8ZDZq0dYYrbHfBCVUb1Q==",
"dev": true,
"license": "MIT",
"peer": true,
"engines": {
"node": ">=12"
},
@@ -3474,7 +3470,6 @@
"integrity": "sha512-ytQKuwgmrrkDTFP4LjR0ToE2nqgy886GpvRSpU0JAnrdBYppuY5rLkRUYPU1yCryb24SsKBTL/hlDQAEFVwtZg==",
"dev": true,
"license": "MIT",
"peer": true,
"dependencies": {
"esbuild": "~0.25.0",
"get-tsconfig": "^4.7.5"
@@ -3576,7 +3571,6 @@
"integrity": "sha512-ITcnkFeR3+fI8P1wMgItjGrR10170d8auB4EpMLPqmx6uxElH3a/hHGQabSHKdqd4FXWO1nFIp9rRn7JQ34ACQ==",
"dev": true,
"license": "MIT",
"peer": true,
"dependencies": {
"esbuild": "^0.25.0",
"fdir": "^6.5.0",
@@ -3670,7 +3664,6 @@
"integrity": "sha512-5gTmgEY/sqK6gFXLIsQNH19lWb4ebPDLA4SdLP7dsWkIXHWlG66oPuVvXSGFPppYZz8ZDZq0dYYrbHfBCVUb1Q==",
"dev": true,
"license": "MIT",
"peer": true,
"engines": {
"node": ">=12"
},
@@ -3872,7 +3865,6 @@
"node_modules/zod": {
"version": "3.25.76",
"license": "MIT",
"peer": true,
"funding": {
"url": "https://github.com/sponsors/colinhacks"
}
+1 -1
View File
@@ -1,6 +1,6 @@
{
"name": "claude-mem",
"version": "7.1.0",
"version": "7.1.2",
"description": "Memory compression system for Claude Code - persist context across sessions",
"keywords": [
"claude",
+1 -1
View File
@@ -1,6 +1,6 @@
{
"name": "claude-mem",
"version": "7.1.0",
"version": "7.1.2",
"description": "Persistent memory system for Claude Code - seamlessly preserve context across sessions",
"author": {
"name": "Alex Newman"
+6 -6
View File
@@ -7,12 +7,12 @@
"hooks": [
{
"type": "command",
"command": "bun \"${CLAUDE_PLUGIN_ROOT}/scripts/smart-install.js\" && bun ${CLAUDE_PLUGIN_ROOT}/scripts/context-hook.js",
"command": "node \"${CLAUDE_PLUGIN_ROOT}/scripts/smart-install.js\" && bun \"${CLAUDE_PLUGIN_ROOT}/scripts/context-hook.js\"",
"timeout": 300
},
{
"type": "command",
"command": "bun ${CLAUDE_PLUGIN_ROOT}/scripts/user-message-hook.js",
"command": "bun \"${CLAUDE_PLUGIN_ROOT}/scripts/user-message-hook.js\"",
"timeout": 10
}
]
@@ -23,7 +23,7 @@
"hooks": [
{
"type": "command",
"command": "bun ${CLAUDE_PLUGIN_ROOT}/scripts/new-hook.js",
"command": "bun \"${CLAUDE_PLUGIN_ROOT}/scripts/new-hook.js\"",
"timeout": 120
}
]
@@ -35,7 +35,7 @@
"hooks": [
{
"type": "command",
"command": "bun ${CLAUDE_PLUGIN_ROOT}/scripts/save-hook.js",
"command": "bun \"${CLAUDE_PLUGIN_ROOT}/scripts/save-hook.js\"",
"timeout": 120
}
]
@@ -46,7 +46,7 @@
"hooks": [
{
"type": "command",
"command": "bun ${CLAUDE_PLUGIN_ROOT}/scripts/summary-hook.js",
"command": "bun \"${CLAUDE_PLUGIN_ROOT}/scripts/summary-hook.js\"",
"timeout": 120
}
]
@@ -57,7 +57,7 @@
"hooks": [
{
"type": "command",
"command": "bun ${CLAUDE_PLUGIN_ROOT}/scripts/cleanup-hook.js",
"command": "bun \"${CLAUDE_PLUGIN_ROOT}/scripts/cleanup-hook.js\"",
"timeout": 120
}
]
+1 -1
View File
@@ -1,6 +1,6 @@
{
"name": "claude-mem-plugin",
"version": "7.1.0",
"version": "7.1.1",
"private": true,
"description": "Runtime dependencies for claude-mem bundled hooks",
"type": "module",
File diff suppressed because one or more lines are too long
+1 -1
View File
@@ -1,6 +1,6 @@
"use strict";var Me=Object.create;var q=Object.defineProperty;var ke=Object.getOwnPropertyDescriptor;var Ue=Object.getOwnPropertyNames;var $e=Object.getPrototypeOf,xe=Object.prototype.hasOwnProperty;var we=(d,e)=>{for(var s in e)q(d,s,{get:e[s],enumerable:!0})},pe=(d,e,s,t)=>{if(e&&typeof e=="object"||typeof e=="function")for(let n of Ue(e))!xe.call(d,n)&&n!==s&&q(d,n,{get:()=>e[n],enumerable:!(t=ke(e,n))||t.enumerable});return d};var Fe=(d,e,s)=>(s=d!=null?Me($e(d)):{},pe(e||!d||!d.__esModule?q(s,"default",{value:d,enumerable:!0}):s,d)),Pe=d=>pe(q({},"__esModule",{value:!0}),d);var ze={};we(ze,{generateContext:()=>Qe});module.exports=Pe(ze);var w=Fe(require("path"),1),z=require("os"),H=require("fs");var Ne=require("bun:sqlite");var g=require("path"),he=require("os"),be=require("fs");var fe=require("url");var B=require("fs"),Se=require("path"),ge=require("os");var te=["bugfix","feature","refactor","discovery","decision","change"],re=["how-it-works","why-it-exists","what-changed","problem-solution","gotcha","pattern","trade-off"],le={bugfix:"\u{1F534}",feature:"\u{1F7E3}",refactor:"\u{1F504}",change:"\u2705",discovery:"\u{1F535}",decision:"\u2696\uFE0F","session-request":"\u{1F3AF}"},Ee={discovery:"\u{1F50D}",change:"\u{1F6E0}\uFE0F",feature:"\u{1F6E0}\uFE0F",bugfix:"\u{1F6E0}\uFE0F",refactor:"\u{1F6E0}\uFE0F",decision:"\u2696\uFE0F"},me=te.join(","),Te=re.join(",");var ne=(i=>(i[i.DEBUG=0]="DEBUG",i[i.INFO=1]="INFO",i[i.WARN=2]="WARN",i[i.ERROR=3]="ERROR",i[i.SILENT=4]="SILENT",i))(ne||{}),oe=class{level=null;useColor;constructor(){this.useColor=process.stdout.isTTY??!1}getLevel(){if(this.level===null){let e=$.get("CLAUDE_MEM_LOG_LEVEL").toUpperCase();this.level=ne[e]??1}return this.level}correlationId(e,s){return`obs-${e}-${s}`}sessionId(e){return`session-${e}`}formatData(e){if(e==null)return"";if(typeof e=="string")return e;if(typeof e=="number"||typeof e=="boolean")return e.toString();if(typeof e=="object"){if(e instanceof Error)return this.getLevel()===0?`${e.message}
${e.stack}`:e.message;if(Array.isArray(e))return`[${e.length} items]`;let s=Object.keys(e);return s.length===0?"{}":s.length<=3?JSON.stringify(e):`{${s.length} keys: ${s.slice(0,3).join(", ")}...}`}return String(e)}formatTool(e,s){if(!s)return e;try{let t=typeof s=="string"?JSON.parse(s):s;if(e==="Bash"&&t.command){let n=t.command.length>50?t.command.substring(0,50)+"...":t.command;return`${e}(${n})`}if(e==="Read"&&t.file_path){let n=t.file_path.split("/").pop()||t.file_path;return`${e}(${n})`}if(e==="Edit"&&t.file_path){let n=t.file_path.split("/").pop()||t.file_path;return`${e}(${n})`}if(e==="Write"&&t.file_path){let n=t.file_path.split("/").pop()||t.file_path;return`${e}(${n})`}return e}catch{return e}}log(e,s,t,n,i){if(e<this.getLevel())return;let a=new Date().toISOString().replace("T"," ").substring(0,23),c=ne[e].padEnd(5),_=s.padEnd(6),h="";n?.correlationId?h=`[${n.correlationId}] `:n?.sessionId&&(h=`[session-${n.sessionId}] `);let T="";i!=null&&(this.getLevel()===0&&typeof i=="object"?T=`
`+JSON.stringify(i,null,2):T=" "+this.formatData(i));let b="";if(n){let{sessionId:f,sdkSessionId:y,correlationId:l,...r}=n;Object.keys(r).length>0&&(b=` {${Object.entries(r).map(([O,S])=>`${O}=${S}`).join(", ")}}`)}let v=`[${a}] [${c}] [${_}] ${h}${t}${b}${T}`;e===3?console.error(v):console.log(v)}debug(e,s,t,n){this.log(0,e,s,t,n)}info(e,s,t,n){this.log(1,e,s,t,n)}warn(e,s,t,n){this.log(2,e,s,t,n)}error(e,s,t,n){this.log(3,e,s,t,n)}dataIn(e,s,t,n){this.info(e,`\u2192 ${s}`,t,n)}dataOut(e,s,t,n){this.info(e,`\u2190 ${s}`,t,n)}success(e,s,t,n){this.info(e,`\u2713 ${s}`,t,n)}failure(e,s,t,n){this.error(e,`\u2717 ${s}`,t,n)}timing(e,s,t,n){this.info(e,`\u23F1 ${s}`,n,{duration:`${t}ms`})}},U=new oe;var $=class{static DEFAULTS={CLAUDE_MEM_MODEL:"claude-haiku-4-5",CLAUDE_MEM_CONTEXT_OBSERVATIONS:"50",CLAUDE_MEM_WORKER_PORT:"37777",CLAUDE_MEM_SKIP_TOOLS:"ListMcpResourcesTool,SlashCommand,Skill,TodoWrite,AskUserQuestion",CLAUDE_MEM_DATA_DIR:(0,Se.join)((0,ge.homedir)(),".claude-mem"),CLAUDE_MEM_LOG_LEVEL:"INFO",CLAUDE_MEM_PYTHON_VERSION:"3.13",CLAUDE_CODE_PATH:"",CLAUDE_MEM_CONTEXT_SHOW_READ_TOKENS:"true",CLAUDE_MEM_CONTEXT_SHOW_WORK_TOKENS:"true",CLAUDE_MEM_CONTEXT_SHOW_SAVINGS_AMOUNT:"true",CLAUDE_MEM_CONTEXT_SHOW_SAVINGS_PERCENT:"true",CLAUDE_MEM_CONTEXT_OBSERVATION_TYPES:me,CLAUDE_MEM_CONTEXT_OBSERVATION_CONCEPTS:Te,CLAUDE_MEM_CONTEXT_FULL_COUNT:"5",CLAUDE_MEM_CONTEXT_FULL_FIELD:"narrative",CLAUDE_MEM_CONTEXT_SESSION_COUNT:"10",CLAUDE_MEM_CONTEXT_SHOW_LAST_SUMMARY:"true",CLAUDE_MEM_CONTEXT_SHOW_LAST_MESSAGE:"false"};static getAllDefaults(){return{...this.DEFAULTS}}static get(e){return this.DEFAULTS[e]}static getInt(e){let s=this.get(e);return parseInt(s,10)}static getBool(e){return this.get(e)==="true"}static loadFromFile(e){if(!(0,B.existsSync)(e))return this.getAllDefaults();let s=(0,B.readFileSync)(e,"utf-8"),t=JSON.parse(s),n=t;if(t.env&&typeof t.env=="object"){n=t.env;try{(0,B.writeFileSync)(e,JSON.stringify(n,null,2),"utf-8"),U.info("SETTINGS","Migrated settings file from nested to flat schema",{settingsPath:e})}catch(a){U.warn("SETTINGS","Failed to auto-migrate settings file",{settingsPath:e},a)}}let i={...this.DEFAULTS};for(let a of Object.keys(this.DEFAULTS))n[a]!==void 0&&(i[a]=n[a]);return i}};var We={};function Xe(){return typeof __dirname<"u"?__dirname:(0,g.dirname)((0,fe.fileURLToPath)(We.url))}var _s=Xe(),C=$.get("CLAUDE_MEM_DATA_DIR"),ie=process.env.CLAUDE_CONFIG_DIR||(0,g.join)((0,he.homedir)(),".claude"),ps=(0,g.join)(C,"archives"),ls=(0,g.join)(C,"logs"),Es=(0,g.join)(C,"trash"),ms=(0,g.join)(C,"backups"),Ts=(0,g.join)(C,"settings.json"),Oe=(0,g.join)(C,"claude-mem.db"),Ss=(0,g.join)(C,"vector-db"),gs=(0,g.join)(ie,"settings.json"),hs=(0,g.join)(ie,"commands"),bs=(0,g.join)(ie,"CLAUDE.md");function Re(d){(0,be.mkdirSync)(d,{recursive:!0})}var J=class{db;constructor(){Re(C),this.db=new Ne.Database(Oe),this.db.run("PRAGMA journal_mode = WAL"),this.db.run("PRAGMA synchronous = NORMAL"),this.db.run("PRAGMA foreign_keys = ON"),this.initializeSchema(),this.ensureWorkerPortColumn(),this.ensurePromptTrackingColumns(),this.removeSessionSummariesUniqueConstraint(),this.addObservationHierarchicalFields(),this.makeObservationsTextNullable(),this.createUserPromptsTable(),this.ensureDiscoveryTokensColumn()}initializeSchema(){try{this.db.run(`
`+JSON.stringify(i,null,2):T=" "+this.formatData(i));let b="";if(n){let{sessionId:f,sdkSessionId:y,correlationId:l,...r}=n;Object.keys(r).length>0&&(b=` {${Object.entries(r).map(([O,S])=>`${O}=${S}`).join(", ")}}`)}let v=`[${a}] [${c}] [${_}] ${h}${t}${b}${T}`;e===3?console.error(v):console.log(v)}debug(e,s,t,n){this.log(0,e,s,t,n)}info(e,s,t,n){this.log(1,e,s,t,n)}warn(e,s,t,n){this.log(2,e,s,t,n)}error(e,s,t,n){this.log(3,e,s,t,n)}dataIn(e,s,t,n){this.info(e,`\u2192 ${s}`,t,n)}dataOut(e,s,t,n){this.info(e,`\u2190 ${s}`,t,n)}success(e,s,t,n){this.info(e,`\u2713 ${s}`,t,n)}failure(e,s,t,n){this.error(e,`\u2717 ${s}`,t,n)}timing(e,s,t,n){this.info(e,`\u23F1 ${s}`,n,{duration:`${t}ms`})}},U=new oe;var $=class{static DEFAULTS={CLAUDE_MEM_MODEL:"claude-haiku-4-5",CLAUDE_MEM_CONTEXT_OBSERVATIONS:"50",CLAUDE_MEM_WORKER_PORT:"37777",CLAUDE_MEM_WORKER_HOST:"127.0.0.1",CLAUDE_MEM_SKIP_TOOLS:"ListMcpResourcesTool,SlashCommand,Skill,TodoWrite,AskUserQuestion",CLAUDE_MEM_DATA_DIR:(0,Se.join)((0,ge.homedir)(),".claude-mem"),CLAUDE_MEM_LOG_LEVEL:"INFO",CLAUDE_MEM_PYTHON_VERSION:"3.13",CLAUDE_CODE_PATH:"",CLAUDE_MEM_CONTEXT_SHOW_READ_TOKENS:"true",CLAUDE_MEM_CONTEXT_SHOW_WORK_TOKENS:"true",CLAUDE_MEM_CONTEXT_SHOW_SAVINGS_AMOUNT:"true",CLAUDE_MEM_CONTEXT_SHOW_SAVINGS_PERCENT:"true",CLAUDE_MEM_CONTEXT_OBSERVATION_TYPES:me,CLAUDE_MEM_CONTEXT_OBSERVATION_CONCEPTS:Te,CLAUDE_MEM_CONTEXT_FULL_COUNT:"5",CLAUDE_MEM_CONTEXT_FULL_FIELD:"narrative",CLAUDE_MEM_CONTEXT_SESSION_COUNT:"10",CLAUDE_MEM_CONTEXT_SHOW_LAST_SUMMARY:"true",CLAUDE_MEM_CONTEXT_SHOW_LAST_MESSAGE:"false"};static getAllDefaults(){return{...this.DEFAULTS}}static get(e){return this.DEFAULTS[e]}static getInt(e){let s=this.get(e);return parseInt(s,10)}static getBool(e){return this.get(e)==="true"}static loadFromFile(e){if(!(0,B.existsSync)(e))return this.getAllDefaults();let s=(0,B.readFileSync)(e,"utf-8"),t=JSON.parse(s),n=t;if(t.env&&typeof t.env=="object"){n=t.env;try{(0,B.writeFileSync)(e,JSON.stringify(n,null,2),"utf-8"),U.info("SETTINGS","Migrated settings file from nested to flat schema",{settingsPath:e})}catch(a){U.warn("SETTINGS","Failed to auto-migrate settings file",{settingsPath:e},a)}}let i={...this.DEFAULTS};for(let a of Object.keys(this.DEFAULTS))n[a]!==void 0&&(i[a]=n[a]);return i}};var We={};function Xe(){return typeof __dirname<"u"?__dirname:(0,g.dirname)((0,fe.fileURLToPath)(We.url))}var _s=Xe(),C=$.get("CLAUDE_MEM_DATA_DIR"),ie=process.env.CLAUDE_CONFIG_DIR||(0,g.join)((0,he.homedir)(),".claude"),ps=(0,g.join)(C,"archives"),ls=(0,g.join)(C,"logs"),Es=(0,g.join)(C,"trash"),ms=(0,g.join)(C,"backups"),Ts=(0,g.join)(C,"settings.json"),Oe=(0,g.join)(C,"claude-mem.db"),Ss=(0,g.join)(C,"vector-db"),gs=(0,g.join)(ie,"settings.json"),hs=(0,g.join)(ie,"commands"),bs=(0,g.join)(ie,"CLAUDE.md");function Re(d){(0,be.mkdirSync)(d,{recursive:!0})}var J=class{db;constructor(){Re(C),this.db=new Ne.Database(Oe),this.db.run("PRAGMA journal_mode = WAL"),this.db.run("PRAGMA synchronous = NORMAL"),this.db.run("PRAGMA foreign_keys = ON"),this.initializeSchema(),this.ensureWorkerPortColumn(),this.ensurePromptTrackingColumns(),this.removeSessionSummariesUniqueConstraint(),this.addObservationHierarchicalFields(),this.makeObservationsTextNullable(),this.createUserPromptsTable(),this.ensureDiscoveryTokensColumn()}initializeSchema(){try{this.db.run(`
CREATE TABLE IF NOT EXISTS schema_versions (
id INTEGER PRIMARY KEY,
version INTEGER UNIQUE NOT NULL,
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
+245 -236
View File
@@ -1,272 +1,281 @@
#!/usr/bin/env node
/**
* Smart Install Script for claude-mem
*
* Features:
* - Detects execution context (cache vs marketplace directory)
* - Installs dependencies where the hooks actually run (cache directory)
* - Only runs npm install when necessary (version change or missing deps)
* - Caches installation state with version marker
* - Provides helpful Windows-specific error messages
* - Cross-platform compatible (pure Node.js)
* - Fast when already installed (just version check)
* Ensures Bun runtime and uv (Python package manager) are installed
* (auto-installs if missing) and handles dependency installation when needed.
*/
import { existsSync, readFileSync, writeFileSync } from 'fs';
import { execSync } from 'child_process';
import { join, dirname } from 'path';
import { execSync, spawnSync } from 'child_process';
import { join } from 'path';
import { homedir } from 'os';
import { fileURLToPath } from 'url';
// Determine the directory where THIS script is running from
// This could be either:
// 1. Cache: ~/.claude/plugins/cache/thedotmack/claude-mem/X.X.X/scripts/
// 2. Marketplace: ~/.claude/plugins/marketplaces/thedotmack/plugin/scripts/
const __dirname = dirname(fileURLToPath(import.meta.url));
const SCRIPT_ROOT = dirname(__dirname); // Parent of scripts/ directory
const ROOT = join(homedir(), '.claude', 'plugins', 'marketplaces', 'thedotmack');
const MARKER = join(ROOT, '.install-version');
const IS_WINDOWS = process.platform === 'win32';
// Detect if running from cache directory (has version number in path)
const CACHE_PATTERN = /[/\\]cache[/\\]thedotmack[/\\]claude-mem[/\\]\d+\.\d+\.\d+/;
const IS_RUNNING_FROM_CACHE = CACHE_PATTERN.test(__dirname);
// Set PLUGIN_ROOT based on where we're running
// If from cache, install dependencies IN the cache directory (where hooks run)
// If from marketplace, use marketplace directory
const PLUGIN_ROOT = IS_RUNNING_FROM_CACHE
? SCRIPT_ROOT // Cache directory (e.g., ~/.claude/plugins/cache/thedotmack/claude-mem/7.0.3/)
: join(homedir(), '.claude', 'plugins', 'marketplaces', 'thedotmack');
const PACKAGE_JSON_PATH = join(PLUGIN_ROOT, 'package.json');
const VERSION_MARKER_PATH = join(PLUGIN_ROOT, '.install-version');
const NODE_MODULES_PATH = join(PLUGIN_ROOT, 'node_modules');
// Colors for output
const colors = {
reset: '\x1b[0m',
bright: '\x1b[1m',
green: '\x1b[32m',
yellow: '\x1b[33m',
red: '\x1b[31m',
cyan: '\x1b[36m',
dim: '\x1b[2m',
};
function log(message, color = colors.reset) {
console.error(`${color}${message}${colors.reset}`);
/**
* Check if Bun is installed and accessible
*/
function isBunInstalled() {
try {
const result = spawnSync('bun', ['--version'], {
encoding: 'utf-8',
stdio: ['pipe', 'pipe', 'pipe'],
shell: IS_WINDOWS
});
return result.status === 0;
} catch {
return false;
}
}
function getPackageVersion() {
/**
* Get Bun version if installed
*/
function getBunVersion() {
try {
const packageJson = JSON.parse(readFileSync(PACKAGE_JSON_PATH, 'utf-8'));
return packageJson.version;
} catch (error) {
log(`⚠️ Failed to read package.json: ${error.message}`, colors.yellow);
const result = spawnSync('bun', ['--version'], {
encoding: 'utf-8',
stdio: ['pipe', 'pipe', 'pipe'],
shell: IS_WINDOWS
});
return result.status === 0 ? result.stdout.trim() : null;
} catch {
return null;
}
}
function getNodeVersion() {
return process.version; // e.g., "v22.21.1"
}
function getInstalledVersion() {
/**
* Check if uv is installed and accessible
*/
function isUvInstalled() {
try {
if (existsSync(VERSION_MARKER_PATH)) {
const content = readFileSync(VERSION_MARKER_PATH, 'utf-8').trim();
// Try parsing as JSON (new format)
try {
const marker = JSON.parse(content);
return {
packageVersion: marker.packageVersion,
nodeVersion: marker.nodeVersion,
installedAt: marker.installedAt
};
} catch {
// Fallback: old format (plain text version string)
return {
packageVersion: content,
nodeVersion: null, // Unknown
installedAt: null
};
}
}
} catch (error) {
// Marker doesn't exist or can't be read
const result = spawnSync('uv', ['--version'], {
encoding: 'utf-8',
stdio: ['pipe', 'pipe', 'pipe'],
shell: IS_WINDOWS
});
return result.status === 0;
} catch {
return false;
}
return null;
}
function setInstalledVersion(packageVersion, nodeVersion) {
/**
* Get uv version if installed
*/
function getUvVersion() {
try {
const marker = {
packageVersion,
nodeVersion,
installedAt: new Date().toISOString()
};
writeFileSync(VERSION_MARKER_PATH, JSON.stringify(marker, null, 2), 'utf-8');
} catch (error) {
log(`⚠️ Failed to write version marker: ${error.message}`, colors.yellow);
const result = spawnSync('uv', ['--version'], {
encoding: 'utf-8',
stdio: ['pipe', 'pipe', 'pipe'],
shell: IS_WINDOWS
});
return result.status === 0 ? result.stdout.trim() : null;
} catch {
return null;
}
}
function needsInstall() {
// Check if package.json exists (required for npm install)
if (!existsSync(PACKAGE_JSON_PATH)) {
log(`⚠️ No package.json found at ${PLUGIN_ROOT}`, colors.yellow);
return false; // Can't install without package.json
}
/**
* Install Bun automatically based on platform
*/
function installBun() {
console.error('🔧 Bun not found. Installing Bun runtime...');
// Check if node_modules exists
if (!existsSync(NODE_MODULES_PATH)) {
log('📦 Dependencies not found - first time setup', colors.cyan);
return true;
}
// Check version marker
const currentPackageVersion = getPackageVersion();
const currentNodeVersion = getNodeVersion();
const installed = getInstalledVersion();
if (!installed) {
log('📦 No version marker found - installing', colors.cyan);
return true;
}
// Check package version
if (currentPackageVersion !== installed.packageVersion) {
log(`📦 Version changed (${installed.packageVersion}${currentPackageVersion}) - updating`, colors.cyan);
return true;
}
// Check Node.js version
if (installed.nodeVersion && currentNodeVersion !== installed.nodeVersion) {
log(`📦 Node.js version changed (${installed.nodeVersion}${currentNodeVersion}) - rebuilding native modules`, colors.cyan);
return true;
}
// If old format (no nodeVersion), assume needs install
if (!installed.nodeVersion) {
log('📦 Old version marker format - updating', colors.cyan);
return true;
}
// All good - no install needed
log(`✓ Dependencies already installed (v${currentPackageVersion})`, colors.dim);
return false;
}
async function runNpmInstall() {
const isWindows = process.platform === 'win32';
log('', colors.cyan);
log(`🔨 Installing dependencies in ${IS_RUNNING_FROM_CACHE ? 'cache' : 'marketplace'}...`, colors.bright);
log(` ${PLUGIN_ROOT}`, colors.dim);
log('', colors.reset);
// Try normal install first, then retry with force if it fails
const strategies = [
{ command: 'npm install', label: 'normal' },
{ command: 'npm install --force', label: 'with force flag' },
];
let lastError = null;
for (const { command, label } of strategies) {
try {
log(`Attempting install ${label}...`, colors.dim);
// Run npm install silently
execSync(command, {
cwd: PLUGIN_ROOT,
stdio: 'pipe', // Silent output unless error
encoding: 'utf-8',
try {
if (IS_WINDOWS) {
// Windows: Use PowerShell installer
console.error(' Installing via PowerShell...');
execSync('powershell -c "irm bun.sh/install.ps1 | iex"', {
stdio: 'inherit',
shell: true
});
const packageVersion = getPackageVersion();
const nodeVersion = getNodeVersion();
setInstalledVersion(packageVersion, nodeVersion);
log('', colors.green);
log('✅ Dependencies installed successfully!', colors.bright);
log(` Package version: ${packageVersion}`, colors.dim);
log(` Node.js version: ${nodeVersion}`, colors.dim);
log('', colors.reset);
return true;
} catch (error) {
lastError = error;
// Continue to next strategy
}
}
// All strategies failed - show error
log('', colors.red);
log('❌ Installation failed after retrying!', colors.bright);
log('', colors.reset);
// Show generic error info with troubleshooting steps
if (lastError) {
if (lastError.stderr) {
log('Error output:', colors.dim);
log(lastError.stderr.toString(), colors.red);
} else if (lastError.message) {
log(lastError.message, colors.red);
}
log('', colors.yellow);
log('📋 Troubleshooting Steps:', colors.bright);
log('', colors.reset);
log('1. Check your internet connection', colors.yellow);
log('2. Try running: npm cache clean --force', colors.yellow);
log('3. Try running: npm install (in plugin directory)', colors.yellow);
log('4. Check npm version: npm --version (requires npm 7+)', colors.yellow);
log('5. Try updating npm: npm install -g npm@latest', colors.yellow);
log('', colors.reset);
}
return false;
}
async function main() {
try {
// Log execution context for debugging
if (IS_RUNNING_FROM_CACHE) {
log('📍 Running from cache directory', colors.dim);
} else {
log('📍 Running from marketplace directory', colors.dim);
// Unix/macOS: Use curl installer
console.error(' Installing via curl...');
execSync('curl -fsSL https://bun.sh/install | bash', {
stdio: 'inherit',
shell: true
});
}
// Check if we need to install dependencies
const installNeeded = needsInstall();
// Verify installation
if (isBunInstalled()) {
const version = getBunVersion();
console.error(`✅ Bun ${version} installed successfully`);
return true;
} else {
// Bun may be installed but not in PATH yet for this session
// Try common installation paths
const bunPaths = IS_WINDOWS
? [join(homedir(), '.bun', 'bin', 'bun.exe')]
: [join(homedir(), '.bun', 'bin', 'bun'), '/usr/local/bin/bun'];
if (installNeeded) {
// Run installation (now async)
const installSuccess = await runNpmInstall();
if (!installSuccess) {
log('', colors.red);
log('⚠️ Installation failed', colors.yellow);
log('', colors.reset);
process.exit(1);
for (const bunPath of bunPaths) {
if (existsSync(bunPath)) {
console.error(`✅ Bun installed at ${bunPath}`);
console.error('⚠️ Please restart your terminal or add Bun to PATH:');
if (IS_WINDOWS) {
console.error(` $env:Path += ";${join(homedir(), '.bun', 'bin')}"`);
} else {
console.error(` export PATH="$HOME/.bun/bin:$PATH"`);
}
return true;
}
}
throw new Error('Bun installation completed but binary not found');
}
// NOTE: Worker auto-start disabled in smart-install.js
// The context-hook.js calls ensureWorkerRunning() which handles worker startup
// This avoids potential process management conflicts during plugin initialization
log('✅ Installation complete', colors.green);
// Success - dependencies installed (if needed)
process.exit(0);
} catch (error) {
log(`❌ Unexpected error: ${error.message}`, colors.red);
log('', colors.reset);
process.exit(1);
console.error('❌ Failed to install Bun automatically');
console.error(' Please install manually:');
if (IS_WINDOWS) {
console.error(' - winget install Oven-sh.Bun');
console.error(' - Or: powershell -c "irm bun.sh/install.ps1 | iex"');
} else {
console.error(' - curl -fsSL https://bun.sh/install | bash');
console.error(' - Or: brew install oven-sh/bun/bun');
}
console.error(' Then restart your terminal and try again.');
throw error;
}
}
main();
/**
* Install uv automatically based on platform
*/
function installUv() {
console.error('🐍 Installing uv for Python/Chroma support...');
try {
if (IS_WINDOWS) {
// Windows: Use PowerShell installer
console.error(' Installing via PowerShell...');
execSync('powershell -ExecutionPolicy ByPass -c "irm https://astral.sh/uv/install.ps1 | iex"', {
stdio: 'inherit',
shell: true
});
} else {
// Unix/macOS: Use curl installer
console.error(' Installing via curl...');
execSync('curl -LsSf https://astral.sh/uv/install.sh | sh', {
stdio: 'inherit',
shell: true
});
}
// Verify installation
if (isUvInstalled()) {
const version = getUvVersion();
console.error(`✅ uv ${version} installed successfully`);
return true;
} else {
// uv may be installed but not in PATH yet for this session
// Try common installation paths
const uvPaths = IS_WINDOWS
? [join(homedir(), '.local', 'bin', 'uv.exe'), join(homedir(), '.cargo', 'bin', 'uv.exe')]
: [join(homedir(), '.local', 'bin', 'uv'), join(homedir(), '.cargo', 'bin', 'uv'), '/usr/local/bin/uv'];
for (const uvPath of uvPaths) {
if (existsSync(uvPath)) {
console.error(`✅ uv installed at ${uvPath}`);
console.error('⚠️ Please restart your terminal or add uv to PATH:');
if (IS_WINDOWS) {
console.error(` $env:Path += ";${join(homedir(), '.local', 'bin')}"`);
} else {
console.error(` export PATH="$HOME/.local/bin:$PATH"`);
}
return true;
}
}
throw new Error('uv installation completed but binary not found');
}
} catch (error) {
console.error('❌ Failed to install uv automatically');
console.error(' Please install manually:');
if (IS_WINDOWS) {
console.error(' - winget install astral-sh.uv');
console.error(' - Or: powershell -c "irm https://astral.sh/uv/install.ps1 | iex"');
} else {
console.error(' - curl -LsSf https://astral.sh/uv/install.sh | sh');
console.error(' - Or: brew install uv (macOS)');
}
console.error(' Then restart your terminal and try again.');
throw error;
}
}
/**
* Check if dependencies need to be installed
*/
function needsInstall() {
if (!existsSync(join(ROOT, 'node_modules'))) return true;
try {
const pkg = JSON.parse(readFileSync(join(ROOT, 'package.json'), 'utf-8'));
const marker = JSON.parse(readFileSync(MARKER, 'utf-8'));
return pkg.version !== marker.version || getBunVersion() !== marker.bun;
} catch {
return true;
}
}
/**
* Install dependencies using Bun
*/
function installDeps() {
console.error('📦 Installing dependencies with Bun...');
try {
execSync('bun install', { cwd: ROOT, stdio: 'inherit', shell: IS_WINDOWS });
} catch {
// Retry with force flag
execSync('bun install --force', { cwd: ROOT, stdio: 'inherit', shell: IS_WINDOWS });
}
// Write version marker
const pkg = JSON.parse(readFileSync(join(ROOT, 'package.json'), 'utf-8'));
writeFileSync(MARKER, JSON.stringify({
version: pkg.version,
bun: getBunVersion(),
uv: getUvVersion(),
installedAt: new Date().toISOString()
}));
}
// Main execution
try {
// Step 1: Ensure Bun is installed (REQUIRED)
if (!isBunInstalled()) {
installBun();
// Re-check after installation
if (!isBunInstalled()) {
console.error('❌ Bun is required but not available in PATH');
console.error(' Please restart your terminal after installation');
process.exit(1);
}
}
// Step 2: Ensure uv is installed (REQUIRED for vector search)
if (!isUvInstalled()) {
installUv();
// Re-check after installation
if (!isUvInstalled()) {
console.error('❌ uv is required but not available in PATH');
console.error(' Please restart your terminal after installation');
process.exit(1);
}
}
// Step 3: Install dependencies if needed
if (needsInstall()) {
installDeps();
console.error('✅ Dependencies installed');
}
} catch (e) {
console.error('❌ Installation failed:', e.message);
process.exit(1);
}
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
+61 -7
View File
@@ -24,18 +24,56 @@ function isBunInstalled() {
stdio: ['pipe', 'pipe', 'pipe'],
shell: IS_WINDOWS
});
return result.status === 0;
if (result.status === 0) return true;
} catch {
return false;
// PATH check failed, try common installation paths
}
// Check common installation paths (handles fresh installs before PATH reload)
const bunPaths = IS_WINDOWS
? [join(homedir(), '.bun', 'bin', 'bun.exe')]
: [join(homedir(), '.bun', 'bin', 'bun'), '/usr/local/bin/bun'];
return bunPaths.some(existsSync);
}
/**
* Get the Bun executable path (from PATH or common install locations)
*/
function getBunPath() {
// Try PATH first
try {
const result = spawnSync('bun', ['--version'], {
encoding: 'utf-8',
stdio: ['pipe', 'pipe', 'pipe'],
shell: IS_WINDOWS
});
if (result.status === 0) return 'bun';
} catch {
// Not in PATH
}
// Check common installation paths
const bunPaths = IS_WINDOWS
? [join(homedir(), '.bun', 'bin', 'bun.exe')]
: [join(homedir(), '.bun', 'bin', 'bun'), '/usr/local/bin/bun'];
for (const bunPath of bunPaths) {
if (existsSync(bunPath)) return bunPath;
}
return null;
}
/**
* Get Bun version if installed
*/
function getBunVersion() {
const bunPath = getBunPath();
if (!bunPath) return null;
try {
const result = spawnSync('bun', ['--version'], {
const result = spawnSync(bunPath, ['--version'], {
encoding: 'utf-8',
stdio: ['pipe', 'pipe', 'pipe'],
shell: IS_WINDOWS
@@ -56,10 +94,17 @@ function isUvInstalled() {
stdio: ['pipe', 'pipe', 'pipe'],
shell: IS_WINDOWS
});
return result.status === 0;
if (result.status === 0) return true;
} catch {
return false;
// PATH check failed, try common installation paths
}
// Check common installation paths (handles fresh installs before PATH reload)
const uvPaths = IS_WINDOWS
? [join(homedir(), '.local', 'bin', 'uv.exe'), join(homedir(), '.cargo', 'bin', 'uv.exe')]
: [join(homedir(), '.local', 'bin', 'uv'), join(homedir(), '.cargo', 'bin', 'uv'), '/usr/local/bin/uv'];
return uvPaths.some(existsSync);
}
/**
@@ -226,12 +271,21 @@ function needsInstall() {
* Install dependencies using Bun
*/
function installDeps() {
const bunPath = getBunPath();
if (!bunPath) {
throw new Error('Bun executable not found');
}
console.error('📦 Installing dependencies with Bun...');
// Quote path for Windows paths with spaces
const bunCmd = IS_WINDOWS && bunPath.includes(' ') ? `"${bunPath}"` : bunPath;
try {
execSync('bun install', { cwd: ROOT, stdio: 'inherit', shell: IS_WINDOWS });
execSync(`${bunCmd} install`, { cwd: ROOT, stdio: 'inherit', shell: IS_WINDOWS });
} catch {
// Retry with force flag
execSync('bun install --force', { cwd: ROOT, stdio: 'inherit', shell: IS_WINDOWS });
execSync(`${bunCmd} install --force`, { cwd: ROOT, stdio: 'inherit', shell: IS_WINDOWS });
}
// Write version marker
+20 -3
View File
@@ -35,7 +35,7 @@ import http from 'http';
import path from 'path';
import { Client } from '@modelcontextprotocol/sdk/client/index.js';
import { StdioClientTransport } from '@modelcontextprotocol/sdk/client/stdio.js';
import { getWorkerPort } from '../shared/worker-utils.js';
import { getWorkerPort, getWorkerHost } from '../shared/worker-utils.js';
import { logger } from '../utils/logger.js';
// Import composed domain services
@@ -132,6 +132,22 @@ export class WorkerService {
res.status(200).json({ status: 'ok' });
});
// Version endpoint - returns the worker's current version
this.app.get('/api/version', (_req, res) => {
try {
// Read version from marketplace package.json
const { homedir } = require('os');
const { readFileSync } = require('fs');
const marketplaceRoot = path.join(homedir(), '.claude', 'plugins', 'marketplaces', 'thedotmack');
const packageJsonPath = path.join(marketplaceRoot, 'package.json');
const packageJson = JSON.parse(readFileSync(packageJsonPath, 'utf-8'));
res.status(200).json({ version: packageJson.version });
} catch (error) {
logger.error('SYSTEM', 'Failed to read version', {}, error as Error);
res.status(500).json({ error: 'Failed to read version' });
}
});
// Admin endpoints for process management
this.app.post('/api/admin/restart', async (_req, res) => {
res.json({ status: 'restarting' });
@@ -163,12 +179,13 @@ export class WorkerService {
async start(): Promise<void> {
// Start HTTP server FIRST - make port available immediately
const port = getWorkerPort();
const host = getWorkerHost();
this.server = await new Promise<http.Server>((resolve, reject) => {
const srv = this.app.listen(port, () => resolve(srv));
const srv = this.app.listen(port, host, () => resolve(srv));
srv.on('error', reject);
});
logger.info('SYSTEM', 'Worker started', { port, pid: process.pid });
logger.info('SYSTEM', 'Worker started', { host, port, pid: process.pid });
// Do slow initialization in background (non-blocking)
this.initializeBackground().catch((error) => {
@@ -83,6 +83,20 @@ export class SettingsRoutes extends BaseRouteHandler {
}
}
// Validate CLAUDE_MEM_WORKER_HOST (IP address or 0.0.0.0)
if (req.body.CLAUDE_MEM_WORKER_HOST) {
const host = req.body.CLAUDE_MEM_WORKER_HOST;
// Allow localhost variants and valid IP patterns
const validHostPattern = /^(127\.0\.0\.1|0\.0\.0\.0|localhost|\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3})$/;
if (!validHostPattern.test(host)) {
res.status(400).json({
success: false,
error: 'CLAUDE_MEM_WORKER_HOST must be a valid IP address (e.g., 127.0.0.1, 0.0.0.0)'
});
return;
}
}
// Validate CLAUDE_MEM_LOG_LEVEL
if (req.body.CLAUDE_MEM_LOG_LEVEL) {
const validLevels = ['DEBUG', 'INFO', 'WARN', 'ERROR', 'SILENT'];
@@ -132,6 +146,7 @@ export class SettingsRoutes extends BaseRouteHandler {
'CLAUDE_MEM_MODEL',
'CLAUDE_MEM_CONTEXT_OBSERVATIONS',
'CLAUDE_MEM_WORKER_PORT',
'CLAUDE_MEM_WORKER_HOST',
// System Configuration
'CLAUDE_MEM_DATA_DIR',
'CLAUDE_MEM_LOG_LEVEL',
+2
View File
@@ -15,6 +15,7 @@ export interface SettingsDefaults {
CLAUDE_MEM_MODEL: string;
CLAUDE_MEM_CONTEXT_OBSERVATIONS: string;
CLAUDE_MEM_WORKER_PORT: string;
CLAUDE_MEM_WORKER_HOST: string;
CLAUDE_MEM_SKIP_TOOLS: string;
// System Configuration
CLAUDE_MEM_DATA_DIR: string;
@@ -46,6 +47,7 @@ export class SettingsDefaultsManager {
CLAUDE_MEM_MODEL: 'claude-haiku-4-5',
CLAUDE_MEM_CONTEXT_OBSERVATIONS: '50',
CLAUDE_MEM_WORKER_PORT: '37777',
CLAUDE_MEM_WORKER_HOST: '127.0.0.1',
CLAUDE_MEM_SKIP_TOOLS: 'ListMcpResourcesTool,SlashCommand,Skill,TodoWrite,AskUserQuestion',
// System Configuration
CLAUDE_MEM_DATA_DIR: join(homedir(), '.claude-mem'),
+3 -1
View File
@@ -4,8 +4,10 @@
*/
export function handleWorkerError(error: any): never {
if (error.cause?.code === 'ECONNREFUSED' ||
error.code === 'ConnectionRefused' || // Bun-specific error format
error.name === 'TimeoutError' ||
error.message?.includes('fetch failed')) {
error.message?.includes('fetch failed') ||
error.message?.includes('Unable to connect')) {
throw new Error(
"There's a problem with the worker. Try: npm run worker:restart"
);
+100 -7
View File
@@ -1,7 +1,7 @@
import path from "path";
import { homedir } from "os";
import { spawnSync } from "child_process";
import { existsSync, writeFileSync } from "fs";
import { existsSync, writeFileSync, readFileSync } from "fs";
import { logger } from "../utils/logger.js";
import { HOOK_TIMEOUTS, getTimeout } from "./hook-constants.js";
import { ProcessManager } from "../services/process/ProcessManager.js";
@@ -46,6 +46,16 @@ export function clearPortCache(): void {
cachedPort = null;
}
/**
* Get the worker host address
* Priority: ~/.claude-mem/settings.json > env var > default (127.0.0.1)
*/
export function getWorkerHost(): string {
const settingsPath = path.join(homedir(), '.claude-mem', 'settings.json');
const settings = SettingsDefaultsManager.loadFromFile(settingsPath);
return settings.CLAUDE_MEM_WORKER_HOST;
}
/**
* Check if worker is responsive by trying the health endpoint
*/
@@ -65,6 +75,74 @@ async function isWorkerHealthy(): Promise<boolean> {
}
}
/**
* Get the current plugin version from package.json
*/
function getPluginVersion(): string | null {
try {
const packageJsonPath = path.join(MARKETPLACE_ROOT, 'package.json');
const packageJson = JSON.parse(readFileSync(packageJsonPath, 'utf-8'));
return packageJson.version;
} catch (error) {
logger.debug('SYSTEM', 'Failed to read plugin version', {
error: error instanceof Error ? error.message : String(error)
});
return null;
}
}
/**
* Get the running worker's version from the API
*/
async function getWorkerVersion(): Promise<string | null> {
try {
const port = getWorkerPort();
const response = await fetch(`http://127.0.0.1:${port}/api/version`, {
signal: AbortSignal.timeout(HEALTH_CHECK_TIMEOUT_MS)
});
if (!response.ok) return null;
const data = await response.json() as { version: string };
return data.version;
} catch (error) {
logger.debug('SYSTEM', 'Failed to get worker version', {
error: error instanceof Error ? error.message : String(error)
});
return null;
}
}
/**
* Check if worker version matches plugin version
* If mismatch detected, restart the worker automatically
*/
async function ensureWorkerVersionMatches(): Promise<void> {
const pluginVersion = getPluginVersion();
const workerVersion = await getWorkerVersion();
if (!pluginVersion || !workerVersion) {
// Can't determine versions, skip check
return;
}
if (pluginVersion !== workerVersion) {
logger.info('SYSTEM', 'Worker version mismatch detected - restarting worker', {
pluginVersion,
workerVersion
});
// Restart the worker
await ProcessManager.restart(getWorkerPort());
// Give it a moment to start
await new Promise(resolve => setTimeout(resolve, 1000));
// Verify it's healthy
if (!await isWorkerHealthy()) {
logger.error('SYSTEM', 'Worker failed to restart after version mismatch');
}
}
}
/**
* Start the worker service using ProcessManager
* Handles both Unix (Bun) and Windows (compiled exe) platforms
@@ -103,22 +181,19 @@ async function startWorker(): Promise<boolean> {
/**
* Ensure worker service is running
* Checks health and auto-starts if not running
* Also ensures worker version matches plugin version
*/
export async function ensureWorkerRunning(): Promise<void> {
// Check if already healthy
if (await isWorkerHealthy()) {
// Worker is healthy, but check if version matches
await ensureWorkerVersionMatches();
return;
}
// Try to start the worker
const started = await startWorker();
// Final health check before throwing error
// Worker might be already running but was temporarily unresponsive
if (!started && await isWorkerHealthy()) {
return;
}
if (!started) {
const port = getWorkerPort();
throw new Error(
@@ -127,4 +202,22 @@ export async function ensureWorkerRunning(): Promise<void> {
`If already running, try: npm run worker:restart`
);
}
// Wait for worker to become responsive after starting
// Try up to 5 times with 500ms delays (2.5 seconds total)
for (let i = 0; i < 5; i++) {
await new Promise(resolve => setTimeout(resolve, 500));
if (await isWorkerHealthy()) {
await ensureWorkerVersionMatches();
return;
}
}
// Worker started but isn't responding
const port = getWorkerPort();
logger.error('SYSTEM', 'Worker started but not responding to health checks');
throw new Error(
`Worker service started but is not responding on port ${port}.\n\n` +
`Try: npm run worker:restart`
);
}
+18 -3
View File
@@ -24,6 +24,7 @@ export function Sidebar({ isOpen, settings, stats, isSaving, saveStatus, isConne
CLAUDE_MEM_MODEL: settings.CLAUDE_MEM_MODEL || DEFAULT_SETTINGS.CLAUDE_MEM_MODEL,
CLAUDE_MEM_CONTEXT_OBSERVATIONS: settings.CLAUDE_MEM_CONTEXT_OBSERVATIONS || DEFAULT_SETTINGS.CLAUDE_MEM_CONTEXT_OBSERVATIONS,
CLAUDE_MEM_WORKER_PORT: settings.CLAUDE_MEM_WORKER_PORT || DEFAULT_SETTINGS.CLAUDE_MEM_WORKER_PORT,
CLAUDE_MEM_WORKER_HOST: settings.CLAUDE_MEM_WORKER_HOST || DEFAULT_SETTINGS.CLAUDE_MEM_WORKER_HOST,
CLAUDE_MEM_CONTEXT_SHOW_READ_TOKENS: settings.CLAUDE_MEM_CONTEXT_SHOW_READ_TOKENS || DEFAULT_SETTINGS.CLAUDE_MEM_CONTEXT_SHOW_READ_TOKENS,
CLAUDE_MEM_CONTEXT_SHOW_WORK_TOKENS: settings.CLAUDE_MEM_CONTEXT_SHOW_WORK_TOKENS || DEFAULT_SETTINGS.CLAUDE_MEM_CONTEXT_SHOW_WORK_TOKENS,
CLAUDE_MEM_CONTEXT_SHOW_SAVINGS_AMOUNT: settings.CLAUDE_MEM_CONTEXT_SHOW_SAVINGS_AMOUNT || DEFAULT_SETTINGS.CLAUDE_MEM_CONTEXT_SHOW_SAVINGS_AMOUNT,
@@ -53,6 +54,7 @@ export function Sidebar({ isOpen, settings, stats, isSaving, saveStatus, isConne
CLAUDE_MEM_MODEL: settings.CLAUDE_MEM_MODEL || DEFAULT_SETTINGS.CLAUDE_MEM_MODEL,
CLAUDE_MEM_CONTEXT_OBSERVATIONS: settings.CLAUDE_MEM_CONTEXT_OBSERVATIONS || DEFAULT_SETTINGS.CLAUDE_MEM_CONTEXT_OBSERVATIONS,
CLAUDE_MEM_WORKER_PORT: settings.CLAUDE_MEM_WORKER_PORT || DEFAULT_SETTINGS.CLAUDE_MEM_WORKER_PORT,
CLAUDE_MEM_WORKER_HOST: settings.CLAUDE_MEM_WORKER_HOST || DEFAULT_SETTINGS.CLAUDE_MEM_WORKER_HOST,
CLAUDE_MEM_CONTEXT_SHOW_READ_TOKENS: settings.CLAUDE_MEM_CONTEXT_SHOW_READ_TOKENS || DEFAULT_SETTINGS.CLAUDE_MEM_CONTEXT_SHOW_READ_TOKENS,
CLAUDE_MEM_CONTEXT_SHOW_WORK_TOKENS: settings.CLAUDE_MEM_CONTEXT_SHOW_WORK_TOKENS || DEFAULT_SETTINGS.CLAUDE_MEM_CONTEXT_SHOW_WORK_TOKENS,
CLAUDE_MEM_CONTEXT_SHOW_SAVINGS_AMOUNT: settings.CLAUDE_MEM_CONTEXT_SHOW_SAVINGS_AMOUNT || DEFAULT_SETTINGS.CLAUDE_MEM_CONTEXT_SHOW_SAVINGS_AMOUNT,
@@ -156,7 +158,7 @@ export function Sidebar({ isOpen, settings, stats, isSaving, saveStatus, isConne
title="Join our Discord community"
>
<svg width="14" height="14" viewBox="0 0 24 24" fill="currentColor" style={{ marginRight: '6px' }}>
<path d="M20.317 4.37a19.791 19.791 0 0 0-4.885-1.515a.074.074 0 0 0-.079.037c-.21.375-.444.864-.608 1.25a18.27 18.27 0 0 0-5.487 0a12.64 12.64 0 0 0-.617-1.25a.077.077 0 0 0-.079-.037A19.736 19.736 0 0 0 3.677 4.37a.07.07 0 0 0-.032.027C.533 9.046-.32 13.58.099 18.057a.082.082 0 0 0 .031.057a19.9 19.9 0 0 0 5.993 3.03a.078.078 0 0 0 .084-.028a14.09 14.09 0 0 0 1.226-1.994a.076.076 0 0 0-.041-.106a13.107 13.107 0 0 1-1.872-.892a.077.077 0 0 1-.008-.128a10.2 10.2 0 0 0 .372-.292a.074.074 0 0 1 .077-.01c3.928 1.793 8.18 1.793 12.062 0a.074.074 0 0 1 .078.01c.12.098.246.198.373.292a.077.077 0 0 1-.006.127a12.299 12.299 0 0 1-1.873.892a.077.077 0 0 0-.041.107c.36.698.772 1.362 1.225 1.993a.076.076 0 0 0 .084.028a19.839 19.839 0 0 0 6.002-3.03a.077.077 0 0 0 .032-.054c.5-5.177-.838-9.674-3.549-13.66a.061.061 0 0 0-.031-.03zM8.02 15.33c-1.183 0-2.157-1.085-2.157-2.419c0-1.333.956-2.419 2.157-2.419c1.21 0 2.176 1.096 2.157 2.42c0 1.333-.956 2.418-2.157 2.418zm7.975 0c-1.183 0-2.157-1.085-2.157-2.419c0-1.333.955-2.419 2.157-2.419c1.21 0 2.176 1.096 2.157 2.42c0 1.333-.946 2.418-2.157 2.418z"/>
<path d="M20.317 4.37a19.791 19.791 0 0 0-4.885-1.515a.074.074 0 0 0-.079.037c-.21.375-.444.864-.608 1.25a18.27 18.27 0 0 0-5.487 0a12.64 12.64 0 0 0-.617-1.25a.077.077 0 0 0-.079-.037A19.736 19.736 0 0 0 3.677 4.37a.07.07 0 0 0-.032.027C.533 9.046-.32 13.58.099 18.057a.082.082 0 0 0 .031.057a19.9 19.9 0 0 0 5.993 3.03a.078.078 0 0 0 .084-.028a14.09 14.09 0 0 0 1.226-1.994a.076.076 0 0 0-.041-.106a13.107 13.107 0 0 1-1.872-.892a.077.077 0 0 1-.008-.128a10.2 10.2 0 0 0 .372-.292a.074.074 0 0 1 .077-.01c3.928 1.793 8.18 1.793 12.062 0a.074.074 0 0 1 .078.01c.12.098.246.198.373.292a.077.077 0 0 1-.006.127a12.299 12.299 0 0 1-1.873.892a.077.077 0 0 0-.041.107c.36.698.772 1.362 1.225 1.993a.076.076 0 0 0 .084.028a19.839 19.839 0 0 0 6.002-3.03a.077.077 0 0 0 .032-.054c.5-5.177-.838-9.674-3.549-13.66a.061.061 0 0 0-.031-.03zM8.02 15.33c-1.183 0-2.157-1.085-2.157-2.419c0-1.333.956-2.419 2.157-2.419c1.21 0 2.176 1.096 2.157 2.42c0 1.333-.956 2.418-2.157 2.418zm7.975 0c-1.183 0-2.157-1.085-2.157-2.419c0-1.333.955-2.419 2.157-2.419c1.21 0 2.176 1.096 2.157 2.42c0 1.333-.946 2.418-2.157 2.418z" />
</svg>
<span>Community</span>
</a>
@@ -181,7 +183,7 @@ export function Sidebar({ isOpen, settings, stats, isSaving, saveStatus, isConne
className="icon-link"
>
<svg width="16" height="16" viewBox="0 0 24 24" fill="currentColor">
<path d="M12 0c-6.626 0-12 5.373-12 12 0 5.302 3.438 9.8 8.207 11.387.599.111.793-.261.793-.577v-2.234c-3.338.726-4.033-1.416-4.033-1.416-.546-1.387-1.333-1.756-1.333-1.756-1.089-.745.083-.729.083-.729 1.205.084 1.839 1.237 1.839 1.237 1.07 1.834 2.807 1.304 3.492.997.107-.775.418-1.305.762-1.604-2.665-.305-5.467-1.334-5.467-5.931 0-1.311.469-2.381 1.236-3.221-.124-.303-.535-1.524.117-3.176 0 0 1.008-.322 3.301 1.23.957-.266 1.983-.399 3.003-.404 1.02.005 2.047.138 3.006.404 2.291-1.552 3.297-1.23 3.297-1.23.653 1.653.242 2.874.118 3.176.77.84 1.235 1.911 1.235 3.221 0 4.609-2.807 5.624-5.479 5.921.43.372.823 1.102.823 2.222v3.293c0 .319.192.694.801.576 4.765-1.589 8.199-6.086 8.199-11.386 0-6.627-5.373-12-12-12z"/>
<path d="M12 0c-6.626 0-12 5.373-12 12 0 5.302 3.438 9.8 8.207 11.387.599.111.793-.261.793-.577v-2.234c-3.338.726-4.033-1.416-4.033-1.416-.546-1.387-1.333-1.756-1.333-1.756-1.089-.745.083-.729.083-.729 1.205.084 1.839 1.237 1.839 1.237 1.07 1.834 2.807 1.304 3.492.997.107-.775.418-1.305.762-1.604-2.665-.305-5.467-1.334-5.467-5.931 0-1.311.469-2.381 1.236-3.221-.124-.303-.535-1.524.117-3.176 0 0 1.008-.322 3.301 1.23.957-.266 1.983-.399 3.003-.404 1.02.005 2.047.138 3.006.404 2.291-1.552 3.297-1.23 3.297-1.23.653 1.653.242 2.874.118 3.176.77.84 1.235 1.911 1.235 3.221 0 4.609-2.807 5.624-5.479 5.921.43.372.823 1.102.823 2.222v3.293c0 .319.192.694.801.576 4.765-1.589 8.199-6.086 8.199-11.386 0-6.627-5.373-12-12-12z" />
</svg>
</a>
<a
@@ -192,7 +194,7 @@ export function Sidebar({ isOpen, settings, stats, isSaving, saveStatus, isConne
className="icon-link"
>
<svg width="16" height="16" viewBox="0 0 24 24" fill="currentColor">
<path d="M18.244 2.25h3.308l-7.227 8.26 8.502 11.24H16.17l-5.214-6.817L4.99 21.75H1.68l7.73-8.835L1.254 2.25H8.08l4.713 6.231zm-1.161 17.52h1.833L7.084 4.126H5.117z"/>
<path d="M18.244 2.25h3.308l-7.227 8.26 8.502 11.24H16.17l-5.214-6.817L4.99 21.75H1.68l7.73-8.835L1.254 2.25H8.08l4.713 6.231zm-1.161 17.52h1.833L7.084 4.126H5.117z" />
</svg>
</a>
</div>
@@ -257,6 +259,19 @@ export function Sidebar({ isOpen, settings, stats, isSaving, saveStatus, isConne
onChange={e => updateFormState('CLAUDE_MEM_WORKER_PORT', e.target.value)}
/>
</div>
<div className="form-group">
<label htmlFor="workerHost">CLAUDE_MEM_WORKER_HOST</label>
<div className="setting-description">
IP address to bind the worker service. Use 127.0.0.1 (default) for local-only access, or 0.0.0.0 for remote access on servers.
</div>
<input
type="text"
id="workerHost"
value={formState.CLAUDE_MEM_WORKER_HOST}
onChange={e => updateFormState('CLAUDE_MEM_WORKER_HOST', e.target.value)}
placeholder="127.0.0.1"
/>
</div>
{/* Token Economics Display */}
<div className="form-group">
+1
View File
@@ -6,6 +6,7 @@ export const DEFAULT_SETTINGS = {
CLAUDE_MEM_MODEL: 'claude-haiku-4-5',
CLAUDE_MEM_CONTEXT_OBSERVATIONS: '50',
CLAUDE_MEM_WORKER_PORT: '37777',
CLAUDE_MEM_WORKER_HOST: '127.0.0.1',
// Token Economics (all true for backwards compatibility)
CLAUDE_MEM_CONTEXT_SHOW_READ_TOKENS: 'true',
+1
View File
@@ -18,6 +18,7 @@ export function useSettings() {
CLAUDE_MEM_MODEL: data.CLAUDE_MEM_MODEL || DEFAULT_SETTINGS.CLAUDE_MEM_MODEL,
CLAUDE_MEM_CONTEXT_OBSERVATIONS: data.CLAUDE_MEM_CONTEXT_OBSERVATIONS || DEFAULT_SETTINGS.CLAUDE_MEM_CONTEXT_OBSERVATIONS,
CLAUDE_MEM_WORKER_PORT: data.CLAUDE_MEM_WORKER_PORT || DEFAULT_SETTINGS.CLAUDE_MEM_WORKER_PORT,
CLAUDE_MEM_WORKER_HOST: data.CLAUDE_MEM_WORKER_HOST || DEFAULT_SETTINGS.CLAUDE_MEM_WORKER_HOST,
// Token Economics Display
CLAUDE_MEM_CONTEXT_SHOW_READ_TOKENS: data.CLAUDE_MEM_CONTEXT_SHOW_READ_TOKENS || DEFAULT_SETTINGS.CLAUDE_MEM_CONTEXT_SHOW_READ_TOKENS,
+1
View File
@@ -58,6 +58,7 @@ export interface Settings {
CLAUDE_MEM_MODEL: string;
CLAUDE_MEM_CONTEXT_OBSERVATIONS: string;
CLAUDE_MEM_WORKER_PORT: string;
CLAUDE_MEM_WORKER_HOST: string;
// Token Economics Display
CLAUDE_MEM_CONTEXT_SHOW_READ_TOKENS?: string;