Compare commits

...

18 Commits

Author SHA1 Message Date
Alex Newman 12c2ecce06 chore: bump version to 7.1.6
Improved error messages with platform-specific worker restart instructions

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

Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
2025-12-13 17:08:16 -05:00
Alex Newman bb0508d639 Refactor error handling to use platform-specific worker restart instructions
- Updated multiple hooks (context-hook, new-hook, save-hook, summary-hook, user-message-hook) to throw errors using `getWorkerRestartInstructions` for improved user guidance on worker connection issues.
- Enhanced `handleWorkerError` function to utilize the new error message generator for consistent error reporting.
- Modified `ensureWorkerRunning` function to provide detailed instructions based on the worker's state, including port information.
- Introduced `getWorkerRestartInstructions` utility in `error-messages.ts` to generate platform-aware error messages for worker failures.
2025-12-13 17:06:45 -05:00
Alex Newman f00ef33f86 Merge remote-tracking branch 'refs/remotes/origin/main' 2025-12-13 16:54:24 -05:00
Alex Newman c270bd3177 chore: add FUNDING.yml to support GitHub Sponsors 2025-12-13 16:53:00 -05:00
Alex Newman 0836a97845 Add GitHub Actions workflow to summarize new issues (#278) 2025-12-13 16:19:18 -05:00
Alex Newman 19e285a209 docs: regenerate CHANGELOG from GitHub releases 2025-12-13 15:55:46 -05:00
Alex Newman ba877214c1 chore: bump version to 7.1.5
🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
2025-12-13 15:54:57 -05:00
Justin Kowarsch 3d4baefac2 fix: Use getWorkerHost() instead of hardcoded localhost in MCP server (#276)
On Windows systems, `localhost` resolves to IPv6 (::1) while the worker
binds to IPv4 (127.0.0.1), causing MCP tool connections to fail.

This change uses the existing getWorkerHost() function which correctly
returns the configured host address (defaulting to 127.0.0.1).

Fixes connection failures on Windows where localhost prefers IPv6.

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

Co-authored-by: Claude Opus 4.5 <noreply@anthropic.com>
2025-12-13 15:54:03 -05:00
Alex Newman 453b7857b8 docs: regenerate CHANGELOG from GitHub releases 2025-12-13 15:37:11 -05:00
Alex Newman c28417af00 chore: bump version to 7.1.4
🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
2025-12-13 15:36:21 -05:00
Jonas Hanisch 2f08db3c01 fix: add npm fallback when bun install fails with alias packages (#265)
* fix: add npm fallback when bun install fails with alias packages

Bun has issues resolving npm alias packages (e.g., string-width-cjs,
strip-ansi-cjs, wrap-ansi-cjs) that are defined in package-lock.json.
When bun fails with 404 errors for these packages, we now fall back
to npm which handles aliases correctly.

This fixes the installation failure that many users are experiencing
where bun install fails with:
  error: GET https://registry.npmjs.org/string-width-cjs/-/string-width-cjs-4.2.3.tgz - 404

The fallback is transparent to users - they will see a warning message
and the installation will continue with npm.

Fixes #262
Related: #261, #253

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

Co-Authored-By: Claude <noreply@anthropic.com>

* fix: preserve original code style (single quotes)

---------

Co-authored-by: Jonas Hanisch <jhanisch@matero.de>
Co-authored-by: Claude <noreply@anthropic.com>
2025-12-13 15:34:13 -05:00
Alex Newman 672cb5d203 docs: regenerate CHANGELOG from GitHub releases
🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
2025-12-12 23:34:55 -05:00
Alex Newman cbe492dde3 chore: bump version to 7.1.3
🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
2025-12-12 23:33:53 -05:00
Alex Newman 0fb6f3cf4e refactor: streamline Bun and uv installation checks and paths 2025-12-12 23:29:42 -05:00
Alex Newman 5cd68f4a96 build: sync plugin build artifacts for v7.1.2
Updated built plugin files with latest changes including localhost
binding security improvements and enhanced runtime detection.

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

Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
2025-12-12 23:03:27 -05:00
Alex Newman 10a8598aac docs: regenerate CHANGELOG from GitHub releases 2025-12-12 22:51:37 -05:00
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
28 changed files with 510 additions and 315 deletions
+1 -1
View File
@@ -10,7 +10,7 @@
"plugins": [
{
"name": "claude-mem",
"version": "7.1.1",
"version": "7.1.6",
"source": "./plugin",
"description": "Persistent memory system for Claude Code - context compression across sessions"
}
+1
View File
@@ -0,0 +1 @@
github: [thedotmack]
+34
View File
@@ -0,0 +1,34 @@
name: Summarize new issues
on:
issues:
types: [opened]
jobs:
summary:
runs-on: ubuntu-latest
permissions:
issues: write
models: read
contents: read
steps:
- name: Checkout repository
uses: actions/checkout@v4
- name: Run AI inference
id: inference
uses: actions/ai-inference@v1
with:
prompt: |
Summarize the following GitHub issue in one paragraph:
Title: ${{ github.event.issue.title }}
Body: ${{ github.event.issue.body }}
- name: Comment with AI summary
run: |
gh issue comment $ISSUE_NUMBER --body '${{ steps.inference.outputs.response }}'
env:
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
ISSUE_NUMBER: ${{ github.event.issue.number }}
RESPONSE: ${{ steps.inference.outputs.response }}
+80 -80
View File
@@ -4,93 +4,93 @@ 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-12
## [7.1.5] - 2025-12-13
### Fixed
## What's Changed
**CRITICAL: Windows 11 Bun Auto-Install Broken**
* fix: Use getWorkerHost() instead of hardcoded localhost in MCP server (#276)
- **Chicken-and-Egg Problem**: v7.1.0 hooks called `bun smart-install.js`, but if Bun wasn't installed, the command failed with "bun is not recognized" before smart-install.js could run to install Bun
- **Root Cause**: v7.1.0 migration removed Bun/uv auto-installation logic from smart-install.js
- **Solution**:
- Changed SessionStart hook to use `node` (always available) for smart-install.js
- Restored Bun auto-installation logic: isBunInstalled(), installBun()
- Restored uv auto-installation for Chroma support
- Fresh Windows installations now work correctly
### Bug Fix
Fixes Windows IPv6 issue where `localhost` resolves to `::1` (IPv6) but worker binds to `127.0.0.1` (IPv4), causing MCP tool connections to fail.
**Path Quoting for Windows Usernames with Spaces**
**Full Changelog**: https://github.com/thedotmack/claude-mem/compare/v7.1.4...v7.1.5
- Fixed `hooks.json` to quote all `${CLAUDE_PLUGIN_ROOT}` paths
- Prevents SyntaxError for Windows users with spaces in usernames (e.g., "C:\Users\John Doe\...")
## [7.1.4] - 2025-12-13
### Added
## What's Changed
**Automatic Worker Restart on Version Updates**
* fix: add npm fallback when bun install fails with alias packages (#265)
- Added `/api/version` endpoint to worker service
- Added version checking in `ensureWorkerRunning()`:
- Compares plugin version with running worker version
- Automatically restarts worker when version mismatch detected
- Logs version mismatch for debugging
- **Impact**: Users no longer need to manually restart worker after upgrades
- **Critical for**: All future releases - eliminates connection errors from running old worker code
**Full Changelog**: https://github.com/thedotmack/claude-mem/compare/v7.1.3...v7.1.4
### Notes
## [7.1.3] - 2025-12-13
- No manual actions required - worker auto-restarts on next session start
- Fresh installs on Windows 11 now work out-of-box
- All hooks now run with correct runtime (node for install, bun for execution)
## Bug Fixes
### Smart Install Script Refactoring
Refactored the smart-install.js script to improve code quality and maintainability:
- Extracted common installation paths as top-level constants (BUN_COMMON_PATHS, UV_COMMON_PATHS)
- Simplified installation check functions to delegate to dedicated path-finding helpers
- Streamlined installation verification logic with clearer error messages
- Removed redundant post-installation verification checks
- Improved error propagation by removing unnecessary retry logic
This refactoring reduces code duplication and makes the installation process more maintainable while preserving the same functionality for detecting Bun and uv binaries across platforms.
## [7.1.2] - 2025-12-13
## 🐛 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
---
**Full Changelog**: https://github.com/thedotmack/claude-mem/compare/v7.1.1...v7.1.2
## [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
## Security Fix: Localhost-Only Binding
**BREAKING CHANGE**: Worker service now binds to `127.0.0.1` (localhost) by default instead of `0.0.0.0` (all interfaces).
### Security Issue Fixed
The worker service was previously binding to `0.0.0.0:37777` by default, exposing all API endpoints to the network without authentication. This posed security risks:
- Unauthorized access to memory data from any network device
- Potential data injection into the database
- Settings modification from remote devices
- Full access to Web Viewer UI from the network
### Solution
Default worker binding changed to `127.0.0.1` (localhost-only), with a new configurable setting `CLAUDE_MEM_WORKER_HOST` for users who need remote access.
### Changes
- **Core**: Added `CLAUDE_MEM_WORKER_HOST` setting with default value `127.0.0.1`
- **Worker**: Modified `worker-service.ts` to bind to configured host address
- **API**: Added host validation in `SettingsRoutes.ts` (IP address format check)
- **UI**: Added host configuration field in Settings panel
- **Docs**: Updated README.md and CLAUDE.md with new setting
### Configuration
**Default (secure):** localhost only
```bash
CLAUDE_MEM_WORKER_HOST=127.0.0.1
```
**Remote access (server deployments):**
```bash
CLAUDE_MEM_WORKER_HOST=0.0.0.0
```
Can be configured via:
- `~/.claude-mem/settings.json`
- Web Viewer UI Settings panel
### Migration
**Automatic**: Existing installations will use `127.0.0.1` on next worker restart. If you need remote access, set `CLAUDE_MEM_WORKER_HOST=0.0.0.0` in `~/.claude-mem/settings.json`.
### Contributors
Thanks to @7Sageer for identifying and fixing this security issue!
## Major Architectural Migration
This release completely replaces PM2 with native Bun-based process management and migrates from better-sqlite3 to bun:sqlite.
@@ -1991,12 +1991,12 @@ None (patch version)
## [4.3.0] - 2025-10-25
## What's Changed
* feat: Enhanced context hook with session observations and cross-platform improvements by @thedotmack in https://github.com/thedotmack/claude-mem/pull/25
## New Contributors
* @thedotmack made their first contribution in https://github.com/thedotmack/claude-mem/pull/25
## What's Changed
* feat: Enhanced context hook with session observations and cross-platform improvements by @thedotmack in https://github.com/thedotmack/claude-mem/pull/25
## New Contributors
* @thedotmack made their first contribution in https://github.com/thedotmack/claude-mem/pull/25
**Full Changelog**: https://github.com/thedotmack/claude-mem/compare/v4.2.11...v4.3.0
## [4.2.10] - 2025-10-25
+1 -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.1
**Current Version**: 7.1.6
## Architecture
+1 -1
View File
@@ -1,6 +1,6 @@
{
"name": "claude-mem",
"version": "7.1.1",
"version": "7.1.6",
"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.1",
"version": "7.1.6",
"description": "Persistent memory system for Claude Code - seamlessly preserve context across sessions",
"author": {
"name": "Alex Newman"
+1 -1
View File
@@ -1,6 +1,6 @@
{
"name": "claude-mem-plugin",
"version": "7.1.1",
"version": "7.1.6",
"private": true,
"description": "Runtime dependencies for claude-mem bundled hooks",
"type": "module",
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
+85 -9
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);
}
/**
@@ -223,15 +268,46 @@ function needsInstall() {
}
/**
* Install dependencies using Bun
* Install dependencies using Bun with npm fallback
*
* Bun has issues with npm alias packages (e.g., string-width-cjs, strip-ansi-cjs)
* that are defined in package-lock.json. When bun fails with 404 errors for these
* packages, we fall back to npm which handles aliases correctly.
*/
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;
let bunSucceeded = false;
try {
execSync('bun install', { cwd: ROOT, stdio: 'inherit', shell: IS_WINDOWS });
execSync(`${bunCmd} install`, { cwd: ROOT, stdio: 'inherit', shell: IS_WINDOWS });
bunSucceeded = true;
} catch {
// Retry with force flag
execSync('bun install --force', { cwd: ROOT, stdio: 'inherit', shell: IS_WINDOWS });
// First attempt failed, try with force flag
try {
execSync(`${bunCmd} install --force`, { cwd: ROOT, stdio: 'inherit', shell: IS_WINDOWS });
bunSucceeded = true;
} catch {
// Bun failed completely, will try npm fallback
}
}
// Fallback to npm if bun failed (handles npm alias packages correctly)
if (!bunSucceeded) {
console.error('⚠️ Bun install failed, falling back to npm...');
console.error(' (This can happen with npm alias packages like *-cjs)');
try {
execSync('npm install', { cwd: ROOT, stdio: 'inherit', shell: IS_WINDOWS });
} catch (npmError) {
throw new Error('Both bun and npm install failed: ' + npmError.message);
}
}
// Write version marker
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
+77 -99
View File
@@ -14,28 +14,51 @@ const ROOT = join(homedir(), '.claude', 'plugins', 'marketplaces', 'thedotmack')
const MARKER = join(ROOT, '.install-version');
const IS_WINDOWS = process.platform === 'win32';
// Common installation paths (handles fresh installs before PATH reload)
const BUN_COMMON_PATHS = IS_WINDOWS
? [join(homedir(), '.bun', 'bin', 'bun.exe')]
: [join(homedir(), '.bun', 'bin', 'bun'), '/usr/local/bin/bun'];
const UV_COMMON_PATHS = 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'];
/**
* Check if Bun is installed and accessible
* Get the Bun executable path (from PATH or common install locations)
*/
function isBunInstalled() {
function getBunPath() {
// Try PATH first
try {
const result = spawnSync('bun', ['--version'], {
encoding: 'utf-8',
stdio: ['pipe', 'pipe', 'pipe'],
shell: IS_WINDOWS
});
return result.status === 0;
if (result.status === 0) return 'bun';
} catch {
return false;
// Not in PATH
}
// Check common installation paths
return BUN_COMMON_PATHS.find(existsSync) || null;
}
/**
* Check if Bun is installed and accessible
*/
function isBunInstalled() {
return getBunPath() !== 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
@@ -47,27 +70,41 @@ function getBunVersion() {
}
/**
* Check if uv is installed and accessible
* Get the uv executable path (from PATH or common install locations)
*/
function isUvInstalled() {
function getUvPath() {
// Try PATH first
try {
const result = spawnSync('uv', ['--version'], {
encoding: 'utf-8',
stdio: ['pipe', 'pipe', 'pipe'],
shell: IS_WINDOWS
});
return result.status === 0;
if (result.status === 0) return 'uv';
} catch {
return false;
// Not in PATH
}
// Check common installation paths
return UV_COMMON_PATHS.find(existsSync) || null;
}
/**
* Check if uv is installed and accessible
*/
function isUvInstalled() {
return getUvPath() !== null;
}
/**
* Get uv version if installed
*/
function getUvVersion() {
const uvPath = getUvPath();
if (!uvPath) return null;
try {
const result = spawnSync('uv', ['--version'], {
const result = spawnSync(uvPath, ['--version'], {
encoding: 'utf-8',
stdio: ['pipe', 'pipe', 'pipe'],
shell: IS_WINDOWS
@@ -86,14 +123,12 @@ function installBun() {
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
});
} else {
// Unix/macOS: Use curl installer
console.error(' Installing via curl...');
execSync('curl -fsSL https://bun.sh/install | bash', {
stdio: 'inherit',
@@ -101,35 +136,17 @@ function installBun() {
});
}
// 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'];
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');
if (!isBunInstalled()) {
throw new Error(
'Bun installation completed but binary not found. ' +
'Please restart your terminal and try again.'
);
}
const version = getBunVersion();
console.error(`✅ Bun ${version} installed successfully`);
} catch (error) {
console.error('❌ Failed to install Bun automatically');
console.error('❌ Failed to install Bun');
console.error(' Please install manually:');
if (IS_WINDOWS) {
console.error(' - winget install Oven-sh.Bun');
@@ -151,14 +168,12 @@ function installUv() {
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',
@@ -166,35 +181,17 @@ function installUv() {
});
}
// 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');
if (!isUvInstalled()) {
throw new Error(
'uv installation completed but binary not found. ' +
'Please restart your terminal and try again.'
);
}
const version = getUvVersion();
console.error(`✅ uv ${version} installed successfully`);
} catch (error) {
console.error('❌ Failed to install uv automatically');
console.error('❌ Failed to install uv');
console.error(' Please install manually:');
if (IS_WINDOWS) {
console.error(' - winget install astral-sh.uv');
@@ -226,14 +223,18 @@ function needsInstall() {
* 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 });
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;
execSync(`${bunCmd} install`, { 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({
@@ -246,31 +247,8 @@ function installDeps() {
// 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 (!isBunInstalled()) installBun();
if (!isUvInstalled()) installUv();
if (needsInstall()) {
installDeps();
console.error('✅ Dependencies installed');
+2 -1
View File
@@ -11,6 +11,7 @@ import { stdin } from "process";
import { ensureWorkerRunning, getWorkerPort } from "../shared/worker-utils.js";
import { HOOK_TIMEOUTS } from "../shared/hook-constants.js";
import { handleWorkerError } from "../shared/hook-error-handler.js";
import { getWorkerRestartInstructions } from "../utils/error-messages.js";
export interface SessionStartInput {
session_id: string;
@@ -34,7 +35,7 @@ async function contextHook(input?: SessionStartInput): Promise<string> {
if (!response.ok) {
const errorText = await response.text();
throw new Error(`Failed to fetch context: ${response.status} ${errorText}`);
throw new Error(getWorkerRestartInstructions({ includeSkillFallback: true }));
}
const result = await response.text();
+3 -2
View File
@@ -4,6 +4,7 @@ import { createHookResponse } from './hook-response.js';
import { ensureWorkerRunning, getWorkerPort } from '../shared/worker-utils.js';
import { happy_path_error__with_fallback } from '../utils/silent-debug.js';
import { handleWorkerError } from '../shared/hook-error-handler.js';
import { getWorkerRestartInstructions } from '../utils/error-messages.js';
export interface UserPromptSubmitInput {
session_id: string;
@@ -52,7 +53,7 @@ async function newHook(input?: UserPromptSubmitInput): Promise<void> {
if (!initResponse.ok) {
const errorText = await initResponse.text();
throw new Error(`Failed to initialize session: ${initResponse.status} ${errorText}`);
throw new Error(getWorkerRestartInstructions({ includeSkillFallback: true }));
}
const initResult = await initResponse.json();
@@ -86,7 +87,7 @@ async function newHook(input?: UserPromptSubmitInput): Promise<void> {
if (!response.ok) {
const errorText = await response.text();
throw new Error(`Failed to start SDK agent: ${response.status} ${errorText}`);
throw new Error(getWorkerRestartInstructions({ includeSkillFallback: true }));
}
} catch (error: any) {
handleWorkerError(error);
+2 -1
View File
@@ -13,6 +13,7 @@ import { ensureWorkerRunning, getWorkerPort } from '../shared/worker-utils.js';
import { HOOK_TIMEOUTS } from '../shared/hook-constants.js';
import { happy_path_error__with_fallback } from '../utils/silent-debug.js';
import { handleWorkerError } from '../shared/hook-error-handler.js';
import { getWorkerRestartInstructions } from '../utils/error-messages.js';
export interface PostToolUseInput {
session_id: string;
@@ -67,7 +68,7 @@ async function saveHook(input?: PostToolUseInput): Promise<void> {
logger.failure('HOOK', 'Failed to send observation', {
status: response.status
}, errorText);
throw new Error(`Failed to send observation to worker: ${response.status} ${errorText}`);
throw new Error(getWorkerRestartInstructions({ includeSkillFallback: true }));
}
logger.debug('HOOK', 'Observation sent successfully', { toolName: tool_name });
+2 -1
View File
@@ -17,6 +17,7 @@ import { HOOK_TIMEOUTS } from '../shared/hook-constants.js';
import { happy_path_error__with_fallback } from '../utils/silent-debug.js';
import { handleWorkerError } from '../shared/hook-error-handler.js';
import { extractLastMessage } from '../shared/transcript-parser.js';
import { getWorkerRestartInstructions } from '../utils/error-messages.js';
export interface StopInput {
session_id: string;
@@ -72,7 +73,7 @@ async function summaryHook(input?: StopInput): Promise<void> {
logger.failure('HOOK', 'Failed to generate summary', {
status: response.status
}, errorText);
throw new Error(`Failed to request summary from worker: ${response.status} ${errorText}`);
throw new Error(getWorkerRestartInstructions({ includeSkillFallback: true }));
}
logger.debug('HOOK', 'Summary request sent successfully');
+2 -1
View File
@@ -9,6 +9,7 @@
import { basename } from "path";
import { ensureWorkerRunning, getWorkerPort } from "../shared/worker-utils.js";
import { HOOK_EXIT_CODES } from "../shared/hook-constants.js";
import { getWorkerRestartInstructions } from "../utils/error-messages.js";
try {
// Ensure worker is running
@@ -24,7 +25,7 @@ try {
);
if (!response.ok) {
throw new Error(`Worker error ${response.status}`);
throw new Error(getWorkerRestartInstructions({ includeSkillFallback: true }));
}
const output = await response.text();
+3 -2
View File
@@ -15,13 +15,14 @@ import {
import { z } from 'zod';
import { zodToJsonSchema } from 'zod-to-json-schema';
import { happy_path_error__with_fallback } from '../utils/silent-debug.js';
import { getWorkerPort } from '../shared/worker-utils.js';
import { getWorkerPort, getWorkerHost } from '../shared/worker-utils.js';
/**
* Worker HTTP API configuration
*/
const WORKER_PORT = getWorkerPort();
const WORKER_BASE_URL = `http://localhost:${WORKER_PORT}`;
const WORKER_HOST = getWorkerHost();
const WORKER_BASE_URL = `http://${WORKER_HOST}:${WORKER_PORT}`;
/**
* Map tool names to Worker HTTP endpoints
+6 -4
View File
@@ -1,14 +1,16 @@
import { getWorkerRestartInstructions } from '../utils/error-messages.js';
/**
* Handles fetch errors by providing user-friendly messages for connection issues
* @throws Error with helpful message if worker is unreachable, re-throws original otherwise
*/
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')) {
throw new Error(
"There's a problem with the worker. Try: npm run worker:restart"
);
error.message?.includes('fetch failed') ||
error.message?.includes('Unable to connect')) {
throw new Error(getWorkerRestartInstructions());
}
throw error;
}
+25 -10
View File
@@ -6,6 +6,7 @@ import { logger } from "../utils/logger.js";
import { HOOK_TIMEOUTS, getTimeout } from "./hook-constants.js";
import { ProcessManager } from "../services/process/ProcessManager.js";
import { SettingsDefaultsManager } from "./SettingsDefaultsManager.js";
import { getWorkerRestartInstructions } from "../utils/error-messages.js";
const MARKETPLACE_ROOT = path.join(homedir(), '.claude', 'plugins', 'marketplaces', 'thedotmack');
@@ -194,19 +195,33 @@ export async function ensureWorkerRunning(): Promise<void> {
// 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()) {
await ensureWorkerVersionMatches();
return;
}
if (!started) {
const port = getWorkerPort();
throw new Error(
`Worker service failed to start on port ${port}.\n\n` +
`To start manually, run: npm run worker:start\n` +
`If already running, try: npm run worker:restart`
getWorkerRestartInstructions({
port,
customPrefix: `Worker service failed to start on port ${port}.`
})
);
}
// 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(
getWorkerRestartInstructions({
port,
customPrefix: `Worker service started but is not responding on port ${port}.`
})
);
}
+53
View File
@@ -0,0 +1,53 @@
/**
* Platform-aware error message generator for worker connection failures
*/
export interface WorkerErrorMessageOptions {
port?: number;
includeSkillFallback?: boolean;
customPrefix?: string;
}
/**
* Generate platform-specific worker restart instructions
* @param options Configuration for error message generation
* @returns Formatted error message with platform-specific paths and commands
*/
export function getWorkerRestartInstructions(
options: WorkerErrorMessageOptions = {}
): string {
const {
port,
includeSkillFallback = false,
customPrefix
} = options;
const isWindows = process.platform === 'win32';
// Platform-specific directory paths
const pluginDir = isWindows
? '%USERPROFILE%\\.claude\\plugins\\marketplaces\\thedotmack'
: '~/.claude/plugins/marketplaces/thedotmack';
// Platform-specific terminal name
const terminal = isWindows
? 'Command Prompt or PowerShell'
: 'Terminal';
// Build error message
const prefix = customPrefix || 'Worker service connection failed.';
const portInfo = port ? ` (port ${port})` : '';
let message = `${prefix}${portInfo}\n\n`;
message += `To restart the worker:\n`;
message += `1. Exit Claude Code completely\n`;
message += `2. Open ${terminal}\n`;
message += `3. Navigate to: ${pluginDir}\n`;
message += `4. Run: npm run worker:restart`;
if (includeSkillFallback) {
message += `\n\nIf that doesn't work, try: /troubleshoot`;
}
return message;
}