@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 +32 -0
- package/commands/init.js +126 -4
- package/lib/provider-detector.js +22 -4
- package/package.json +1 -1
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
|
-
|
|
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
|
}
|
package/lib/provider-detector.js
CHANGED
|
@@ -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 });
|