/** * Save + propagation pipeline. * * 1. Validate every pending field via its `FieldDef.validate?.()`. * Any failure aborts the whole save (no partial writes); pending stays * intact for the user to fix. * 2. Persist secrets (`secret: true` fields) to the injected `SecretStore`. * Only the `keyLocation` reference is then written to config.json — the * raw value never lands there. * 3. Atomic-write ~/.wigolo/config.json (tmp file + rename) with 0o600. * 4. Fan out propagateable keys to every detected agent's `env:` block: * a. Back the agent file up to `/backups/-.json`. * b. Merge the propagation set into env, preserving every other key. * c. Atomic-write the agent config. * 5. On full success → `store.commit()`. Partial failures surface in * `SaveResult.failed` and pending is still committed (config.json is * durable; failed agent fan-out is fixable on retry without re-staging). * 6. Prune per-agent backups to the most recent 5. * * All filesystem access flows through the `WritableFs` interface so tests * can inject EACCES / rename-failure shapes without touching real permissions. */ import type { CategoryDef } from '../schema/types.js'; import type { SettingsStore } from './settings-store.js'; import type { AgentTarget } from './agent-targets.js'; export interface SaveOpts { store: SettingsStore; catalog: ReadonlyArray; /** ~/.wigolo/config.json (or test-tmp path). */ configPath: string; agents: ReadonlyArray; secretStore: SecretStore; /** Optional injection point for tests. Defaults to `defaultWritableFs()`. */ fs?: WritableFs; } export interface SaveResult { /** settings keys persisted to config.json + secretStore (excludes failures). */ saved: string[]; /** Agent IDs whose env block was successfully updated. */ propagated: string[]; /** Per-agent failures, with reason for display. */ failed: Array<{ agentId: string; reason: string; }>; /** Validation errors, if any. Save is aborted when present. */ errors?: Array<{ key: string; reason: string; }>; } export interface SecretStore { /** Persist a secret. Returns where it landed (keychain or encrypted file). */ set(key: string, value: string): Promise<{ location: 'keychain' | 'file'; }>; /** Read a secret. Returns null when not stored. */ get(key: string): Promise; /** Remove a stored secret. No-op when absent. */ remove(key: string): Promise; } export interface FsStat { isFile(): boolean; isDirectory(): boolean; isSymbolicLink(): boolean; } export interface WritableFs { readFile(path: string): Promise; writeFile(path: string, data: string): Promise; rename(from: string, to: string): Promise; mkdir(path: string, opts?: { recursive?: boolean; mode?: number; }): Promise; readdir(path: string): Promise; unlink(path: string): Promise; stat(path: string): Promise; /** Like stat, but does not follow symlinks. Used to refuse symlinked configs. */ lstat(path: string): Promise; } export declare function defaultWritableFs(): WritableFs; export declare function save(opts: SaveOpts): Promise; export interface InstallAgentOpts { target: AgentTarget; /** Env block to seed under target.envPath. Existing keys are preserved. */ env: Readonly>; fs?: WritableFs; } export interface InstallAgentResult { ok: boolean; reason?: string; } export declare function installAgent(opts: InstallAgentOpts): Promise; export interface UninstallOpts { target: AgentTarget; secretStore: SecretStore; fs?: WritableFs; } export interface UninstallResult { ok: boolean; reason?: string; } export declare function uninstallAgent(opts: UninstallOpts): Promise; //# sourceMappingURL=propagation.d.ts.map