diff --git a/README.md b/README.md index 7be46c6..a282587 100644 --- a/README.md +++ b/README.md @@ -16,13 +16,16 @@ Threadlinking solves this. Claude automatically saves the relevant conversation ## Installation -### Quick Start (Recommended) +### Quick Start + +Install globally and run the guided setup: ```bash -npx threadlinking init +npm install -g threadlinking +threadlinking init ``` -This runs a guided setup that configures everything: +The global install is required because Threadlinking's hooks need `threadlinking` available as a command between sessions. The setup walks you through everything: ``` Setting up threadlinking... @@ -31,67 +34,86 @@ Checking environment... ✓ Claude Code detected ✓ ~/.claude directory exists -[1/3] PostToolUse hook (auto-tracks files you create) +[1/4] PostToolUse hook (auto-tracks files you create) + Status: Not installed + Install to ~/.claude/settings.json? (Y/n) y + ✓ Hook installed + +[2/4] SessionStart hook (shows context at session start) Status: Not installed - Install to ~/.claude/settings.json? (Y/n) + Install to ~/.claude/settings.json? (Y/n) y ✓ Hook installed -[2/3] MCP Server (gives Claude direct access to threadlinking tools) +[3/4] MCP Server (gives Claude direct access to threadlinking tools) Status: Not configured - Add to ~/.claude/settings.json mcpServers? (Y/n) + Add to ~/.claude/settings.json mcpServers? (Y/n) y ✓ MCP server configured -[3/3] CLAUDE.md instructions (teaches Claude when/how to use threadlinking) +[4/4] CLAUDE.md instructions (teaches Claude when/how to use threadlinking) Status: Not present - Append to ~/.claude/CLAUDE.md? (Y/n) + Append to ~/.claude/CLAUDE.md? (Y/n) y ✓ Instructions added Done! Threadlinking is fully configured. + +Start a new Claude Code session to begin. +Tip: Run `threadlinking list` to see your threads. ``` **Check your setup anytime:** ```bash -npx threadlinking init --status +threadlinking init --status ``` **Non-interactive install (scripts/CI):** ```bash -npx threadlinking init --no-interactive +threadlinking init --no-interactive ``` -> **Note:** `npx` runs threadlinking without installing it globally. You'll need to use `npx threadlinking` for each command. If you prefer using `threadlinking` directly, run `npm install -g threadlinking` (see Global CLI install below). +### Standalone CLI (No AI Integration) + +Threadlinking also works as a standalone CLI tool without any Claude/AI integration. If you just want to manually track context for your projects, skip the `init` and use it directly: + +```bash +# Create a thread and add context +threadlinking snippet myproject "Starting auth module with JWT" + +# Link files to threads +threadlinking attach myproject src/auth/jwt.ts + +# Later, check why a file exists +threadlinking explain src/auth/jwt.ts + +# See all your threads +threadlinking list +``` + +See [Commands](#commands) for the full reference. ### Manual Setup -If you prefer to configure components individually: +If you prefer to configure components individually instead of using `init`: **MCP Server only** (Claude Desktop or Claude Code): ```json -// ~/.claude/settings.json (Claude Code) +// ~/.claude/mcp.json (Claude Code) // or ~/Library/Application Support/Claude/claude_desktop_config.json (Claude Desktop) { "mcpServers": { "threadlinking": { - "command": "npx", - "args": ["threadlinking-mcp"] + "command": "threadlinking-mcp" } } } ``` -**Global CLI install** (if you want `threadlinking` available as a direct command): -```bash -npm install -g threadlinking -``` -After installing, restart your terminal. You can then use `threadlinking` directly instead of `npx threadlinking`. - ### What Gets Installed | Component | Purpose | Location | |-----------|---------|----------| | PostToolUse hook | Auto-tracks files you create/edit | `~/.claude/settings.json` | -| MCP Server | Gives Claude direct tool access | `~/.claude/settings.json` | +| MCP Server | Gives Claude direct tool access | `~/.claude/mcp.json` | | CLAUDE.md block | Teaches Claude when/how to use threadlinking | `~/.claude/CLAUDE.md` | All components are optional and can be skipped during init. diff --git a/ROADMAP.md b/ROADMAP.md index 914ccf8..ace714e 100644 --- a/ROADMAP.md +++ b/ROADMAP.md @@ -53,6 +53,20 @@ The main blocker for wider adoption. - [ ] Submit to official MCP Registry - [ ] Add `mcp.json` manifest file - [ ] Create setup instructions for Claude Code users + - **Recommend `~/.claude/mcp.json`** (dedicated MCP config, not settings.json) + - Document the difference: mcp.json is for MCP servers only, settings.json mixes personal config + - Provide simple copy-paste config: + ```json + { + "mcpServers": { + "threadlinking": { + "command": "npx", + "args": ["threadlinking-mcp"] + } + } + } + ``` + - Note: Users can also use settings.json → mcpServers if they prefer single-file config - [ ] List on directories: Smithery, Glama, MCP.so - [ ] Submit PR to awesome-mcp-servers diff --git a/package-lock.json b/package-lock.json index 6ddb5c0..a874e10 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,12 +1,12 @@ { "name": "threadlinking", - "version": "2.0.9", + "version": "2.0.10", "lockfileVersion": 3, "requires": true, "packages": { "": { "name": "threadlinking", - "version": "2.0.9", + "version": "2.0.10", "license": "MIT", "dependencies": { "@modelcontextprotocol/sdk": "^1.25.2", diff --git a/package.json b/package.json index 149ec3a..5ed274f 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "threadlinking", - "version": "2.0.9", + "version": "2.0.10", "description": "Connect your files with their origin stories", "type": "module", "main": "dist/index.js", diff --git a/src/commands/init.ts b/src/commands/init.ts index 18f967f..3f61187 100644 --- a/src/commands/init.ts +++ b/src/commands/init.ts @@ -6,6 +6,7 @@ import { createInterface } from 'readline'; const CLAUDE_DIR = join(homedir(), '.claude'); const SETTINGS_PATH = join(CLAUDE_DIR, 'settings.json'); +const MCP_JSON_PATH = join(CLAUDE_DIR, 'mcp.json'); const CLAUDE_MD_PATH = join(CLAUDE_DIR, 'CLAUDE.md'); const POST_TOOL_USE_HOOK_CONFIG = { @@ -86,6 +87,7 @@ interface SetupStatus { mcpConfigured: boolean; claudeMdPresent: boolean; settingsJsonValid: boolean; + mcpJsonValid: boolean; } function ask(question: string): Promise { @@ -115,6 +117,19 @@ function loadSettings(): { settings: Record; valid: boolean } { } } +function loadMcpJson(): { mcpJson: Record; valid: boolean } { + if (!existsSync(MCP_JSON_PATH)) { + return { mcpJson: {}, valid: true }; + } + + try { + const data = readFileSync(MCP_JSON_PATH, 'utf-8'); + return { mcpJson: JSON.parse(data), valid: true }; + } catch { + return { mcpJson: {}, valid: false }; + } +} + function checkStatus(): SetupStatus { const claudeDirExists = existsSync(CLAUDE_DIR); const claudeCodeDetected = claudeDirExists; // If ~/.claude exists, Claude Code is likely installed @@ -145,10 +160,11 @@ function checkStatus(): SetupStatus { } } - // Check MCP server + // Check MCP server (in mcp.json) + const { mcpJson, valid: mcpJsonValid } = loadMcpJson(); let mcpConfigured = false; - if (settingsJsonValid && settings.mcpServers) { - const mcpServers = settings.mcpServers as Record; + if (mcpJsonValid && mcpJson.mcpServers) { + const mcpServers = mcpJson.mcpServers as Record; mcpConfigured = 'threadlinking' in mcpServers; } @@ -167,6 +183,7 @@ function checkStatus(): SetupStatus { mcpConfigured, claudeMdPresent, settingsJsonValid, + mcpJsonValid, }; } @@ -249,12 +266,17 @@ function installSessionStartHook(settings: Record): boolean { return true; } -function installMcpServer(settings: Record): boolean { +function installMcpServer(mcpJson: Record): boolean { + // Ensure ~/.claude exists + if (!existsSync(CLAUDE_DIR)) { + mkdirSync(CLAUDE_DIR, { recursive: true }); + } + // Initialize mcpServers if needed - if (!settings.mcpServers) { - settings.mcpServers = {}; + if (!mcpJson.mcpServers) { + mcpJson.mcpServers = {}; } - const mcpServers = settings.mcpServers as Record; + const mcpServers = mcpJson.mcpServers as Record; // Check if already configured if ('threadlinking' in mcpServers) { @@ -285,12 +307,15 @@ function printStatus(status: SetupStatus): void { console.log(` Claude Code: ${status.claudeCodeDetected ? '\u2713 Detected' : '\u2717 Not detected'}`); console.log(` PostToolUse hook: ${status.postToolUseHookInstalled ? '\u2713 Installed' : '\u2717 Not installed'}`); console.log(` SessionStart hook: ${status.sessionStartHookInstalled ? '\u2713 Installed' : '\u2717 Not installed'}`); - console.log(` MCP server: ${status.mcpConfigured ? '\u2713 Configured' : '\u2717 Not configured'}`); + console.log(` MCP server: ${status.mcpConfigured ? '\u2713 Configured (mcp.json)' : '\u2717 Not configured'}`); console.log(` CLAUDE.md: ${status.claudeMdPresent ? '\u2713 Present' : '\u2717 Not present'}`); if (!status.settingsJsonValid && existsSync(SETTINGS_PATH)) { console.log(` settings.json: \u2717 Invalid JSON`); } + if (!status.mcpJsonValid && existsSync(MCP_JSON_PATH)) { + console.log(` mcp.json: \u2717 Invalid JSON`); + } console.log(''); } @@ -393,33 +418,59 @@ export const initCommand = new Command('init') } } - // Reload settings in case they changed - reloaded = loadSettings(); - if (reloaded.valid) { - settings = reloaded.settings; - } - - // Step 3: MCP Server + // Step 3: MCP Server (uses separate mcp.json file) console.log('[3/4] MCP Server (gives Claude direct access to threadlinking tools)'); - const mcpAlreadyConfigured = settings.mcpServers && - 'threadlinking' in (settings.mcpServers as Record); + let { mcpJson, valid: mcpJsonValid } = loadMcpJson(); - if (mcpAlreadyConfigured) { - console.log(' Status: Already configured'); - console.log(' \u2713 Skipping\n'); - } else { - console.log(' Status: Not configured'); - let shouldInstall = true; + // Handle invalid mcp.json + if (!mcpJsonValid && existsSync(MCP_JSON_PATH)) { + console.log(' \u26a0 mcp.json exists but is not valid JSON'); + let shouldFix = true; if (options.interactive !== false) { - const answer = await ask(' Add to ~/.claude/settings.json mcpServers? (Y/n) '); - shouldInstall = answer !== 'n' && answer !== 'no'; + const answer = await ask(' Back up and reset mcp.json? (Y/n) '); + shouldFix = answer !== 'n' && answer !== 'no'; } - if (shouldInstall) { - installMcpServer(settings); - writeFileSync(SETTINGS_PATH, JSON.stringify(settings, null, 2), 'utf-8'); - console.log(' \u2713 MCP server configured\n'); + if (shouldFix) { + const backupPath = `${MCP_JSON_PATH}.backup.${Date.now()}`; + try { + copyFileSync(MCP_JSON_PATH, backupPath); + console.log(` Backed up to ${backupPath}`); + writeFileSync(MCP_JSON_PATH, '{}', 'utf-8'); + mcpJson = {}; + mcpJsonValid = true; + console.log(' \u2713 mcp.json reset'); + } catch (error) { + console.error(` Failed to backup: ${error instanceof Error ? error.message : error}`); + console.log(' Skipping MCP server setup\n'); + mcpJsonValid = false; + } } else { - console.log(' Skipped\n'); + console.log(' Skipping MCP server setup - please fix mcp.json manually\n'); + mcpJsonValid = false; + } + } + + if (mcpJsonValid) { + const mcpAlreadyConfigured = mcpJson.mcpServers && + 'threadlinking' in (mcpJson.mcpServers as Record); + + if (mcpAlreadyConfigured) { + console.log(' Status: Already configured'); + console.log(' \u2713 Skipping\n'); + } else { + console.log(' Status: Not configured'); + let shouldInstall = true; + if (options.interactive !== false) { + const answer = await ask(' Add to ~/.claude/mcp.json? (Y/n) '); + shouldInstall = answer !== 'n' && answer !== 'no'; + } + if (shouldInstall) { + installMcpServer(mcpJson); + writeFileSync(MCP_JSON_PATH, JSON.stringify(mcpJson, null, 2), 'utf-8'); + console.log(' \u2713 MCP server configured\n'); + } else { + console.log(' Skipped\n'); + } } }