import type { IExecuteFunctions, IDataObject, IHttpRequestOptions, } from 'n8n-workflow'; import { NodeOperationError } from 'n8n-workflow'; import { toCleanApiError } from './errors'; import { maybeParseStdout, normalizeCommandResult } from './response'; export interface ObsidianCommandResult extends IDataObject { id: string; command: string[]; cwd: string; exit_code: number; stdout: string; stderr: string; duration_ms: number; timed_out: boolean; parsed_stdout?: IDataObject; } export interface ObsidianCredentials extends IDataObject { baseUrl: string; apiToken: string; defaultVault?: string; } export function compactArgs(args: Array): string[] { return args.filter((arg): arg is string => typeof arg === 'string' && arg.length > 0); } export function kv(name: string, value: string | number | boolean | undefined | null): string | undefined { if (value === undefined || value === null || value === '') return undefined; if (typeof value === 'boolean') return value ? name : undefined; return `${name}=${value}`; } export function withVault(args: string[], vault?: string): string[] { if (!vault || args[0]?.startsWith('vault=')) return args; return [`vault=${vault}`, ...args]; } export async function executeObsidianCommand( context: IExecuteFunctions, itemIndex: number, args: string[], options: { vault?: string; timeout?: number; stdin?: string } = {}, ): Promise { const credentials = await context.getCredentials('obsidianApi') as ObsidianCredentials; const baseUrl = String(credentials.baseUrl).replace(/\/$/, ''); const vault = options.vault || String(credentials.defaultVault || ''); const finalArgs = withVault(args, vault); const request: IHttpRequestOptions = { method: 'POST', url: `${baseUrl}/commands`, headers: { Authorization: `Bearer ${credentials.apiToken}`, 'Content-Type': 'application/json', }, body: { args: finalArgs, stdin: options.stdin || undefined, timeout: options.timeout, }, json: true, returnFullResponse: false, }; let response: unknown; try { response = await context.helpers.httpRequest(request) as unknown; } catch (error) { throw toCleanApiError(context, error, itemIndex); } const result = normalizeCommandResult(response); const parsed = maybeParseStdout(result); if (parsed) result.parsed_stdout = parsed; if (result.exit_code !== 0) { throw new NodeOperationError( context.getNode(), `Obsidian CLI command failed with exit code ${result.exit_code}: ${result.stderr || result.stdout}`, { itemIndex }, ); } return result; }