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>
118 lines
2.9 KiB
TypeScript
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,
|
|
});
|
|
}
|