/** * In-place edits against a single `.md` file inside the vault. * * Ported from the patch/window-edit logic in aaronsb/obsidian-mcp-plugin * (`src/utils/obsidian-api.ts` patchHeading/patchFrontmatter and * `src/tools/window-edit.ts`), with these simplifications: * - No Obsidian metadata-cache; heading lookup is a regex over lines. * - No block-id (^blockId) targeting. * - No content-buffer recovery UX; caller handles errors. * - Frontmatter round-trips through gray-matter so YAML structure * (arrays, nested objects) is preserved. */ export type EditMode = { kind: 'append'; content: string; } | { kind: 'prepend'; content: string; } | { kind: 'replace_window'; search: string; content: string; fuzzy?: boolean; fuzzyThreshold?: number; } | { kind: 'patch_heading'; heading: string; content: string; op?: 'replace' | 'after' | 'before'; scope?: 'section' | 'body'; /** * 0-indexed position among matching headings. When the target text * appears more than once, `patch_heading` throws MultipleMatchesError * unless this is set — pipelines must not silently patch the wrong * section. Match order is top-to-bottom by source-file position. */ headingIndex?: number; } | { kind: 'patch_frontmatter'; key: string; value: unknown; } | { kind: 'at_line'; line: number; content: string; op?: 'before' | 'after' | 'replace'; }; export interface HeadingMatch { /** Full matched heading line as it appears in the file. */ heading: string; /** 1-indexed line number of the heading. */ line: number; } /** * Thrown by `patch_heading` when the target heading text matches more than * one heading and the caller didn't pass `headingIndex` to disambiguate. * `matches` lists every occurrence so the caller can pick one. */ export declare class MultipleMatchesError extends Error { readonly matches: HeadingMatch[]; constructor(matches: HeadingMatch[]); } export interface EditResult { path: string; bytesWritten: number; /** * Number of bytes consumed by this edit on the replacement path — i.e. how * much pre-existing content was removed before inserting `content`. For * insert-only modes (`append`, `prepend`, `at_line` with `op: 'before' | * 'after'`) this is always 0. Surfaced specifically so `patch_heading` * callers can detect when the default greedy `section` scope consumed more * than they expected (e.g. trailing content past a blank line on the last * heading). */ removedLen: number; diff: { before: string; after: string; }; } export type Apply = { next: string; at: number; len: number; removedLen: number; }; export declare function editNote(vaultPath: string, fileRelPath: string, mode: EditMode): Promise; export interface BulkEditResult { path: string; editsApplied: number; bytesWritten: number; before: string; after: string; } export declare function bulkEditNote(vaultPath: string, fileRelPath: string, modes: EditMode[]): Promise; export declare function applyEdit(s: string, mode: EditMode): Apply;