import path from 'node:path'; import { MemoryExtensionPublisher, PublisherCapabilities, PublishContext, PublishResult } from '@remnic/core'; /** * Host-specific parameters for a Pi-family memory extension publisher. * * The Remnic runtime extension is host-neutral (it only uses Pi's extension * hooks, which omp preserves as a superset), so the only things that vary * between hosts are *where* the extension is installed, *which* connector * token it uses, and *how* it is labelled. Everything else — atomic writes, * rollback, symlink guards, config merge — is shared. */ interface HostPublisherDescriptor { readonly hostId: string; readonly connectorId: string; readonly displayName: string; readonly tokenGenerateHint: string; resolveAgentHome(env: NodeJS.ProcessEnv): string; resolveExtensionRoot(env: NodeJS.ProcessEnv): string; /** * Optional: every agent home `unpublish` should sweep for a stale extension, * beyond the one resolved from the current env. Hosts with env-sensitive * install locations (e.g. omp profiles) provide this so `remnic connectors * remove` cleans up even when the remove-time env differs from install time. */ listRemovalAgentHomes?(env: NodeJS.ProcessEnv): string[]; } /** * Shared publisher for Pi-family hosts. Concrete hosts (Pi, omp) subclass this * with a {@link HostPublisherDescriptor}; the install/rollback machinery is * identical across hosts. */ declare class HostMemoryExtensionPublisher implements MemoryExtensionPublisher { private readonly host; static readonly capabilities: PublisherCapabilities; protected constructor(host: HostPublisherDescriptor); /** * File basenames this publisher owns inside the extension root. The shared * set is config + wrapper + readme; subclasses add host-specific files * (e.g. omp's pre-bundle loader + package manifest). Used for snapshot, * atomic-write rollback, and unpublish cleanup. */ protected get ownedFileNames(): readonly string[]; /** * Directory names this publisher owns inside the extension root (build * outputs). Recursively removed on unpublish and on publish rollback when * newly created. */ protected get ownedDirNames(): readonly string[]; /** * Whether the generated wrapper must use a bun-buildable import specifier * (relative path) instead of a file:// URL. omp pre-bundles the wrapper with * `bun build`, which cannot resolve file:// specifiers; pi loads the wrapper * directly via tsx and keeps the file:// URL. */ protected get usesBundledWrapper(): boolean; /** * Hook for subclasses to write host-specific files and run install-time * build steps after the shared config/wrapper/readme are written. Runs * inside the publish try-block: a throw triggers full rollback. */ protected finalizePublish(_ctx: PublishContext, _extensionRoot: string, _paths: { configPath: string; wrapperPath: string; pluginPiDistPath: string; }): void; get hostId(): string; resolveExtensionRoot(env?: NodeJS.ProcessEnv): Promise; isHostAvailable(): Promise; renderInstructions(ctx: PublishContext): Promise; publish(ctx: PublishContext): Promise; unpublish(): Promise; } /** Publisher for upstream Pi (`~/.pi/agent/extensions/remnic`). */ declare class PiMemoryExtensionPublisher extends HostMemoryExtensionPublisher { constructor(); } /** Publisher for Oh My Pi / omp (`~/.omp/agent/extensions/remnic`). */ declare class OmpMemoryExtensionPublisher extends HostMemoryExtensionPublisher { constructor(); protected get ownedFileNames(): readonly string[]; protected get ownedDirNames(): readonly string[]; protected get usesBundledWrapper(): boolean; protected finalizePublish(ctx: PublishContext, extensionRoot: string, paths: { configPath: string; wrapperPath: string; pluginPiDistPath: string; }): void; /** * Pre-bundles the omp extension with `bun build` so omp's embedded runtime * never resolves bare npm specifiers (e.g. @sinclair/typebox) from the * extension's node_modules at load time. The bundle is written to a temp * directory and swapped into dist-bundle/ on success. The pre-existing * dist-bundle is renamed aside (not removed) before the swap, so a failure * during the final rename restores the previously working bundle rather than * leaving the install with no bundle at all. * * Override in tests to skip the real bun invocation. */ protected runBundleBuild(ctx: PublishContext, extensionRoot: string, bunBin: string): void; } /** * Resolves the import specifier the omp wrapper uses to reach the * `@remnic/plugin-pi` dist entry from the generated `index.ts`. omp pre-bundles * that wrapper with `bun build`, whose bundler cannot resolve `file://` * specifiers ("Could not resolve: file://…" on Bun 1.2–1.3, verified), so the * specifier must be a relative path. On Windows, when the extension directory * and the plugin-pi install sit on different drives, `path.relative` cannot * express a relative path and returns an absolute drive path (e.g. `D:\…`); * prefixing `./` then yields an invalid module specifier that fails `bun build` * with a cryptic error. Detect that layout and fail fast with an actionable * message instead. (Cross-drive omp installs are unsupported because neither a * relative specifier nor a `file://` URL is acceptable to `bun build`.) Drive * roots are compared case-insensitively so a same-drive Windows install is not * falsely rejected when the agent home and the plugin-pi install report the * drive letter in different casing (`C:\\` vs `c:\\`). * * Exported so the cross-drive guard can be exercised on non-Windows hosts via * `path.win32`. */ declare function resolveOmpWrapperImportSpecifier(extensionModulePath: string, wrapperDir: string, pathApi?: typeof path): string; /** * Walks `PATH` the way a shell does and returns the first `bun` executable it * finds, as a realpath-resolved absolute path (or null when nothing on PATH * is an executable `bun`). Used so the install-time PATH probe can embed an * absolute bun path in the generated loader/postinstall instead of the bare * string `"bun"`, which would break self-heal rebuilds under a stripped * runtime PATH (GUI/service launches). Mirrors `which(1)`; no dependency. */ declare function resolveBunOnPath(): string | null; /** * Resolves the `bun` binary for the install-time pre-bundle. Honours * `REMNIC_OMP_BUN_BIN` (test/override seam), then PATH, then common locations. * Returns null when bun is unavailable so the caller can fail with guidance. */ declare function resolveBunBinary(): string | null; export { HostMemoryExtensionPublisher, type HostPublisherDescriptor, OmpMemoryExtensionPublisher, PiMemoryExtensionPublisher, resolveBunBinary, resolveBunOnPath, resolveOmpWrapperImportSpecifier };