/**
 * @fileoverview Obsidian Local REST API service. Wraps every upstream HTTP
 * endpoint we use, builds the right URL/headers/body for the consolidated
 * `target` discriminator, and classifies errors for the framework.
 * @module services/obsidian/obsidian-service
 */
import type { Context } from '@cyanheads/mcp-ts-core';
import { type Dispatcher, type RequestInit, fetch as undiciFetch } from 'undici';
import { type ServerConfig } from '../../config/server-config.js';
import { PathPolicy } from './path-policy.js';
import type { DocumentMap, FileListing, NoteJson, NoteTarget, ObsidianCommand, ObsidianTag, OmnisearchHit, PatchHeaders, StructuredSearchHit, TextSearchHit, VaultStatus } from './types.js';
type UndiciResponse = Awaited<ReturnType<typeof undiciFetch>>;
/**
 * The HTTP fetch contract this service depends on. Defaults to undici's
 * `fetch`; tests inject a stub here instead of mocking the `undici` module
 * (Bun's runtime treats `undici` as a builtin, so `vi.mock('undici')` has no
 * effect under `bunx vitest`).
 */
export type ObsidianFetch = (url: string, init: RequestInit & {
    dispatcher?: Dispatcher;
    signal?: AbortSignal;
}) => Promise<UndiciResponse>;
export declare class ObsidianService {
    #private;
    /**
     * @param config - Validated server config (api key, base URL, TLS, timeouts).
     * @param fetchImpl - Optional fetch override for tests. Defaults to undici's
     *   `fetch`, which honors the constructed TLS dispatcher in production.
     */
    constructor(config: ServerConfig, fetchImpl?: ObsidianFetch);
    /** Path-policy accessor — used by `obsidian_search_notes` to filter hits. */
    get policy(): PathPolicy;
    /** Resolved Omnisearch URL (derived from baseUrl or OBSIDIAN_OMNISEARCH_URL override). */
    get omnisearchUrl(): string;
    getStatus(ctx: Context): Promise<VaultStatus>;
    /**
     * Probe whether the configured `OBSIDIAN_API_KEY` is accepted. Hits the
     * authenticated `/vault/` listing endpoint and reports `true` only on a 2xx
     * response. Network/auth errors yield `false` — the resource caller wants a
     * boolean, not an exception. Aborts are re-thrown so cancellation/timeout
     * doesn't masquerade as an auth failure.
     */
    probeAuthenticated(ctx: Context): Promise<boolean>;
    getNoteContent(ctx: Context, target: NoteTarget): Promise<string>;
    getNoteJson(ctx: Context, target: NoteTarget): Promise<NoteJson>;
    /**
     * Resolve `target` to a vault-relative path. For path targets this is a
     * no-op; for `active` and `periodic` targets we have to ask upstream which
     * concrete file is currently in play.
     */
    resolvePath(ctx: Context, target: NoteTarget): Promise<string>;
    getDocumentMap(ctx: Context, target: NoteTarget): Promise<DocumentMap>;
    writeNote(ctx: Context, target: NoteTarget, content: string, contentType?: 'markdown' | 'json'): Promise<void>;
    appendToNote(ctx: Context, target: NoteTarget, content: string, contentType?: 'markdown' | 'json'): Promise<void>;
    patchNote(ctx: Context, target: NoteTarget, content: string, headers: PatchHeaders): Promise<void>;
    deleteNote(ctx: Context, target: NoteTarget): Promise<void>;
    /**
     * Byte size of a note at `target`, derived from the HEAD `Content-Length`
     * header. Returns `null` on 404 — distinct from a 0-byte file.
     *
     * Source-of-truth rule for note byte sizes across mutating tools:
     *   1. HEAD `Content-Length` (this method)       — when no GET is in flight.
     *   2. `Buffer.byteLength(deliveredContent)`     — when a GET happens anyway (free).
     *   3. `note.stat.size` from the JSON envelope   — REJECTED: shares the upstream
     *      `getAbstractFileByPath` cache path with the rest of the envelope, so it
     *      can't act as an independent cross-check (cache-desync scenario in
     *      coddingtonbear/obsidian-local-rest-api#237). Always prefer delivered
     *      bytes or HEAD over the metadata field.
     *
     * Bypasses retries (a 404 is the answer, not a transient failure) and
     * gates readable on path targets before issuing the HEAD.
     */
    tryGetSize(ctx: Context, target: NoteTarget): Promise<number | null>;
    /**
     * Like `tryGetSize`, but throws `note_missing` on 404 — for verification
     * reads that come *after* a write where the file is expected to exist.
     */
    getSize(ctx: Context, target: NoteTarget): Promise<number>;
    listFiles(ctx: Context, dirPath?: string): Promise<FileListing>;
    listTags(ctx: Context): Promise<ObsidianTag[]>;
    listCommands(ctx: Context): Promise<ObsidianCommand[]>;
    searchText(ctx: Context, query: string, contextLength?: number): Promise<TextSearchHit[]>;
    searchJsonLogic(ctx: Context, logic: Record<string, unknown>): Promise<StructuredSearchHit[]>;
    /**
     * One-shot startup probe for the Omnisearch plugin's HTTP endpoint. Returns
     * `true` only when the response is `HTTP 200`, declares `application/json`,
     * and the body parses as a JSON array — unrouted paths on the Omnisearch
     * server also return `200` with an empty body, so status alone is
     * insufficient. The entry point passes the return value into the
     * `obsidian_search_notes` factory to decide whether to expose the
     * `omnisearch` mode.
     */
    probeOmnisearch(signal?: AbortSignal): Promise<boolean>;
    /**
     * Query the Omnisearch HTTP endpoint. Normalizes the response on the way
     * out: decodes HTML entities + `<br>` → `\n` in `excerpt`, renames `path`
     * to `filename`, and drops `vault`. Throws `omnisearch_unreachable`
     * (ServiceUnavailable) on network failures or non-2xx responses — the
     * plugin can shut down mid-session (Obsidian quits, plugin disabled), and
     * the tool needs a distinct signal from the upstream's success cases.
     */
    searchOmnisearch(ctx: Context, query: string): Promise<OmnisearchHit[]>;
    executeCommand(ctx: Context, commandId: string): Promise<void>;
    openInUi(ctx: Context, path: string, opts?: {
        newLeaf?: boolean;
    }): Promise<void>;
}
/**
 * Encode a vault-relative path for the URL. Splits on `/` and `\` (so
 * Windows-style separators are honored), URL-encodes each segment, and
 * rejoins with `/` since the Local REST API plugin expects forward slashes.
 *
 * Rejects `.` and `..` segments here rather than relying on the upstream Local
 * REST API plugin to normalize them — `PathPolicy` short-circuits to "allow"
 * when `OBSIDIAN_READ_PATHS` is unset, and `..` is unreserved per RFC 3986 so
 * `encodeURIComponent` leaves it intact. This is the single chokepoint before
 * URL construction, so guard vault escape here. Backslash is treated as a
 * separator so `..\..\etc` traverses identically to `../../etc` and can't
 * sneak past as a single opaque segment.
 */
export declare function encodeVaultPath(path: string): string;
export declare function initObsidianService(config?: ServerConfig, fetchImpl?: ObsidianFetch): void;
/** Test-only: directly install an instance (e.g., one backed by a stub fetch). */
export declare function setObsidianService(service: ObsidianService | undefined): void;
export declare function getObsidianService(): ObsidianService;
export {};
//# sourceMappingURL=obsidian-service.d.ts.map