diff --git a/app/runner.py b/app/runner.py index 55650c8..67ec1f4 100644 --- a/app/runner.py +++ b/app/runner.py @@ -36,8 +36,8 @@ async def execute_command(args:list[str], cwd:Path, stdin:str|None, timeout:floa timed_out = True duration_ms = int((time.perf_counter() - started) * 1000) - stdout = stdout_b.decode(errors="replace") - stderr = stderr_b.decode(errors="replace") + stdout = stdout_b.decode(errors="replace").rstrip() + stderr = stderr_b.decode(errors="replace").rstrip() exit_code = process.returncode if process.returncode is not None else -1 if timed_out: diff --git a/packages/n8n-nodes-obsidian-api/nodes/ObsidianApi/ObsidianApi.node.ts b/packages/n8n-nodes-obsidian-api/nodes/ObsidianApi/ObsidianApi.node.ts index 95337d7..b008622 100644 --- a/packages/n8n-nodes-obsidian-api/nodes/ObsidianApi/ObsidianApi.node.ts +++ b/packages/n8n-nodes-obsidian-api/nodes/ObsidianApi/ObsidianApi.node.ts @@ -260,7 +260,16 @@ export class ObsidianApi implements INodeType { } const result = await executeObsidianCommand(this, itemIndex, args, { vault, timeout }); - returnData.push({ json: result, pairedItem: { item: itemIndex } }); + + // Always keep n8n output minimal/clean. + // - Prefer parsed stdout (e.g. vault/vaults) + // - Otherwise keep stdout as-is + const stdoutValue = (result as any).parsed_stdout ?? result.stdout; + const out: any = { stdout: stdoutValue }; + if (result.stderr) out.stderr = result.stderr; + if (result.timed_out) out.timed_out = result.timed_out; + + returnData.push({ json: out, pairedItem: { item: itemIndex } }); } catch (error) { if (this.continueOnFail()) { const message = error instanceof Error ? error.message : String(error); diff --git a/packages/n8n-nodes-obsidian-api/package-lock.json b/packages/n8n-nodes-obsidian-api/package-lock.json index b0b1a61..520588a 100644 --- a/packages/n8n-nodes-obsidian-api/package-lock.json +++ b/packages/n8n-nodes-obsidian-api/package-lock.json @@ -1,12 +1,12 @@ { "name": "n8n-nodes-obsidian-cli-api", - "version": "0.2.1", + "version": "0.2.2", "lockfileVersion": 3, "requires": true, "packages": { "": { "name": "n8n-nodes-obsidian-cli-api", - "version": "0.2.1", + "version": "0.2.2", "license": "MIT", "devDependencies": { "@types/node": "^24.0.0", diff --git a/packages/n8n-nodes-obsidian-api/package.json b/packages/n8n-nodes-obsidian-api/package.json index 1681f98..9ff084b 100644 --- a/packages/n8n-nodes-obsidian-api/package.json +++ b/packages/n8n-nodes-obsidian-api/package.json @@ -1,6 +1,6 @@ { "name": "n8n-nodes-obsidian-cli-api", - "version": "0.2.1", + "version": "0.2.2", "description": "n8n community nodes for the Obsidian API Docker service backed by the official Obsidian CLI.", "license": "MIT", "homepage": "https://git.yiprawr.dev/Docker/obsidian-api", diff --git a/packages/n8n-nodes-obsidian-api/shared/ObsidianApiClient.ts b/packages/n8n-nodes-obsidian-api/shared/ObsidianApiClient.ts index c129994..35a01b7 100644 --- a/packages/n8n-nodes-obsidian-api/shared/ObsidianApiClient.ts +++ b/packages/n8n-nodes-obsidian-api/shared/ObsidianApiClient.ts @@ -14,6 +14,7 @@ export interface ObsidianCommandResult extends IDataObject { stderr: string; duration_ms: number; timed_out: boolean; + parsed_stdout?: IDataObject; } export interface ObsidianCredentials extends IDataObject { @@ -72,6 +73,9 @@ export async function executeObsidianCommand( } const result = normalizeCommandResult(response); + const parsed = maybeParseStdout(result); + if (parsed) result.parsed_stdout = parsed; + if (result.exit_code !== 0) { throw new NodeOperationError( context.getNode(), @@ -139,6 +143,42 @@ function extractErrorMessage(body: unknown): string { return ''; } +function maybeParseStdout(result: ObsidianCommandResult): IDataObject | undefined { + const lastArg = result.command[result.command.length - 1]; + if (lastArg !== 'vault' && lastArg !== 'vaults') return undefined; + + // Obsidian CLI prints either: + // vault: + // keyvalue\nkeyvalue\n... + // vaults: + // often one vault per line (names) or a similar tabular output. + const lines = (result.stdout || '') + .split(/\r?\n/) + .map((l) => l.trim()) + .filter(Boolean); + + if (!lines.length) return undefined; + + const isKv = lines.some((l) => l.includes('\t')); + if (!isKv && lastArg === 'vaults') { + return { vaults: lines }; + } + + const kv: Record = {}; + for (const line of lines) { + const parts = line.split('\t'); + if (parts.length < 2) continue; + const key = parts[0].trim(); + const rawValue = parts.slice(1).join('\t').trim(); + if (!key || !rawValue) continue; + + if (/^-?\d+$/.test(rawValue)) kv[key] = Number(rawValue); + else kv[key] = rawValue; + } + + return Object.keys(kv).length ? kv : undefined; +} + function normalizeCommandResult(response: unknown): ObsidianCommandResult { const data = extractResponseBody(response); const result = typeof data === 'string' ? JSON.parse(data) : data; diff --git a/pyproject.toml b/pyproject.toml index 71da8a7..5dfcfde 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -1,6 +1,6 @@ [project] name = "obsidian-api" -version = "1.0.2" +version = "1.0.3" description = "HTTP API wrapper for running Obsidian CLI commands from n8n, SDKs, or agent harnesses." requires-python = ">=3.14" dependencies = [ diff --git a/uv.lock b/uv.lock index f61e04d..5dac073 100644 --- a/uv.lock +++ b/uv.lock @@ -123,7 +123,7 @@ wheels = [ [[package]] name = "obsidian-api" -version = "1.0.0" +version = "1.0.3" source = { virtual = "." } dependencies = [ { name = "starlette" },