Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
68 changes: 45 additions & 23 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -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...
Expand All @@ -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.
Expand Down
14 changes: 14 additions & 0 deletions ROADMAP.md
Original file line number Diff line number Diff line change
Expand Up @@ -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

Expand Down
4 changes: 2 additions & 2 deletions package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
@@ -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",
Expand Down
111 changes: 81 additions & 30 deletions src/commands/init.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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 = {
Expand Down Expand Up @@ -86,6 +87,7 @@ interface SetupStatus {
mcpConfigured: boolean;
claudeMdPresent: boolean;
settingsJsonValid: boolean;
mcpJsonValid: boolean;
}

function ask(question: string): Promise<string> {
Expand Down Expand Up @@ -115,6 +117,19 @@ function loadSettings(): { settings: Record<string, unknown>; valid: boolean } {
}
}

function loadMcpJson(): { mcpJson: Record<string, unknown>; 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
Expand Down Expand Up @@ -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<string, unknown>;
if (mcpJsonValid && mcpJson.mcpServers) {
const mcpServers = mcpJson.mcpServers as Record<string, unknown>;
mcpConfigured = 'threadlinking' in mcpServers;
}

Expand All @@ -167,6 +183,7 @@ function checkStatus(): SetupStatus {
mcpConfigured,
claudeMdPresent,
settingsJsonValid,
mcpJsonValid,
};
}

Expand Down Expand Up @@ -249,12 +266,17 @@ function installSessionStartHook(settings: Record<string, unknown>): boolean {
return true;
}

function installMcpServer(settings: Record<string, unknown>): boolean {
function installMcpServer(mcpJson: Record<string, unknown>): 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<string, unknown>;
const mcpServers = mcpJson.mcpServers as Record<string, unknown>;

// Check if already configured
if ('threadlinking' in mcpServers) {
Expand Down Expand Up @@ -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('');
}

Expand Down Expand Up @@ -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<string, unknown>);
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<string, unknown>);

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');
}
}
}

Expand Down