Files
obsidian-mcp/src/cli/executor.ts
Peter.Morton c577c07877 refactor: update search tool to match Obsidian CLI spec
Removed obsidian_search_with_context tool (not in CLI spec)
Updated obsidian_search to use exact CLI parameter names:
- query (required) - Search query text
- path (optional) - Limit search to folder path
- limit (optional) - Max number of files to return
- total (optional) - Return match count instead of file list
- case (optional) - Case sensitive search
- format (optional) - Output format: text or json (default: text)

Changed parameter names to match CLI:
- folder → path
- caseSensitive → case
- Added: total flag for match counts
- Removed: contextLines (not in CLI)

Files updated:
- src/tools/search.ts: Simplified to single search tool
- src/validation/schemas.ts: Updated searchSchema parameters
- manifest.json: Removed search_with_context, updated description
- tasks.md: Marked T048 as REMOVED

Total tools: 20 (was 21)
- User Story 1: 9 tools
- User Story 2: 11 tools (was 12)

Build:  0 errors
Validation:  Manifest passes

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
2026-03-22 12:26:17 -05:00

118 lines
2.9 KiB
TypeScript

/**
* CLI Executor for Obsidian commands
* Constitutional Principle IV: Defensive Programming
* Uses spawn for better streaming and timeout control
*/
import { spawn } from 'child_process';
import { CLICommand, CLIResult } from '../utils/types.js';
import { logger } from '../utils/logger.js';
import { getCommandTimeout } from '../config/timeouts.js';
/**
* Execute an Obsidian CLI command with timeout
*/
export async function executeCommand(cmd: CLICommand): Promise<CLIResult> {
const timeout = cmd.timeout || getCommandTimeout(cmd.command);
logger.debug('Executing CLI command', {
command: cmd.command,
argCount: cmd.args.length,
timeout,
});
return new Promise((resolve) => {
const child = spawn(cmd.command, cmd.args, {
cwd: cmd.cwd || process.cwd(),
shell: true,
});
let stdout = '';
let stderr = '';
let timedOut = false;
// Set timeout
const timeoutId = setTimeout(() => {
timedOut = true;
child.kill('SIGTERM');
logger.warn('CLI command timed out', {
command: cmd.command,
timeout,
});
}, timeout);
// Collect stdout
child.stdout?.on('data', (data) => {
stdout += data.toString();
});
// Collect stderr
child.stderr?.on('data', (data) => {
stderr += data.toString();
});
// Handle completion
child.on('close', (code) => {
clearTimeout(timeoutId);
const result: CLIResult = {
stdout: stdout.trim(),
stderr: stderr.trim(),
exitCode: code || 0,
timedOut,
};
if (result.exitCode === 0) {
logger.debug('CLI command succeeded', {
command: cmd.command,
outputLength: result.stdout.length,
});
} else {
logger.warn('CLI command failed', {
command: cmd.command,
exitCode: result.exitCode,
timedOut: result.timedOut,
});
}
resolve(result);
});
// Handle spawn errors
child.on('error', (error) => {
clearTimeout(timeoutId);
logger.error('CLI command spawn error', { error: error.message });
resolve({
stdout: '',
stderr: error.message,
exitCode: 1,
timedOut: false,
});
});
});
}
/**
* Execute Obsidian CLI command with vault context
*/
export async function executeObsidianCommand(
subcommand: string,
args: string[] = [],
options?: { timeout?: number }
): Promise<CLIResult> {
const vaultName = process.env.OBSIDIAN_VAULT;
if (!vaultName) {
throw new Error('OBSIDIAN_VAULT environment variable not set');
}
// Build full command: obsidian <subcommand> --vault <vault_name> <args>
const fullArgs = [subcommand, '--vault', vaultName, ...args];
return executeCommand({
command: '/Applications/Obsidian.app/Contents/MacOS/obsidian',
args: fullArgs,
timeout: options?.timeout,
});
}