e975555896
* feat: Switch to persistent Chroma HTTP server Replace MCP subprocess approach with persistent Chroma HTTP server for improved performance and reliability. This re-enables Chroma on Windows by eliminating the subprocess spawning that caused console popups. Changes: - NEW: ChromaServerManager.ts - Manages local Chroma server lifecycle via `npx chroma run` - REFACTOR: ChromaSync.ts - Uses chromadb npm package's ChromaClient instead of MCP subprocess (removes Windows disabling) - UPDATE: worker-service.ts - Starts Chroma server on initialization - UPDATE: GracefulShutdown.ts - Stops Chroma server on shutdown - UPDATE: SettingsDefaultsManager.ts - New Chroma configuration options - UPDATE: build-hooks.js - Mark optional chromadb deps as external Benefits: - Eliminates subprocess spawn latency on first query - Single server process instead of per-operation subprocesses - No Python/uvx dependency for local mode - Re-enables Chroma vector search on Windows - Future-ready for cloud-hosted Chroma (claude-mem pro) - Cross-platform: Linux, macOS, Windows Configuration: CLAUDE_MEM_CHROMA_MODE=local|remote CLAUDE_MEM_CHROMA_HOST=127.0.0.1 CLAUDE_MEM_CHROMA_PORT=8000 CLAUDE_MEM_CHROMA_SSL=false Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com> * fix: Use chromadb v3.2.2 with v2 API heartbeat endpoint - Updated chromadb from ^1.9.2 to ^3.2.2 (includes CLI binary) - Changed heartbeat endpoint from /api/v1 to /api/v2 The 1.9.x version did not include the CLI, causing `npx chroma run` to fail. Version 3.2.2 includes the chroma CLI and uses the v2 API. Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com> * feat: Add DefaultEmbeddingFunction for local vector embeddings - Added @chroma-core/default-embed dependency for local embeddings - Updated ChromaSync to use DefaultEmbeddingFunction with collections - Added isServerReachable() async method for reliable server detection - Fixed start() to detect and reuse existing Chroma servers - Updated build script to externalize native ONNX binaries - Added runtime dependency to plugin/package.json The embedding function uses all-MiniLM-L6-v2 model locally via ONNX, eliminating need for external embedding API calls. Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com> * Update src/services/sync/ChromaServerManager.ts Co-authored-by: greptile-apps[bot] <165735046+greptile-apps[bot]@users.noreply.github.com> * fix: Remove duplicate else block from merge Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com> * feat: Add multi-tenancy support for claude-mem pro Wire tenant, database, and API key settings into ChromaSync for remote/pro mode. In remote mode: - Passes tenant and database to ChromaClient for data isolation - Adds Authorization header when API key is configured - Logs tenant isolation connection details Local mode unchanged - uses default_tenant without explicit params. Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com> * fix: add plugin.json to root .claude-plugin directory Claude Code's plugin discovery looks for plugin.json at the marketplace root level in .claude-plugin/, not nested inside plugin/.claude-plugin/. Without this file at the root level, skills and commands are not discovered. This matches the structure of working plugins like claude-research-team. Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com> * fix: resolve SDK spawn failures and sharp native binary crashes - Strip CLAUDECODE env var from SDK subprocesses to prevent "cannot be launched inside another Claude Code session" error (Claude Code 2.1.42+) - Lazy-load @chroma-core/default-embed to avoid eagerly pulling in sharp native binaries at bundle startup (fixes ERR_DLOPEN_FAILED) - Add stderr capture to SDK spawn for diagnosing future process failures - Exclude lockfiles from marketplace rsync and delete stale lockfiles before npm install to prevent native dep version mismatches Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com> * feat: scaffold installer package with @clack/prompts and esbuild Sets up the claude-mem-installer project structure with build tooling, placeholder step and utility modules, and verified esbuild bundling. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com> * feat: implement entry point, welcome screen, and dependency checks Adds TTY guard, styled welcome banner with install mode selection, OS detection utilities, and automated dependency checking/installation for Node.js, git, Bun, and uv. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com> * feat: implement IDE selection and AI provider configuration Adds multiselect IDE picker (Claude Code, Cursor) and provider configuration with Claude CLI/API, Gemini, and OpenRouter support. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com> * feat: implement settings configuration wizard and settings file writer Adds interactive settings wizard with default/custom modes, Chroma configuration, and a settings writer that merges with existing settings for upgrade support. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com> * feat: implement installation execution and worker startup Adds git clone, build, plugin registration (marketplace, cache, settings), and worker startup with health check polling. Fixes TypeScript errors in settings.ts validate callbacks. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com> * feat: add completion screen and curl|bash bootstrap script Completion screen shows configuration summary and next steps. Bootstrap shell script enables curl -fsSL install.cmem.ai | bash with TTY reconnection for interactive prompts. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com> * feat: wire up full installer flow in index.ts Connects all steps: welcome → dependency checks → IDE selection → provider config → settings → installation → worker startup → completion. Configure-only mode skips clone/build/worker steps. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com> * docs: add animated installer implementation plan Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com> --------- Co-authored-by: bigphoot <bigphoot@local> Co-authored-by: Claude Opus 4.5 <noreply@anthropic.com> Co-authored-by: Alexander Knigge <166455923+bigph00t@users.noreply.github.com> Co-authored-by: greptile-apps[bot] <165735046+greptile-apps[bot]@users.noreply.github.com> Co-authored-by: bigphoot <bigphoot@gmail.com>
168 lines
5.3 KiB
TypeScript
168 lines
5.3 KiB
TypeScript
import * as p from '@clack/prompts';
|
|
import pc from 'picocolors';
|
|
import { execSync } from 'child_process';
|
|
import { existsSync, mkdirSync, readFileSync, writeFileSync, cpSync } from 'fs';
|
|
import { join } from 'path';
|
|
import { homedir, tmpdir } from 'os';
|
|
import type { IDE } from './ide-selection.js';
|
|
|
|
const MARKETPLACE_DIR = join(homedir(), '.claude', 'plugins', 'marketplaces', 'thedotmack');
|
|
const PLUGINS_DIR = join(homedir(), '.claude', 'plugins');
|
|
const CLAUDE_SETTINGS_PATH = join(homedir(), '.claude', 'settings.json');
|
|
|
|
function ensureDir(directoryPath: string): void {
|
|
if (!existsSync(directoryPath)) {
|
|
mkdirSync(directoryPath, { recursive: true });
|
|
}
|
|
}
|
|
|
|
function readJsonFile(filepath: string): any {
|
|
if (!existsSync(filepath)) return {};
|
|
return JSON.parse(readFileSync(filepath, 'utf-8'));
|
|
}
|
|
|
|
function writeJsonFile(filepath: string, data: any): void {
|
|
ensureDir(join(filepath, '..'));
|
|
writeFileSync(filepath, JSON.stringify(data, null, 2) + '\n', 'utf-8');
|
|
}
|
|
|
|
function registerMarketplace(): void {
|
|
const knownMarketplacesPath = join(PLUGINS_DIR, 'known_marketplaces.json');
|
|
const knownMarketplaces = readJsonFile(knownMarketplacesPath);
|
|
|
|
knownMarketplaces['thedotmack'] = {
|
|
source: {
|
|
source: 'github',
|
|
repo: 'thedotmack/claude-mem',
|
|
},
|
|
installLocation: MARKETPLACE_DIR,
|
|
lastUpdated: new Date().toISOString(),
|
|
autoUpdate: true,
|
|
};
|
|
|
|
ensureDir(PLUGINS_DIR);
|
|
writeJsonFile(knownMarketplacesPath, knownMarketplaces);
|
|
}
|
|
|
|
function registerPlugin(version: string): void {
|
|
const installedPluginsPath = join(PLUGINS_DIR, 'installed_plugins.json');
|
|
const installedPlugins = readJsonFile(installedPluginsPath);
|
|
|
|
if (!installedPlugins.version) installedPlugins.version = 2;
|
|
if (!installedPlugins.plugins) installedPlugins.plugins = {};
|
|
|
|
const pluginCachePath = join(PLUGINS_DIR, 'cache', 'thedotmack', 'claude-mem', version);
|
|
const now = new Date().toISOString();
|
|
|
|
installedPlugins.plugins['claude-mem@thedotmack'] = [
|
|
{
|
|
scope: 'user',
|
|
installPath: pluginCachePath,
|
|
version,
|
|
installedAt: now,
|
|
lastUpdated: now,
|
|
},
|
|
];
|
|
|
|
writeJsonFile(installedPluginsPath, installedPlugins);
|
|
|
|
// Copy built plugin to cache directory
|
|
ensureDir(pluginCachePath);
|
|
const pluginSourceDir = join(MARKETPLACE_DIR, 'plugin');
|
|
if (existsSync(pluginSourceDir)) {
|
|
cpSync(pluginSourceDir, pluginCachePath, { recursive: true });
|
|
}
|
|
}
|
|
|
|
function enablePluginInClaudeSettings(): void {
|
|
const settings = readJsonFile(CLAUDE_SETTINGS_PATH);
|
|
|
|
if (!settings.enabledPlugins) settings.enabledPlugins = {};
|
|
settings.enabledPlugins['claude-mem@thedotmack'] = true;
|
|
|
|
writeJsonFile(CLAUDE_SETTINGS_PATH, settings);
|
|
}
|
|
|
|
function getPluginVersion(): string {
|
|
const pluginJsonPath = join(MARKETPLACE_DIR, 'plugin', '.claude-plugin', 'plugin.json');
|
|
if (existsSync(pluginJsonPath)) {
|
|
const pluginJson = JSON.parse(readFileSync(pluginJsonPath, 'utf-8'));
|
|
return pluginJson.version ?? '1.0.0';
|
|
}
|
|
return '1.0.0';
|
|
}
|
|
|
|
export async function runInstallation(selectedIDEs: IDE[]): Promise<void> {
|
|
const tempDir = join(tmpdir(), `claude-mem-install-${Date.now()}`);
|
|
|
|
await p.tasks([
|
|
{
|
|
title: 'Cloning claude-mem repository',
|
|
task: async (message) => {
|
|
message('Downloading latest release...');
|
|
execSync(
|
|
`git clone --depth 1 https://github.com/thedotmack/claude-mem.git "${tempDir}"`,
|
|
{ stdio: 'pipe' },
|
|
);
|
|
return `Repository cloned ${pc.green('OK')}`;
|
|
},
|
|
},
|
|
{
|
|
title: 'Installing dependencies',
|
|
task: async (message) => {
|
|
message('Running npm install...');
|
|
execSync('npm install', { cwd: tempDir, stdio: 'pipe' });
|
|
return `Dependencies installed ${pc.green('OK')}`;
|
|
},
|
|
},
|
|
{
|
|
title: 'Building plugin',
|
|
task: async (message) => {
|
|
message('Compiling TypeScript and bundling...');
|
|
execSync('npm run build', { cwd: tempDir, stdio: 'pipe' });
|
|
return `Plugin built ${pc.green('OK')}`;
|
|
},
|
|
},
|
|
{
|
|
title: 'Registering plugin',
|
|
task: async (message) => {
|
|
message('Copying files to marketplace directory...');
|
|
ensureDir(MARKETPLACE_DIR);
|
|
|
|
// Sync from cloned repo to marketplace dir, excluding .git and lock files
|
|
execSync(
|
|
`rsync -a --delete --exclude=.git --exclude=package-lock.json --exclude=bun.lock "${tempDir}/" "${MARKETPLACE_DIR}/"`,
|
|
{ stdio: 'pipe' },
|
|
);
|
|
|
|
message('Registering marketplace...');
|
|
registerMarketplace();
|
|
|
|
message('Installing marketplace dependencies...');
|
|
execSync('npm install', { cwd: MARKETPLACE_DIR, stdio: 'pipe' });
|
|
|
|
message('Registering plugin in Claude Code...');
|
|
const version = getPluginVersion();
|
|
registerPlugin(version);
|
|
|
|
message('Enabling plugin...');
|
|
enablePluginInClaudeSettings();
|
|
|
|
return `Plugin registered (v${getPluginVersion()}) ${pc.green('OK')}`;
|
|
},
|
|
},
|
|
]);
|
|
|
|
// Cleanup temp directory (non-critical if it fails)
|
|
try {
|
|
execSync(`rm -rf "${tempDir}"`, { stdio: 'pipe' });
|
|
} catch {
|
|
// Temp dir will be cleaned by OS eventually
|
|
}
|
|
|
|
if (selectedIDEs.includes('cursor')) {
|
|
p.log.info('Cursor hook configuration will be available after first launch.');
|
|
p.log.info('Run: claude-mem cursor-setup (coming soon)');
|
|
}
|
|
}
|