@claudetools/tools 0.1.1 → 0.2.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -1,6 +1,6 @@
1
1
  # claudetools
2
2
 
3
- MCP server for Claude Code - persistent AI memory, task management, and codebase intelligence.
3
+ Persistent AI memory, task management, and codebase intelligence for Claude Code.
4
4
 
5
5
  ## Installation
6
6
 
package/dist/cli.js CHANGED
@@ -7,7 +7,7 @@ import { parseArgs } from 'node:util';
7
7
  import { readFileSync } from 'node:fs';
8
8
  import { fileURLToPath } from 'node:url';
9
9
  import { dirname, join } from 'node:path';
10
- import { runSetup } from './setup.js';
10
+ import { runSetup, runUninstall } from './setup.js';
11
11
  import { startServer } from './index.js';
12
12
  // Get version from package.json
13
13
  const __filename = fileURLToPath(import.meta.url);
@@ -19,6 +19,7 @@ const version = packageJson.version;
19
19
  const { values } = parseArgs({
20
20
  options: {
21
21
  setup: { type: 'boolean', short: 's' },
22
+ uninstall: { type: 'boolean', short: 'u' },
22
23
  version: { type: 'boolean', short: 'v' },
23
24
  help: { type: 'boolean', short: 'h' },
24
25
  },
@@ -32,17 +33,18 @@ if (values.version) {
32
33
  // Handle help flag
33
34
  if (values.help) {
34
35
  console.log(`
35
- claudetools - MCP server for Claude Code
36
+ claudetools - Persistent AI memory for Claude Code
36
37
 
37
38
  Usage:
38
39
  claudetools [options]
39
40
 
40
41
  Options:
41
- -s, --setup Interactive setup wizard
42
- -v, --version Show version
43
- -h, --help Show this help
42
+ -s, --setup Interactive setup wizard
43
+ -u, --uninstall Remove from Claude Code
44
+ -v, --version Show version
45
+ -h, --help Show this help
44
46
 
45
- Running without options starts the MCP server.
47
+ Running without options starts the server.
46
48
 
47
49
  Documentation: https://github.com/claudetools/memory
48
50
  `);
@@ -55,6 +57,12 @@ if (values.setup) {
55
57
  process.exit(1);
56
58
  });
57
59
  }
60
+ else if (values.uninstall) {
61
+ runUninstall().catch((error) => {
62
+ console.error('Uninstall failed:', error);
63
+ process.exit(1);
64
+ });
65
+ }
58
66
  else {
59
67
  // Start MCP server
60
68
  startServer();
package/dist/setup.d.ts CHANGED
@@ -1 +1,2 @@
1
1
  export declare function runSetup(): Promise<void>;
2
+ export declare function runUninstall(): Promise<void>;
package/dist/setup.js CHANGED
@@ -1,206 +1,554 @@
1
1
  // =============================================================================
2
- // ClaudeTools Memory Interactive Setup Wizard
2
+ // ClaudeTools Interactive Setup Wizard
3
3
  // =============================================================================
4
- // Guides users through initial configuration with validation
5
- import readline from 'readline';
6
- import { stdin as input, stdout as output } from 'process';
4
+ // Guides users through authentication, service configuration, and Claude Code integration
5
+ import prompts from 'prompts';
6
+ import chalk from 'chalk';
7
+ import ora from 'ora';
8
+ import { homedir } from 'os';
9
+ import { join } from 'path';
10
+ import { existsSync, readFileSync, writeFileSync, mkdirSync, copyFileSync } from 'fs';
7
11
  import { loadConfigFromFile, saveConfig, ensureConfigDir, getConfigPath, DEFAULT_CONFIG, } from './helpers/config-manager.js';
8
12
  // -----------------------------------------------------------------------------
9
- // Prompt Helpers
10
- // -----------------------------------------------------------------------------
11
- /**
12
- * Prompt user for input with optional default value
13
- */
14
- function prompt(question, defaultValue) {
15
- const rl = readline.createInterface({ input, output });
16
- return new Promise((resolve) => {
17
- const displayQuestion = defaultValue
18
- ? `${question} [${defaultValue}]: `
19
- : `${question}: `;
20
- rl.question(displayQuestion, (answer) => {
21
- rl.close();
22
- resolve(answer.trim() || defaultValue || '');
23
- });
24
- });
13
+ // Constants
14
+ // -----------------------------------------------------------------------------
15
+ const CLAUDE_DIR = join(homedir(), '.claude');
16
+ const MCP_CONFIG_PATH = join(CLAUDE_DIR, 'mcp.json');
17
+ const HOOKS_DIR = join(CLAUDE_DIR, 'hooks');
18
+ // -----------------------------------------------------------------------------
19
+ // Utility Functions
20
+ // -----------------------------------------------------------------------------
21
+ function header(title) {
22
+ console.log('\n' + chalk.cyan('━'.repeat(50)));
23
+ console.log(chalk.cyan.bold(title));
24
+ console.log(chalk.cyan('━'.repeat(50)) + '\n');
25
25
  }
26
- /**
27
- * Prompt user for yes/no confirmation
28
- */
29
- async function confirm(question, defaultValue = true) {
30
- const defaultStr = defaultValue ? 'Y/n' : 'y/N';
31
- const answer = await prompt(`${question} (${defaultStr})`);
32
- if (!answer) {
33
- return defaultValue;
34
- }
35
- return answer.toLowerCase().startsWith('y');
26
+ function success(msg) {
27
+ console.log(chalk.green('✓ ') + msg);
28
+ }
29
+ function error(msg) {
30
+ console.log(chalk.red('✗ ') + msg);
31
+ }
32
+ function info(msg) {
33
+ console.log(chalk.blue('ℹ ') + msg);
36
34
  }
37
35
  // -----------------------------------------------------------------------------
38
- // API Validation
36
+ // Authentication
39
37
  // -----------------------------------------------------------------------------
40
- /**
41
- * Validate API connection and authentication
42
- */
43
- async function validateApiConnection(apiUrl, apiKey) {
38
+ async function authenticateWithEmailPassword(apiUrl) {
39
+ const { email, password } = await prompts([
40
+ {
41
+ type: 'text',
42
+ name: 'email',
43
+ message: 'Email:',
44
+ validate: (v) => v.includes('@') || 'Enter a valid email',
45
+ },
46
+ {
47
+ type: 'password',
48
+ name: 'password',
49
+ message: 'Password:',
50
+ validate: (v) => v.length >= 8 || 'Password must be at least 8 characters',
51
+ },
52
+ ]);
53
+ if (!email || !password)
54
+ return null;
55
+ const spinner = ora('Authenticating...').start();
44
56
  try {
45
- const headers = {
46
- 'Content-Type': 'application/json',
47
- };
48
- if (apiKey) {
49
- headers['Authorization'] = `Bearer ${apiKey}`;
57
+ const response = await fetch(`${apiUrl}/api/v1/auth/login`, {
58
+ method: 'POST',
59
+ headers: { 'Content-Type': 'application/json' },
60
+ body: JSON.stringify({ email, password }),
61
+ });
62
+ if (!response.ok) {
63
+ const data = await response.json().catch(() => ({}));
64
+ spinner.fail('Authentication failed');
65
+ error(data.message || `HTTP ${response.status}`);
66
+ return null;
50
67
  }
51
- const response = await fetch(`${apiUrl}/health`, {
52
- headers,
53
- signal: AbortSignal.timeout(10000), // 10 second timeout
68
+ const data = await response.json();
69
+ spinner.succeed('Authenticated');
70
+ return data;
71
+ }
72
+ catch (err) {
73
+ spinner.fail('Connection failed');
74
+ error(err instanceof Error ? err.message : String(err));
75
+ return null;
76
+ }
77
+ }
78
+ async function authenticateWithDeviceCode(apiUrl) {
79
+ const spinner = ora('Requesting device code...').start();
80
+ try {
81
+ // Request device code
82
+ const codeResponse = await fetch(`${apiUrl}/api/v1/auth/device/code`, {
83
+ method: 'POST',
84
+ headers: { 'Content-Type': 'application/json' },
54
85
  });
55
- return response.ok;
86
+ if (!codeResponse.ok) {
87
+ spinner.fail('Failed to get device code');
88
+ return null;
89
+ }
90
+ const { device_code, user_code, verification_uri, expires_in, interval } = await codeResponse.json();
91
+ spinner.stop();
92
+ console.log('\n' + chalk.yellow.bold(' ' + user_code) + '\n');
93
+ console.log(` Open: ${chalk.underline(verification_uri)}`);
94
+ console.log(` Enter the code above to authenticate.\n`);
95
+ const pollSpinner = ora('Waiting for authentication...').start();
96
+ // Poll for token
97
+ const pollInterval = (interval || 5) * 1000;
98
+ const expiresAt = Date.now() + (expires_in || 900) * 1000;
99
+ while (Date.now() < expiresAt) {
100
+ await new Promise((r) => setTimeout(r, pollInterval));
101
+ const tokenResponse = await fetch(`${apiUrl}/api/v1/auth/device/token`, {
102
+ method: 'POST',
103
+ headers: { 'Content-Type': 'application/json' },
104
+ body: JSON.stringify({ device_code }),
105
+ });
106
+ if (tokenResponse.ok) {
107
+ const data = await tokenResponse.json();
108
+ pollSpinner.succeed('Authenticated');
109
+ return data;
110
+ }
111
+ const errorData = await tokenResponse.json().catch(() => ({ error: 'unknown' }));
112
+ if (errorData.error === 'authorization_pending') {
113
+ continue;
114
+ }
115
+ else if (errorData.error === 'slow_down') {
116
+ await new Promise((r) => setTimeout(r, 5000));
117
+ continue;
118
+ }
119
+ else {
120
+ pollSpinner.fail('Authentication failed');
121
+ error(errorData.error || 'Unknown error');
122
+ return null;
123
+ }
124
+ }
125
+ pollSpinner.fail('Authentication timed out');
126
+ return null;
56
127
  }
57
- catch (error) {
58
- return false;
128
+ catch (err) {
129
+ spinner.fail('Connection failed');
130
+ error(err instanceof Error ? err.message : String(err));
131
+ return null;
59
132
  }
60
133
  }
61
- /**
62
- * Register this system with the ClaudeTools API
63
- */
64
- async function registerSystem(config) {
134
+ async function signUp(apiUrl) {
135
+ const { email, password, confirmPassword } = await prompts([
136
+ {
137
+ type: 'text',
138
+ name: 'email',
139
+ message: 'Email:',
140
+ validate: (v) => v.includes('@') || 'Enter a valid email',
141
+ },
142
+ {
143
+ type: 'password',
144
+ name: 'password',
145
+ message: 'Password:',
146
+ validate: (v) => v.length >= 8 || 'Password must be at least 8 characters',
147
+ },
148
+ {
149
+ type: 'password',
150
+ name: 'confirmPassword',
151
+ message: 'Confirm password:',
152
+ },
153
+ ]);
154
+ if (!email || !password)
155
+ return null;
156
+ if (password !== confirmPassword) {
157
+ error('Passwords do not match');
158
+ return null;
159
+ }
160
+ const spinner = ora('Creating account...').start();
65
161
  try {
66
- const hostname = require('os').hostname();
67
- const platform = process.platform;
68
- const cwd = process.cwd();
69
- const response = await fetch(`${config.apiUrl}/api/v1/systems/register`, {
162
+ const response = await fetch(`${apiUrl}/api/v1/auth/signup`, {
70
163
  method: 'POST',
71
- headers: {
72
- 'Content-Type': 'application/json',
73
- 'Authorization': config.apiKey ? `Bearer ${config.apiKey}` : '',
74
- },
75
- body: JSON.stringify({
76
- name: hostname,
77
- platform,
78
- working_directory: cwd,
79
- }),
164
+ headers: { 'Content-Type': 'application/json' },
165
+ body: JSON.stringify({ email, password }),
80
166
  });
81
167
  if (!response.ok) {
82
- throw new Error(`Registration failed: ${response.status} ${response.statusText}`);
168
+ const data = await response.json().catch(() => ({}));
169
+ spinner.fail('Sign up failed');
170
+ error(data.message || `HTTP ${response.status}`);
171
+ return null;
83
172
  }
84
173
  const data = await response.json();
85
- return data.system_id;
174
+ spinner.succeed('Account created');
175
+ return data;
86
176
  }
87
- catch (error) {
88
- throw new Error(`Failed to register system: ${error instanceof Error ? error.message : String(error)}`);
177
+ catch (err) {
178
+ spinner.fail('Connection failed');
179
+ error(err instanceof Error ? err.message : String(err));
180
+ return null;
89
181
  }
90
182
  }
183
+ async function runAuthFlow(apiUrl) {
184
+ const { authMethod } = await prompts({
185
+ type: 'select',
186
+ name: 'authMethod',
187
+ message: 'How would you like to authenticate?',
188
+ choices: [
189
+ { title: 'Sign up (new account)', value: 'signup' },
190
+ { title: 'Login with email/password', value: 'email' },
191
+ { title: 'Login with device code (browser)', value: 'device' },
192
+ { title: 'Skip (use existing API key)', value: 'skip' },
193
+ ],
194
+ });
195
+ switch (authMethod) {
196
+ case 'signup':
197
+ return signUp(apiUrl);
198
+ case 'email':
199
+ return authenticateWithEmailPassword(apiUrl);
200
+ case 'device':
201
+ return authenticateWithDeviceCode(apiUrl);
202
+ case 'skip':
203
+ return null;
204
+ default:
205
+ return null;
206
+ }
207
+ }
208
+ // -----------------------------------------------------------------------------
209
+ // Service Configuration
210
+ // -----------------------------------------------------------------------------
211
+ async function configureServices() {
212
+ header('Service Configuration');
213
+ info('Configure optional integrations for enhanced functionality.\n');
214
+ // Context7
215
+ console.log(chalk.bold('Context7') + ' - Library documentation fetching');
216
+ console.log(chalk.dim('Provides up-to-date docs for npm packages, frameworks, etc.\n'));
217
+ const { context7ApiKey } = await prompts({
218
+ type: 'text',
219
+ name: 'context7ApiKey',
220
+ message: 'Context7 API key (leave blank to skip):',
221
+ });
222
+ // Sequential Thinking
223
+ console.log('\n' + chalk.bold('Sequential Thinking') + ' - Planning and reasoning MCP');
224
+ console.log(chalk.dim('Enables structured problem-solving and task planning.\n'));
225
+ const { sequentialThinking } = await prompts({
226
+ type: 'confirm',
227
+ name: 'sequentialThinking',
228
+ message: 'Enable Sequential Thinking?',
229
+ initial: true,
230
+ });
231
+ return {
232
+ context7ApiKey: context7ApiKey || undefined,
233
+ sequentialThinkingEnabled: sequentialThinking,
234
+ };
235
+ }
91
236
  // -----------------------------------------------------------------------------
92
- // Setup Wizard
237
+ // Claude Code Integration
238
+ // -----------------------------------------------------------------------------
239
+ function ensureClaudeDir() {
240
+ if (!existsSync(CLAUDE_DIR)) {
241
+ mkdirSync(CLAUDE_DIR, { recursive: true });
242
+ }
243
+ }
244
+ function backupFile(path) {
245
+ if (existsSync(path)) {
246
+ const backupPath = `${path}.backup.${Date.now()}`;
247
+ copyFileSync(path, backupPath);
248
+ return backupPath;
249
+ }
250
+ return null;
251
+ }
252
+ async function configureMcpSettings(services) {
253
+ header('Claude Code MCP Configuration');
254
+ ensureClaudeDir();
255
+ // Read existing config
256
+ let mcpConfig = { mcpServers: {} };
257
+ if (existsSync(MCP_CONFIG_PATH)) {
258
+ try {
259
+ mcpConfig = JSON.parse(readFileSync(MCP_CONFIG_PATH, 'utf-8'));
260
+ const backup = backupFile(MCP_CONFIG_PATH);
261
+ if (backup) {
262
+ info(`Backed up existing config to ${backup}`);
263
+ }
264
+ }
265
+ catch {
266
+ error('Could not parse existing mcp.json, creating new one');
267
+ }
268
+ }
269
+ // Ensure mcpServers object exists
270
+ if (!mcpConfig.mcpServers || typeof mcpConfig.mcpServers !== 'object') {
271
+ mcpConfig.mcpServers = {};
272
+ }
273
+ const servers = mcpConfig.mcpServers;
274
+ // Add claudetools
275
+ servers['claudetools_memory'] = {
276
+ command: 'claudetools',
277
+ };
278
+ success('Added claudetools_memory server');
279
+ // Add context7 if configured
280
+ if (services.context7ApiKey) {
281
+ servers['context7'] = {
282
+ command: 'npx',
283
+ args: ['-y', '@context7/mcp-server'],
284
+ env: {
285
+ CONTEXT7_API_KEY: services.context7ApiKey,
286
+ },
287
+ };
288
+ success('Added context7 server');
289
+ }
290
+ // Add sequential-thinking if enabled
291
+ if (services.sequentialThinkingEnabled) {
292
+ servers['sequential-thinking'] = {
293
+ command: 'npx',
294
+ args: ['-y', '@modelcontextprotocol/server-sequential-thinking'],
295
+ };
296
+ success('Added sequential-thinking server');
297
+ }
298
+ // Write config
299
+ writeFileSync(MCP_CONFIG_PATH, JSON.stringify(mcpConfig, null, 2));
300
+ success(`Saved MCP config to ${MCP_CONFIG_PATH}`);
301
+ }
302
+ async function installHooks() {
303
+ header('Claude Code Hooks Installation');
304
+ ensureClaudeDir();
305
+ if (!existsSync(HOOKS_DIR)) {
306
+ mkdirSync(HOOKS_DIR, { recursive: true });
307
+ }
308
+ // User prompt submit hook - injects context before each message
309
+ const userPromptHook = `#!/bin/bash
310
+ # ClaudeTools Context Injection Hook
311
+ # Automatically injects relevant memory context before each prompt
312
+
313
+ # Prevent recursion
314
+ LOCK_FILE="/tmp/claude-prompt-hook.lock"
315
+ if [ -f "$LOCK_FILE" ]; then exit 0; fi
316
+ touch "$LOCK_FILE"
317
+ trap "rm -f $LOCK_FILE" EXIT
318
+
319
+ # Skip if disabled
320
+ if [ "$CLAUDE_DISABLE_HOOKS" = "1" ]; then exit 0; fi
321
+
322
+ # Read config
323
+ CONFIG_FILE="$HOME/.claudetools/config.json"
324
+ if [ ! -f "$CONFIG_FILE" ]; then exit 0; fi
325
+
326
+ API_URL=$(jq -r '.apiUrl // "https://api.claudetools.dev"' "$CONFIG_FILE")
327
+ API_KEY=$(jq -r '.apiKey // empty' "$CONFIG_FILE")
328
+
329
+ if [ -z "$API_KEY" ]; then exit 0; fi
330
+
331
+ # Get current project
332
+ PROJECT_FILE="$HOME/.claudetools/projects.json"
333
+ CWD=$(pwd)
334
+ PROJECT_ID=""
335
+
336
+ if [ -f "$PROJECT_FILE" ]; then
337
+ PROJECT_ID=$(jq -r --arg cwd "$CWD" '.[$cwd] // empty' "$PROJECT_FILE")
338
+ fi
339
+
340
+ # Inject context (silent fail)
341
+ curl -s -X POST "$API_URL/api/v1/context/inject" \\
342
+ -H "Authorization: Bearer $API_KEY" \\
343
+ -H "Content-Type: application/json" \\
344
+ -d "{\\"project_id\\": \\"$PROJECT_ID\\", \\"cwd\\": \\"$CWD\\"}" \\
345
+ 2>/dev/null || true
346
+ `;
347
+ const userPromptPath = join(HOOKS_DIR, 'user-prompt-submit.sh');
348
+ if (existsSync(userPromptPath)) {
349
+ const backup = backupFile(userPromptPath);
350
+ if (backup)
351
+ info(`Backed up existing hook to ${backup}`);
352
+ }
353
+ writeFileSync(userPromptPath, userPromptHook, { mode: 0o755 });
354
+ success('Installed user-prompt-submit.sh hook');
355
+ // Post tool use hook - logs tool usage for learning
356
+ const postToolHook = `#!/bin/bash
357
+ # ClaudeTools Tool Usage Logger
358
+ # Logs tool executions for pattern learning
359
+
360
+ # Prevent recursion
361
+ LOCK_FILE="/tmp/claude-tool-hook.lock"
362
+ if [ -f "$LOCK_FILE" ]; then exit 0; fi
363
+ touch "$LOCK_FILE"
364
+ trap "rm -f $LOCK_FILE" EXIT
365
+
366
+ # Skip if disabled
367
+ if [ "$CLAUDE_DISABLE_HOOKS" = "1" ]; then exit 0; fi
368
+
369
+ # Read input from stdin
370
+ INPUT=$(cat)
371
+
372
+ # Read config
373
+ CONFIG_FILE="$HOME/.claudetools/config.json"
374
+ if [ ! -f "$CONFIG_FILE" ]; then exit 0; fi
375
+
376
+ API_URL=$(jq -r '.apiUrl // "https://api.claudetools.dev"' "$CONFIG_FILE")
377
+ API_KEY=$(jq -r '.apiKey // empty' "$CONFIG_FILE")
378
+
379
+ if [ -z "$API_KEY" ]; then exit 0; fi
380
+
381
+ # Log tool usage (silent fail)
382
+ curl -s -X POST "$API_URL/api/v1/tools/log" \\
383
+ -H "Authorization: Bearer $API_KEY" \\
384
+ -H "Content-Type: application/json" \\
385
+ -d "$INPUT" \\
386
+ 2>/dev/null || true
387
+ `;
388
+ const postToolPath = join(HOOKS_DIR, 'post-tool-use.sh');
389
+ if (existsSync(postToolPath)) {
390
+ const backup = backupFile(postToolPath);
391
+ if (backup)
392
+ info(`Backed up existing hook to ${backup}`);
393
+ }
394
+ writeFileSync(postToolPath, postToolHook, { mode: 0o755 });
395
+ success('Installed post-tool-use.sh hook');
396
+ }
397
+ // -----------------------------------------------------------------------------
398
+ // Verification
399
+ // -----------------------------------------------------------------------------
400
+ async function verifySetup(config) {
401
+ header('Verification');
402
+ const spinner = ora('Checking API connection...').start();
403
+ try {
404
+ const response = await fetch(`${config.apiUrl}/health`, {
405
+ headers: config.apiKey ? { Authorization: `Bearer ${config.apiKey}` } : {},
406
+ signal: AbortSignal.timeout(10000),
407
+ });
408
+ if (response.ok) {
409
+ spinner.succeed('API connection verified');
410
+ }
411
+ else {
412
+ spinner.warn('API returned non-OK status');
413
+ }
414
+ }
415
+ catch {
416
+ spinner.fail('Could not connect to API');
417
+ }
418
+ // Check MCP config exists
419
+ if (existsSync(MCP_CONFIG_PATH)) {
420
+ success('MCP config installed');
421
+ }
422
+ else {
423
+ error('MCP config not found');
424
+ }
425
+ // Check hooks installed
426
+ if (existsSync(join(HOOKS_DIR, 'user-prompt-submit.sh'))) {
427
+ success('Hooks installed');
428
+ }
429
+ else {
430
+ error('Hooks not found');
431
+ }
432
+ }
433
+ // -----------------------------------------------------------------------------
434
+ // Main Setup Flow
93
435
  // -----------------------------------------------------------------------------
94
436
  export async function runSetup() {
95
- console.log('\n🧠 ClaudeTools Memory Setup\n');
96
- console.log('This wizard will guide you through the initial configuration.\n');
437
+ console.log('\n' + chalk.bold.cyan(' ClaudeTools Setup Wizard') + '\n');
438
+ console.log(' ' + chalk.dim('Persistent AI memory for Claude Code') + '\n');
97
439
  try {
98
- // 1. Ensure config directory exists
440
+ // Ensure config directory exists
99
441
  await ensureConfigDir();
100
- // 2. Load existing config or use defaults
442
+ // Load existing config
101
443
  const loadedConfig = await loadConfigFromFile();
102
- // Merge with defaults to ensure all fields exist
103
444
  let config = { ...DEFAULT_CONFIG, ...loadedConfig };
104
- // 3. API URL configuration
105
- console.log('━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━');
106
- console.log('API Configuration');
107
- console.log('━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\n');
108
- const useDefaultApi = await confirm(`Use default API URL (${DEFAULT_CONFIG.apiUrl})?`, true);
109
- if (!useDefaultApi) {
110
- const customApiUrl = await prompt('Enter custom API URL');
111
- if (customApiUrl) {
112
- config.apiUrl = customApiUrl;
113
- }
114
- }
115
- // 4. API Key configuration
116
- console.log('\n━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━');
117
- console.log('Authentication');
118
- console.log('━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\n');
445
+ // Step 1: Authentication
446
+ header('Authentication');
119
447
  if (config.apiKey) {
120
- console.log(`Current API key: ${config.apiKey.substring(0, 10)}...`);
121
- const replaceKey = await confirm('Replace existing API key?', false);
122
- if (replaceKey) {
123
- const newApiKey = await prompt('Enter new API key (from claudetools.dev/dashboard)');
124
- if (newApiKey) {
125
- config.apiKey = newApiKey;
448
+ info(`Existing API key found: ${config.apiKey.substring(0, 10)}...`);
449
+ const { replace } = await prompts({
450
+ type: 'confirm',
451
+ name: 'replace',
452
+ message: 'Replace existing authentication?',
453
+ initial: false,
454
+ });
455
+ if (replace) {
456
+ const auth = await runAuthFlow(config.apiUrl || DEFAULT_CONFIG.apiUrl);
457
+ if (auth) {
458
+ config.apiKey = auth.token;
459
+ success(`Logged in as ${auth.email}`);
126
460
  }
127
461
  }
128
462
  }
129
463
  else {
130
- console.log('Get your API key from: https://claudetools.dev/dashboard\n');
131
- const apiKey = await prompt('Enter API key (leave blank to skip)');
132
- if (apiKey) {
133
- config.apiKey = apiKey;
464
+ const auth = await runAuthFlow(config.apiUrl || DEFAULT_CONFIG.apiUrl);
465
+ if (auth) {
466
+ config.apiKey = auth.token;
467
+ success(`Logged in as ${auth.email}`);
134
468
  }
469
+ else {
470
+ // Manual API key entry
471
+ const { apiKey } = await prompts({
472
+ type: 'text',
473
+ name: 'apiKey',
474
+ message: 'Enter API key manually (from claudetools.dev/dashboard):',
475
+ });
476
+ if (apiKey) {
477
+ config.apiKey = apiKey;
478
+ }
479
+ }
480
+ }
481
+ // Step 2: Service Configuration
482
+ const services = await configureServices();
483
+ // Store service configs in extended config
484
+ const extendedConfig = config;
485
+ if (services.context7ApiKey) {
486
+ extendedConfig.context7ApiKey = services.context7ApiKey;
135
487
  }
136
- // 5. Validate API connection
137
- console.log('\n━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━');
138
- console.log('Validation');
139
- console.log('━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\n');
140
- console.log('Validating API connection...');
141
- const isValid = await validateApiConnection(config.apiUrl || DEFAULT_CONFIG.apiUrl, config.apiKey);
142
- if (!isValid) {
143
- console.error('❌ Could not connect to API.');
144
- console.error(' Check your API URL and key, or continue without validation.\n');
145
- const continueAnyway = await confirm('Continue anyway?', false);
146
- if (!continueAnyway) {
147
- console.log('\nSetup cancelled.');
148
- process.exit(1);
488
+ extendedConfig.sequentialThinkingEnabled = services.sequentialThinkingEnabled;
489
+ // Step 3: Save ClaudeTools config
490
+ header('Saving Configuration');
491
+ await saveConfig(extendedConfig);
492
+ success(`Configuration saved to ${getConfigPath()}`);
493
+ // Step 4: Configure Claude Code MCP
494
+ await configureMcpSettings(services);
495
+ // Step 5: Install Hooks
496
+ await installHooks();
497
+ // Step 6: Verify
498
+ await verifySetup(extendedConfig);
499
+ // Done
500
+ header('Setup Complete');
501
+ console.log(chalk.green(' ClaudeTools is now configured!\n'));
502
+ console.log(' ' + chalk.bold('Next step:') + ' Restart Claude Code\n');
503
+ console.log(' The memory system will activate automatically.\n');
504
+ }
505
+ catch (err) {
506
+ console.error('\n' + chalk.red('Setup failed:'), err instanceof Error ? err.message : String(err));
507
+ process.exit(1);
508
+ }
509
+ }
510
+ // -----------------------------------------------------------------------------
511
+ // Uninstall
512
+ // -----------------------------------------------------------------------------
513
+ export async function runUninstall() {
514
+ console.log('\n' + chalk.bold.red(' ClaudeTools Uninstall') + '\n');
515
+ const { confirm } = await prompts({
516
+ type: 'confirm',
517
+ name: 'confirm',
518
+ message: 'Remove ClaudeTools from Claude Code?',
519
+ initial: false,
520
+ });
521
+ if (!confirm) {
522
+ console.log('Cancelled.');
523
+ return;
524
+ }
525
+ // Remove from MCP config
526
+ if (existsSync(MCP_CONFIG_PATH)) {
527
+ try {
528
+ const mcpConfig = JSON.parse(readFileSync(MCP_CONFIG_PATH, 'utf-8'));
529
+ if (mcpConfig.mcpServers) {
530
+ delete mcpConfig.mcpServers['claudetools_memory'];
531
+ writeFileSync(MCP_CONFIG_PATH, JSON.stringify(mcpConfig, null, 2));
532
+ success('Removed from MCP config');
149
533
  }
150
534
  }
151
- else {
152
- console.log(' API connection validated');
535
+ catch {
536
+ error('Could not update MCP config');
153
537
  }
154
- // 6. Auto-inject context configuration
155
- console.log('\n━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━');
156
- console.log('Context Injection');
157
- console.log('━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\n');
158
- console.log('Auto-inject context automatically adds relevant memory to every request.');
159
- console.log('This uses tokens but provides more contextual responses.\n');
160
- const enableAutoInject = await confirm('Enable automatic context injection?', config.autoInjectContext);
161
- config.autoInjectContext = enableAutoInject;
162
- // 7. System registration (optional)
163
- if (isValid && config.apiKey) {
164
- console.log('\n━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━');
165
- console.log('System Registration');
166
- console.log('━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\n');
167
- const shouldRegister = await confirm('Register this system?', true);
168
- if (shouldRegister) {
169
- try {
170
- console.log('Registering system...');
171
- const systemId = await registerSystem(config);
172
- console.log(`✅ System registered: ${systemId}`);
173
- }
174
- catch (error) {
175
- console.error(`❌ Registration failed: ${error instanceof Error ? error.message : String(error)}`);
176
- console.error(' You can continue without registration.');
177
- }
538
+ }
539
+ // Remove hooks
540
+ const hooks = ['user-prompt-submit.sh', 'post-tool-use.sh'];
541
+ for (const hook of hooks) {
542
+ const hookPath = join(HOOKS_DIR, hook);
543
+ if (existsSync(hookPath)) {
544
+ const content = readFileSync(hookPath, 'utf-8');
545
+ if (content.includes('ClaudeTools')) {
546
+ const { unlinkSync } = await import('fs');
547
+ unlinkSync(hookPath);
548
+ success(`Removed ${hook}`);
178
549
  }
179
550
  }
180
- // 8. Save configuration
181
- console.log('\n━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━');
182
- console.log('Saving Configuration');
183
- console.log('━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\n');
184
- await saveConfig(config);
185
- console.log(`✅ Configuration saved to ${getConfigPath()}`);
186
- // 9. Show next steps
187
- console.log('\n━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━');
188
- console.log('Next Steps');
189
- console.log('━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\n');
190
- console.log('1. Add to Claude Code MCP settings:');
191
- console.log(' Location: ~/.claude/mcp.json\n');
192
- console.log(' {');
193
- console.log(' "mcpServers": {');
194
- console.log(' "claudetools-memory": {');
195
- console.log(' "command": "claudetools-memory"');
196
- console.log(' }');
197
- console.log(' }');
198
- console.log(' }\n');
199
- console.log('2. Restart Claude Code\n');
200
- console.log('Done! 🎉\n');
201
- }
202
- catch (error) {
203
- console.error('\n❌ Setup failed:', error instanceof Error ? error.message : String(error));
204
- process.exit(1);
205
551
  }
552
+ console.log('\n' + chalk.green('ClaudeTools removed from Claude Code.'));
553
+ console.log(chalk.dim('Your ~/.claudetools/ config and data are preserved.\n'));
206
554
  }
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@claudetools/tools",
3
- "version": "0.1.1",
4
- "description": "MCP server for Claude Code - persistent AI memory, task management, and codebase intelligence",
3
+ "version": "0.2.0",
4
+ "description": "Persistent AI memory, task management, and codebase intelligence for Claude Code",
5
5
  "type": "module",
6
6
  "main": "dist/index.js",
7
7
  "bin": {
@@ -46,10 +46,14 @@
46
46
  "access": "public"
47
47
  },
48
48
  "dependencies": {
49
- "@modelcontextprotocol/sdk": "^1.0.0"
49
+ "@modelcontextprotocol/sdk": "^1.0.0",
50
+ "chalk": "^5.6.2",
51
+ "ora": "^9.0.0",
52
+ "prompts": "^2.4.2"
50
53
  },
51
54
  "devDependencies": {
52
55
  "@types/node": "^20.10.0",
56
+ "@types/prompts": "^2.4.9",
53
57
  "@vitest/ui": "^4.0.15",
54
58
  "tsx": "^4.7.0",
55
59
  "typescript": "^5.3.0",