import type { Loop } from "../../types.js"; import { LOOP_BUNDLE_SCHEMA, type BundleManifest } from "./manifest.js"; import { type BundleEntry } from "./pack.js"; /** * The bundle sub-layer root. * * Resolved through the loops path resolver (`paths.ts` -> `app-home.ts`, the * @hasna/paths resolver), never a hard-coded string, so the XDG data home and * the `LOOPS_DATA_DIR` / `HASNA_LOOPS_DATA_DIR` exact-app overrides keep * working. `LOOPS_BUNDLE_ROOT` is a TEST-ONLY escape hatch — it is deliberately * not documented as an operator switch (no new *_STORAGE_MODE-shaped env, per * hasna/apps#1599). */ export declare function bundleRoot(env?: NodeJS.ProcessEnv): string; export declare function bundleDir(name: string, env?: NodeJS.ProcessEnv): string; export declare function ensureBundleRoot(env?: NodeJS.ProcessEnv): string; /** How a local directory came to hold what it holds. */ export type BundleMarkerSource = "pull" | "push" | "materialize"; /** * The pull marker. The ONLY proof a directory is loops-managed: a directory * without one is never deleted or overwritten by any remote answer. */ export interface BundleMarker { managedBy: "@hasna/loops"; bundle: string; loopId: string; version: number; pinnedVersion: number | null; bundleDigest: string; source: BundleMarkerSource; apiUrl?: string; syncedAt: string; } export declare function markerPath(dir: string): string; export declare function readBundleMarker(dir: string): BundleMarker | undefined; export declare function writeBundleMarker(dir: string, marker: Omit): void; /** Where a bundle's local state sits relative to what it was pulled/pushed as. */ export type BundleLocalState = "absent" | "unmanaged" | "clean" | "dirty"; export interface LocalBundle { name: string; dir: string; state: BundleLocalState; marker?: BundleMarker; manifest?: BundleManifest; /** Digest recomputed from the tree right now, absent when the directory has no files. */ digest?: string; /** Paths whose content or mode differs from `manifest.json`. Names only — never contents. */ changedPaths: string[]; } /** * Classify a bundle directory. * * `dirty` is decided against `manifest.json` (the file that travels with the * bundle), not against the marker, so a locally edited script is dirty even on * a station that never pulled — which is exactly the state the executor must * refuse to run. */ export declare function inspectLocalBundle(name: string, env?: NodeJS.ProcessEnv): LocalBundle; export interface LoopBundleDefinition extends Record { schema: typeof LOOP_BUNDLE_SCHEMA; id: string; name: string; status: string; schedule: unknown; target: unknown; } /** Project a loop row into the definition file. Definition fields only. */ export declare function loopToDefinition(loop: Loop): LoopBundleDefinition; /** * Parse a definition file. Unknown top-level keys are PRESERVED (a pull->push * round trip through an older CLI must not silently drop a field a newer server * wrote), while runtime keys are dropped outright. */ export declare function parseDefinition(value: unknown): LoopBundleDefinition; /** True when the definition's target is an agent target carrying a live prompt. */ export declare function definitionCarriesPrompt(definition: LoopBundleDefinition): boolean; export declare function serializeDefinition(definition: LoopBundleDefinition): string; export interface BuildManifestOptions { name: string; loopId: string; version: number; files: BundleManifest["files"]; archiveSha256?: string; carriesPrompt?: boolean; reason?: string; station?: string; agent?: string; now?: Date; } export declare function buildManifest(opts: BuildManifestOptions): BundleManifest; export declare function sourceStation(env?: NodeJS.ProcessEnv): string; export declare function sourceAgent(env?: NodeJS.ProcessEnv): string; /** * Install an entry set into `dir` atomically. * * Stage into a sibling temp directory, move the existing tree aside, rename the * staged tree in, then remove the backup. A crash therefore leaves EITHER the * old tree or the new one — never a half-written mixture, which for a directory * of executables would mean running a script from one version against a config * from another. */ export declare function installBundleTree(dir: string, entries: readonly BundleEntry[], manifest: BundleManifest, opts?: { onStaged?: (staged: string) => void; }): void; /** Create the skeleton `init` and `materialize` both produce. */ export declare function writeBundleSkeleton(dir: string, name: string, definition: LoopBundleDefinition, opts?: { readme?: string; now?: Date; reason?: string; }): BundleManifest; /** Recompute a directory's manifest in place after an edit (used by `init`/`materialize`). */ export declare function refreshManifest(dir: string, opts?: { version?: number; reason?: string; now?: Date; }): BundleManifest;