/** * `skaile preset export` — pure core: turn a project's own `skaile.yaml` * (`SkWorkspaceConfig`) into a shareable `PresetManifest`, with first-class * detection and redaction of inline secrets. * * These helpers are intentionally free of I/O and prompting so they can be * unit-tested in isolation; the Commander action in `preset-cmd.ts` owns file * reads/writes, the loud stderr summary, and interactive confirmation. */ import { type LockFile, type SkWorkspaceConfig, type SourceEntry } from "@skaile/workspaces/core"; import type { PresetManifest } from "@skaile/workspaces/types/manifests"; /** One inline-secret hit: which item, where in its config, and a safe preview. */ export interface SecretFinding { /** `id` of the offending preset item (connector/mcp id). */ itemId: string; /** Dotted path into the item's `config`, e.g. `options.dsn` or `env.TOKEN`. */ path: string; /** Value with its secret portion masked, safe to print to a terminal. */ maskedValue: string; } /** * Build a self-consistent {@link PresetManifest} from a workspace config. * * Mapping (a future `preset apply` reverses this): * - `dependencies[]` → `{ ref }` items, ref copied VERBATIM (no re-pinning). * - `connectors[]` → `{ id, ref: "connector:", config }` items. * - `mcp_servers[]` → `{ id, ref: "mcp:", config }` items. * * `agent_config` is deliberately NOT exported — it is personal/machine-specific * (driver, model, API-cloud transport) and does not belong in a shared preset. * * @param config - The project-own `skaile.yaml` (NOT the merged/global config). * @param name - Preset name; also the default output filename stem. * @param sources - Git sources the dep items resolve from (see {@link neededSources}). * @returns A manifest that passes `validatePreset` whenever it has ≥1 item. */ export declare function buildPresetFromWorkspace(config: SkWorkspaceConfig, name: string, sources?: SourceEntry[]): PresetManifest; /** * The subset of `config.sources` a cross-repo apply must register before install. * * The lock's top-level `sources` list is the full **transitive** closure of every * source that contributed a resolved asset, so filtering `config.sources` to it * (minus the always-available factory source) captures direct + transitive deps * with no drop risk. With no lock (provenance unknown), every non-factory source * is kept so apply still resolves. Connectors/mcp carry no source. */ export declare function neededSources(config: SkWorkspaceConfig, lock: LockFile | null): SourceEntry[]; /** * Of the captured `neededSources`, the ones whose assets resolve through a * MACHINE-LOCAL curated store manifest (`~/.skaile/store/manifests/.yaml`, * written by `source manifest init`). Those overlays are not shared with the * preset, so a recipient on another machine won't reproduce the same resolution * until the sidecar is itself published. Returns the affected source URLs (empty * when none carry a sidecar). */ export declare function sourcesWithSidecar(sources: SourceEntry[]): string[]; /** * True when `value` is a secret *reference* (indirection) rather than an inline * secret — e.g. `env:DATABASE_URL`, `forge:api-key`, `pat:env:GH_TOKEN`. Such * values are safe to publish and are never flagged. */ export declare function isSecretReference(value: string): boolean; /** True when a source URL embeds `user:pass@` credentials (private-HTTPS auth form). */ export declare function urlHasCredentials(url: string): boolean; /** * Strip `user:pass@` userinfo from a URL. The repo is fully identified without it * (git auth on apply uses the target's own credential helper / ssh key), so an * exported source URL must never carry the token into a shared preset. */ export declare function stripUrlCredentials(url: string): string; /** * Scan every string inside each item's `config` and return inline secrets. * * A string is flagged when it is NOT a {@link isSecretReference} AND sits in a * credential context: the inherent `auth` field, a suspicious key name, a * `proto://user:pass@` URL, or a long high-entropy blob. Items without a `config` * (bare deps) are skipped. * * Detection is **heuristic / best-effort**, not exhaustive: a short, low-entropy * secret under an innocuous key name can slip through. A clean scan is not a * guarantee — treat it as a safety net, not proof the file holds no secrets. */ export declare function scanInlineSecrets(preset: PresetManifest): SecretFinding[]; /** * Replace each flagged inline value with a `${INPUT:}` placeholder and add * a matching `secret` placeholder entry to the owning item. Returns a NEW preset * (the input is not mutated) plus the generated placeholder keys. * * `` is `_` upper-snake-cased so it is stable and readable. */ export declare function redactSecrets(preset: PresetManifest, findings: SecretFinding[]): { preset: PresetManifest; keys: string[]; }; /** Exact wording written into the file when unresolved inline secrets remain. */ export declare const INLINE_SECRETS_HEADER = "# \u26A0\uFE0F CONTAINS INLINE SECRETS \u2014 replace with placeholders before sharing or publishing"; /** * Header comment for a preset that still holds unresolved inline secrets, or * `null` when `findings` is empty (nothing left to warn about). */ export declare function secretHeaderComment(findings: SecretFinding[]): string | null; //# sourceMappingURL=preset-export.d.ts.map