@unifiedmemory/cli 1.1.0 → 1.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/CHANGELOG.md CHANGED
@@ -7,6 +7,38 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
7
7
 
8
8
  ## [Unreleased]
9
9
 
10
+ ## [1.2.0] - 2026-01-08
11
+
12
+ ### Added
13
+
14
+ - **commands/init.js** - Smart default project name based on directory detection
15
+ - Project name prompt now defaults to the parent top-level directory name
16
+ - Automatically detects project root by walking up directories looking for markers:
17
+ - `.git` directory (Git repository)
18
+ - `package.json` (Node.js project)
19
+ - `pyproject.toml` (Python project)
20
+ - `Cargo.toml` (Rust project)
21
+ - `.um` directory (already initialized)
22
+ - Works correctly from nested subdirectories (e.g., `/project/src/components/`)
23
+ - Max depth limit of 5 levels prevents walking too far up (e.g., to home directory)
24
+ - Users can press Enter to accept default or type custom name
25
+ - Graceful fallback to current directory name if no markers found within 5 levels
26
+ - New helper functions: `findProjectRoot()` and `getDefaultProjectName()`
27
+ - No new dependencies - uses built-in Node.js `path` and `fs` modules
28
+
29
+ - **commands/init.js, lib/provider-detector.js** - Auto-authorize MCP tools in Claude Code during init
30
+ - Dynamically fetches available MCP tools from the gateway during `um init`
31
+ - Prompts user once to pre-authorize all UnifiedMemory tools (search_notes, create_note, create_topic, etc.)
32
+ - If user agrees, adds tool permissions to `.claude/settings.local.json` in format `mcp__unifiedmemory__<toolname>`
33
+ - Future-proof: Automatically includes new tools as they're added to the MCP server
34
+ - Single yes/no question with clear explanation of what's being authorized
35
+ - Graceful error handling: Skips permission setup if API unavailable, continues with init
36
+ - Idempotent: Won't duplicate existing permissions if run multiple times
37
+ - Preserves existing permissions (e.g., Bash commands) when adding new ones
38
+ - New helper function: `fetchMCPToolPermissions()` in commands/init.js
39
+ - Updated `ClaudeProvider.updateClaudeSettings()` to write permissions array
40
+ - Eliminates need for manual "Allow" clicks on every MCP tool use
41
+
10
42
  ### Fixed
11
43
 
12
44
  - **commands/init.js** - Enhanced error handling for 401/403/network errors during project fetching
package/commands/init.js CHANGED
@@ -8,6 +8,52 @@ import { loadAndRefreshToken } from '../lib/token-validation.js';
8
8
  import { login } from './login.js';
9
9
  import { ProviderDetector } from '../lib/provider-detector.js';
10
10
 
11
+ /**
12
+ * Find project root directory by looking for common markers
13
+ * @param {string} startDir - Directory to start searching from
14
+ * @returns {string} Project root directory path
15
+ */
16
+ function findProjectRoot(startDir = process.cwd()) {
17
+ let currentDir = startDir;
18
+ const root = path.parse(currentDir).root;
19
+ let levelsUp = 0;
20
+ const MAX_DEPTH = 5; // Only walk up 5 levels to avoid finding distant markers
21
+
22
+ while (currentDir !== root && levelsUp < MAX_DEPTH) {
23
+ // Check for common project markers
24
+ const markers = [
25
+ path.join(currentDir, '.git'),
26
+ path.join(currentDir, 'package.json'),
27
+ path.join(currentDir, 'pyproject.toml'),
28
+ path.join(currentDir, 'Cargo.toml'),
29
+ path.join(currentDir, '.um'),
30
+ ];
31
+
32
+ // If any marker exists, this is the project root
33
+ for (const marker of markers) {
34
+ if (fs.existsSync(marker)) {
35
+ return currentDir;
36
+ }
37
+ }
38
+
39
+ // Move up one directory
40
+ currentDir = path.dirname(currentDir);
41
+ levelsUp++;
42
+ }
43
+
44
+ // Fallback: return the starting directory
45
+ return startDir;
46
+ }
47
+
48
+ /**
49
+ * Get default project name based on directory
50
+ * @returns {string} Default project name
51
+ */
52
+ function getDefaultProjectName() {
53
+ const projectRoot = findProjectRoot();
54
+ return path.basename(projectRoot);
55
+ }
56
+
11
57
  export async function init(options = {}) {
12
58
  console.log(chalk.cyan('\nšŸš€ UnifiedMemory Initialization\n'));
13
59
 
@@ -35,9 +81,15 @@ export async function init(options = {}) {
35
81
  // Step 3: Save project config
36
82
  await saveProjectConfig(authData, projectData);
37
83
 
84
+ // Step 3.5: Fetch available MCP tools for permissions
85
+ let mcpToolPermissions = null;
86
+ if (!options.skipConfigure) {
87
+ mcpToolPermissions = await fetchMCPToolPermissions(authData, projectData);
88
+ }
89
+
38
90
  // Step 4: Configure AI tools
39
91
  if (!options.skipConfigure) {
40
- await configureProviders(authData, projectData);
92
+ await configureProviders(authData, projectData, mcpToolPermissions);
41
93
  }
42
94
 
43
95
  console.log(chalk.green('\nāœ… Initialization complete!\n'));
@@ -228,11 +280,14 @@ async function selectOrCreateProject(authData, options) {
228
280
  }]);
229
281
 
230
282
  if (action === 'create') {
283
+ const defaultName = getDefaultProjectName();
284
+
231
285
  const { name, description } = await inquirer.prompt([
232
286
  {
233
287
  type: 'input',
234
288
  name: 'name',
235
289
  message: 'Project name:',
290
+ default: defaultName,
236
291
  validate: input => input.length > 0 || 'Name is required',
237
292
  },
238
293
  {
@@ -246,11 +301,14 @@ async function selectOrCreateProject(authData, options) {
246
301
  } else {
247
302
  if (projects.length === 0) {
248
303
  console.log(chalk.yellow('No projects found. Creating first project...'));
304
+ const defaultName = getDefaultProjectName();
305
+
249
306
  const { name, description } = await inquirer.prompt([
250
307
  {
251
308
  type: 'input',
252
309
  name: 'name',
253
310
  message: 'Project name:',
311
+ default: defaultName,
254
312
  validate: input => input.length > 0 || 'Name is required',
255
313
  },
256
314
  {
@@ -440,7 +498,66 @@ async function saveProjectConfig(authData, projectData) {
440
498
  }
441
499
  }
442
500
 
443
- async function configureProviders(authData, projectData) {
501
+ /**
502
+ * Fetch MCP tools and prompt user for permission to pre-authorize
503
+ * @param {Object} authData - Auth data with tokens
504
+ * @param {Object} projectData - Project data
505
+ * @returns {Promise<Array<string>|null>} - Array of permission strings or null if declined
506
+ */
507
+ async function fetchMCPToolPermissions(authData, projectData) {
508
+ try {
509
+ // Build auth headers (similar to lib/mcp-server.js)
510
+ const authHeaders = {
511
+ 'Authorization': `Bearer ${authData.access_token}`,
512
+ 'X-Org-Id': authData.org_id,
513
+ 'X-User-Id': authData.user_id,
514
+ };
515
+
516
+ // Fetch tools from gateway
517
+ const { fetchRemoteMCPTools } = await import('../lib/mcp-proxy.js');
518
+ const projectContext = {
519
+ project_id: projectData.project_id,
520
+ org_id: authData.org_id,
521
+ };
522
+
523
+ const toolsResult = await fetchRemoteMCPTools(authHeaders, projectContext);
524
+ const tools = toolsResult.tools || [];
525
+
526
+ if (tools.length === 0) {
527
+ return null; // No tools available
528
+ }
529
+
530
+ // Format tool names for display
531
+ const toolNames = tools.map(t => t.name).join(', ');
532
+
533
+ // Prompt user
534
+ console.log(chalk.cyan('\nšŸ”§ MCP Tool Permissions\n'));
535
+ console.log(chalk.gray(`Found ${tools.length} available tools: ${toolNames}`));
536
+
537
+ const { allowPermissions } = await inquirer.prompt([{
538
+ type: 'confirm',
539
+ name: 'allowPermissions',
540
+ message: 'Pre-authorize these UnifiedMemory tools in Claude Code? (Recommended)',
541
+ default: true,
542
+ }]);
543
+
544
+ if (!allowPermissions) {
545
+ console.log(chalk.yellow('⚠ Tools not pre-authorized. Claude will prompt for permission on first use.'));
546
+ return null;
547
+ }
548
+
549
+ // Convert tool names to permission format
550
+ const permissions = tools.map(tool => `mcp__unifiedmemory__${tool.name}`);
551
+ return permissions;
552
+
553
+ } catch (error) {
554
+ console.error(chalk.yellow(`⚠ Could not fetch MCP tools: ${error.message}`));
555
+ console.log(chalk.gray('Skipping permission setup. You can manually add permissions later.'));
556
+ return null;
557
+ }
558
+ }
559
+
560
+ async function configureProviders(authData, projectData, mcpToolPermissions = null) {
444
561
  console.log(chalk.cyan('\nšŸ”§ Configuring AI code assistants...\n'));
445
562
 
446
563
  // Pass current directory for project-level configs (like Claude Code)
@@ -456,8 +573,8 @@ async function configureProviders(authData, projectData) {
456
573
 
457
574
  // NEW APPROACH: Configure local MCP server (no tokens in config files)
458
575
  for (const provider of detected) {
459
- // Configure MCP server
460
- const mcpSuccess = provider.configureMCP();
576
+ // Configure MCP server (pass permissions for Claude Code)
577
+ const mcpSuccess = provider.configureMCP(mcpToolPermissions);
461
578
 
462
579
  // Configure memory instructions
463
580
  const instructionsResult = provider.configureMemoryInstructions?.();
@@ -465,6 +582,11 @@ async function configureProviders(authData, projectData) {
465
582
  // Display results
466
583
  if (mcpSuccess) {
467
584
  console.log(chalk.green(`āœ“ Configured ${provider.name} MCP server`));
585
+
586
+ // Show permission status for Claude Code
587
+ if (provider.name === 'Claude Code' && mcpToolPermissions && mcpToolPermissions.length > 0) {
588
+ console.log(chalk.green(` āœ“ Pre-authorized ${mcpToolPermissions.length} MCP tools`));
589
+ }
468
590
  } else {
469
591
  console.log(chalk.red(`āœ— Failed to configure ${provider.name} MCP`));
470
592
  }
@@ -59,9 +59,10 @@ class BaseProvider {
59
59
  }
60
60
  }
61
61
 
62
- configureMCP() {
62
+ configureMCP(toolPermissions = null) {
63
63
  // NEW APPROACH: Configure local server instead of HTTP
64
64
  // No parameters needed - local server reads from filesystem
65
+ // toolPermissions parameter for Claude Code compatibility (unused in base class)
65
66
  const config = this.readConfig() || { mcpServers: {} };
66
67
 
67
68
  config.mcpServers = config.mcpServers || {};
@@ -136,19 +137,19 @@ class ClaudeProvider extends BaseProvider {
136
137
  }
137
138
  }
138
139
 
139
- configureMCP() {
140
+ configureMCP(toolPermissions = null) {
140
141
  // Create .mcp.json at project root with local server config
141
142
  const success = super.configureMCP();
142
143
 
143
144
  if (success) {
144
145
  // Update .claude/settings.local.json to enable the MCP server
145
- this.updateClaudeSettings();
146
+ this.updateClaudeSettings(toolPermissions);
146
147
  }
147
148
 
148
149
  return success;
149
150
  }
150
151
 
151
- updateClaudeSettings() {
152
+ updateClaudeSettings(toolPermissions = null) {
152
153
  if (!this.settingsPath) return false;
153
154
 
154
155
  try {
@@ -169,6 +170,23 @@ class ClaudeProvider extends BaseProvider {
169
170
  settings.enabledMcpjsonServers.push('unifiedmemory');
170
171
  }
171
172
 
173
+ // Add tool permissions if provided
174
+ if (toolPermissions && Array.isArray(toolPermissions) && toolPermissions.length > 0) {
175
+ if (!settings.permissions) {
176
+ settings.permissions = { allow: [] };
177
+ }
178
+ if (!settings.permissions.allow) {
179
+ settings.permissions.allow = [];
180
+ }
181
+
182
+ // Add new permissions, avoiding duplicates
183
+ toolPermissions.forEach(permission => {
184
+ if (!settings.permissions.allow.includes(permission)) {
185
+ settings.permissions.allow.push(permission);
186
+ }
187
+ });
188
+ }
189
+
172
190
  // Write settings
173
191
  fs.ensureDirSync(this.claudeDir);
174
192
  fs.writeJSONSync(this.settingsPath, settings, { spaces: 2 });
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@unifiedmemory/cli",
3
- "version": "1.1.0",
3
+ "version": "1.2.0",
4
4
  "description": "UnifiedMemory CLI - AI code assistant integration",
5
5
  "main": "index.js",
6
6
  "type": "module",