/** * Unified workspace configuration — `skaile.yaml` / `.skaile.yaml` * * Single file combining AI resources, data connectors, agent behavior, * runtime defaults, and workspace layout. * * ── File naming ────────────────────────────────────────────────────────── * * skaile.yaml → canonical default workspace config * staging.skaile.yaml → alternative workspace config * *.skaile.yaml → glob pattern for named configs * * ── Stacking ───────────────────────────────────────────────────────────── * * Files stack across scope levels (same name must match): * ~/.skaile/skaile.yaml priority 10 (user) * /app/skaile.yaml priority 20 (app) * /project/skaile.yaml priority 30 (project) * * ── Relationship to other files ────────────────────────────────────────── * * .skaile/settings.json User-specific overrides (API keys, personal * model preferences). Gitignored. Overrides * workspace config defaults at runtime. * * workspace.yaml Scaffold template. Generates skaile.yaml * at `skaile init` time. Never read at runtime. * * agent.yaml Agent definition (identity). Referenced from * skaile.yaml via `agent.definition`. */ import { type DriverTarget } from "./driver-targets.js"; /** * Named agent configuration profile. The "default" profile is the active one. * Personal overrides (apiKeys, model preference) live in .skaile/settings.json. * @docLink packages/core/workspace-config#agent-config-profile */ export interface AgentConfigProfile { /** Bridge driver to use: "claude-sdk", "codex", or "omp". */ driver?: string; /** Default LLM provider: "anthropic", "openai", "google", etc. */ provider?: string; /** * AI cloud transport for claude-sdk: `default | bedrock | vertex | azure | * gateway`. Absent means `default` (the provider's native API). Unknown * values warn at decode time but never fail — forward compat / tolerated- * unknown per the cloud-provider contract. * * @since 1.3.0 */ cloud?: string; /** * Non-secret transport settings for `cloud` (snake_case — this is the * skaile.yaml wire shape; use {@link cloudConfigFromProfile} for the * camelCase in-process shape). Secrets NEVER appear here — they ride the * `session_init` secrets map. * * @since 1.3.0 */ cloud_config?: { region?: string; project_id?: string; resource?: string; base_url?: string; }; /** Default model identifier. */ model?: string; /** Thinking mode for Claude models. */ thinking?: "adaptive" | "enabled" | "disabled"; /** Reasoning effort level. */ effort?: "low" | "medium" | "high" | string; /** Override framework install paths (rarely needed). */ skills_dir?: string; agents_dir?: string; prompts_dir?: string; } /** * Camel-cased in-process shape of an agent profile's `cloud_config` block. * Mirrors the bridge's `CloudConfig` (kept structurally identical; no import * to avoid a core ↔ bridge cycle). * * @since 1.3.0 */ export interface CloudTransportConfig { region?: string; projectId?: string; resource?: string; baseUrl?: string; } /** * Convert a profile's snake_case `cloud_config` wire block to the camelCase * in-process shape. The single sanctioned snake→camel conversion site — both * the settings cascade and the runner's serve path go through here so the * mapping can never drift. Returns `undefined` when nothing is set. * * @since 1.3.0 */ export declare function cloudConfigFromProfile(profile: AgentConfigProfile | undefined): CloudTransportConfig | undefined; /** * A startup directive — run on workspace launch. * * String form: "agent:research-assistant" — start the named agent * Object form: { "system-prompt-override": "..." } — replace system prompt * { "system-prompt-append": "..." } — append to system prompt * @docLink packages/core/workspace-config#startup-directive */ export type StartupDirective = string | Record; /** * An AI resource source with its own dependency list. * All declared dependencies are installed from this source's catalog. * @docLink packages/core/workspace-config#ai-resource-entry */ export interface AiResourceEntry { /** Source name (used as cache key and in CLI output). */ name: string; /** Path to local directory or GitHub repo URL. */ path: string; /** Git branch (default: main). */ branch?: string; /** Skill/agent/package/flow refs to install from this source. Syntax: "kind:name". */ dependencies?: string[]; /** Auto-deploy to framework dirs on install. */ auto_deploy?: boolean; } /** * Internal-only data shape used by the asset install pipeline (`repo-manager`, * `lock`, `runtime-assets`). Carries the resolved URL or path for a `sources[]` * entry. Not surfaced directly via the workspace config schema. * * NOTE: this type was named `RepositoryDeclaration` before 2026-05-31 (the * canonical-identity rename). The shape is unchanged — only the name moved * from a project-local "repository" concept to the source carrier. * * @docLink packages/core/workspace-config#source-declaration */ export interface SourceDeclaration { /** GitHub or git URL (for remote repos). */ url?: string; /** Local filesystem path (for local repos). */ path?: string; /** Git branch (default: main). */ branch?: string; /** Upstream URL for fork workflows. */ upstream?: string; /** * Built-in source shipped inside the package (`dist/factory-assets/`). Deploys * are **copied**, not symlinked, even though `path` is set: the bundled dir * lives under `node_modules` and is replaced on every upgrade, so a symlink * into it would dangle. Injected at runtime by `AssetManager`, never persisted * to `skaile.yaml`. */ factory?: boolean; } /** * One entry in a project's `sources:` list. Names the upstream git URL plus * an optional pin (tag, branch HEAD, or 40-char SHA). The publisher identity * is NOT carried here — it is intrinsic to the asset bytes, declared in the * source's own `skaile.yaml`. * * Persisted under the `sources:` key in `skaile.yaml`. Bytes live under * `/sources//` (unified machine-global clone cache, via * `getGlobalCacheDir()`) where the slug is derived from the URL. * * @docLink packages/core/workspace-config#source-entry */ export interface SourceEntry { /** Git URL (ssh or https). Cache key derives from this. */ url: string; /** Optional pin: branch | tag | 40-char sha. Defaults to HEAD of default branch. */ pin?: string; } /** * One entry in a project's `stores:` list. Catalog endpoint URL only. * The store backend authenticates publisher identity; no project-side * trust declaration applies. * * @docLink packages/core/workspace-config#store-entry */ export interface StoreEntry { /** Catalog base URL (e.g. `https://skaile.store`). */ url: string; } /** * One entry in `overrides:`. Pins a specific canonical ref to one source * URL when two or more candidates diverge on sha256. The `reason:` field * is REQUIRED and non-empty; empty or missing is a parse-time error. * * @docLink packages/core/workspace-config#override-entry */ export interface OverrideEntry { /** Canonical ref form: `/:@`. */ ref: string; /** One of the candidate source URLs. */ source: string; /** Non-empty justification. */ reason: string; } /** * A filesystem mount — projected storage backend. * @docLink packages/core/workspace-config#mount-declaration */ interface MountDeclaration { /** Unique identifier. Used as mount dir name. */ id: string; /** Mount driver: "local", "git", "s3", "webdav", "sharepoint". */ driver: string; /** What to mount: URL, bucket path, host filesystem path. */ source: string; /** Workspace-relative mount point (default: .mounts/). */ target?: string; /** Access level enforced by the mount driver. */ access?: "read-only" | "read-write"; /** Credential reference: "env:VAR_NAME" or inline value. */ auth?: string; /** * Whether to watch this mount for filesystem changes. * true = always watch, false = never watch, undefined = auto (watch if read-write). */ watch?: boolean; /** * **Expert-only.** When `true`, the mount manager wires the resolved * credential into the workspace so the agent's interactive `git`/`curl` * tools can authenticate directly against the mount's host. Defaults to * `false` — agents have no credential exposure and rely on driver-managed * pulls/pushes. See `_devlog/specs/2026-05-05-git-credential-tiers.md`. */ exposeAccessToken?: boolean; /** * Optional override for the access token TTL (seconds). When omitted, the * provider's natural TTL is used. Values above the provider's documented * maximum are silently capped. */ accessTokenTTL?: number; /** Driver-specific configuration (branch, region, etc.). */ options?: Record; } /** * A data connector — tool-accessed backend the agent interacts with. * @docLink packages/core/workspace-config#connector-declaration */ export interface ConnectorDeclaration { /** Unique identifier. Used as tool namespace. */ id: string; /** Connector adapter: "postgres", "redis", "sqlite", "memory", "xstate", "xstate-store", "yjs". */ driver: string; /** * Access level enforced by ConnectorManager. * Optional in config — ConnectorManager defaults to "read-only" when absent. */ access?: "read-only" | "read-write"; /** Credential reference: "env:VAR_NAME" or inline value. */ auth?: string; /** * Backend-mediated credential routing key. For backend-auth mounts the * platform serializes this top-level so the runner can mint/refresh the * connector token via `host.refresh_credential`. Must survive the * skaile.yaml → declaration mapping or live token refresh fails. */ providerLinkId?: string; /** * Operating mode for connectors that manage a working copy. Orthogonal to * faces. `agent` = the agent drives the source natively (e.g. native git) and * the connector only provisions short-lived credentials. `sync` = the * connector owns the working copy (clone + watch + auto-commit/pull). Absent = * the driver's legacy `options.{session,sync,lifecycle}` blocks decide. */ mode?: "agent" | "sync"; /** Adapter-specific configuration (dsn, host, etc.). */ options?: Record; /** Semver range for future catalog versioning, e.g. "^1.0.0". */ version?: string; /** * Run a health check (ping) when the connector is first connected. * Default: false. Set to true to surface connection errors early. */ health_check?: boolean; /** * Inject this connector's skill description into the agent's system prompt. * Default: false. */ expose_as_skill?: boolean; /** Tags for catalog browsing and skill routing. */ tags?: string[]; /** * Optional filesystem face — makes the connector "mountable". * When present, ConnectorManager allocates a mount directory and passes it * to the connector via `ConnectContext.mountTarget`. * * YAML key: `mount` (sub-block under a `connectors:` entry). * Replaces the legacy top-level `mounts:` key (hard cut — see Task 11). */ mount?: { /** Source URL or path (repo URL, bucket name, host path). */ source: string; /** Override mount target directory (default: `.mounts//`). */ target?: string; /** Watch flag: omitted → watch read-write, skip read-only. */ watch?: boolean; /** Expert-only: expose the resolved credential to the agent CLI. */ exposeAccessToken?: boolean; /** Optional access-token TTL override (seconds). */ accessTokenTTL?: number; }; } /** * Reference to a Nix-built asset recipe. * * Two source forms: * - **Platform-flake attr (legacy):** `{ attr }` selects an attribute path in * the platform flake (`/etc/skaile/flake/`), e.g. `mcps.excel`, `mcps.ppt`, * `stacks.baseline`. The leaf of `attr` doubles as the recipe-map lookup key. * - **Self-contained flake (BYO-flake):** `{ flake, attr? }` names a flake that * travels with the asset (`flake: "."`) or a remote URL for allowlisted * third-party publishers. The host-side builder resolves it to a store path. * Here `attr` is the in-flake BUILD target only (defaults to `"default"`); * the recipe **id** (the MCP declaration id) is the recipe-map / `${recipe:}` * marker key, never `attr`. * * Resolution happens at session start (cold) and on `reconfigure_mcps` (hot-add) * via the prebuilt recipe map → `nix path-info --offline` fallback. * * @docLink packages/core/concepts#asset-recipe */ export interface AssetRecipe { /** * Flake attribute to resolve/build. * - Legacy `{ attr }` form: required, e.g. `mcps.excel`. * - BYO-flake `{ flake, attr? }` form: optional, defaults to `"default"`. */ attr?: string; /** * Optional flake source. `"."` = the asset's own `flake.nix` (resolved * relative to the asset directory by the host-side builder). A flake URL * (`github:…`, `git+https://…`, `git+ssh://…`, `path:/…`) is permitted only * for publishers on the curated allowlist (enforced host-side). Absent = the * platform flake (legacy behaviour). */ flake?: string; /** * Publisher id that authored this recipe's flake. Required (host-side) when * `flake` is a remote URL; the builder checks it against the allowlist. * Defaults to the owning asset's publisher when `flake` is `"."`. */ publisher?: string; } /** Default in-flake build target for the BYO-flake form when `attr` is omitted. */ export declare const DEFAULT_RECIPE_ATTR = "default"; /** * Validates an `AssetRecipe.flake` source string. * * Accepted forms: * - `"."` — the asset's own flake directory (always allowed). * - a flake URL with a recognised scheme: `github:`, `git+https://`, * `git+ssh://`, `path:/` (absolute-only). * * Rejected: empty strings, local relative/absolute paths other than `"."` * (including `..` traversal and bare `/abs/path` — those must go through an * explicit `path:` URL), and unknown schemes. This guards the value before the * host-side builder interpolates it into `nix build "#"`; the * allowlist gate is a *separate* trust check, not a substitute for this. * * NOTE: this validates *shape*, not allowlist membership — the publisher * allowlist is enforced host-side by the builder, which has the allowlist file. * * @throws Error when `flake` is malformed */ export declare function validateAssetRecipeFlake(flake: string): void; /** * Validates an `AssetRecipe.attr` value against nix attribute path syntax. * * Nix attribute paths are dot-separated segments. Each segment must start with * a letter and contain only letters, digits, underscores, hyphens, and dots. * Characters like `#`, spaces, and nix expression fragments are explicitly * rejected — even though `spawnSync` array args prevent shell injection, nix * parses the combined `flakeRef#attr` string and invalid chars trigger * unintended evaluation paths. * * Deliberate restriction: leading digits per segment are not allowed even * though some nix attrs permit them, because they are rare in practice and * the restriction makes the validation straightforward to audit. * * @throws Error when `attr` is invalid */ export declare function validateAssetRecipeAttr(attr: string): void; /** * A declarative external MCP server — spawned or connected at session startup * and injected into the Claude SDK driver as a named `mcpServers` entry. * * Maps directly to the Claude Agent SDK's `McpServerConfig` union: * - `transport: "stdio"` (default) → `McpStdioServerConfig` — subprocess spawn * - `transport: "sse"` → `McpSSEServerConfig` — HTTP Server-Sent Events * - `transport: "http"` → `McpHttpServerConfig` — HTTP streaming * * The `id` becomes the key in the SDK's `mcpServers` record and also the MCP * tool prefix (e.g., `mcp__my-server__`). * @docLink packages/core/workspace-config#mcp-server-declaration */ export interface McpServerDeclaration { /** Unique identifier — used as the `mcpServers` record key and tool prefix. */ id: string; /** * Transport type. * Default: `"stdio"` (subprocess). */ transport?: "stdio" | "sse" | "http"; /** For `"stdio"`: the command to execute (e.g. `"npx"`, `"node"`, `"python"`). */ command?: string; /** For `"stdio"`: arguments passed to the command. */ args?: string[]; /** For `"stdio"`: environment variables injected into the subprocess. */ env?: Record; /** For `"sse"` and `"http"`: the server URL. */ url?: string; /** For `"sse"` and `"http"`: HTTP headers (e.g. `Authorization`). */ headers?: Record; /** * Credential resolution mode for a remote (sse/http) server. `"backend"` means * the platform mints + provisions the bearer (header `Authorization: * env:MCP____AUTH`) and the runner re-mints it on a 401. Written by the * platform; absent for local / static-token servers. */ auth?: string; /** * For a remote `auth: "backend"` server: the org `ProviderLink` whose OAuth * credential backs this server's bearer. The runner passes it as the re-mint * `id` on a 401. Written by the platform alongside `auth`. */ providerLinkId?: string; /** Short description of what this server provides (used in catalog). */ description?: string; /** Tags for catalog browsing and discovery. */ tags?: string[]; /** * Named launcher strategy for a local (stdio) server. Selectable + explicit; * default is inferred (recipe present → "recipe"; else a pass-through * "path"/"package-runner" command-shape). Only "recipe" carries resolution * logic; "package-runner" (npx/uvx/pip) and "path" are documented command * shapes the stdio transport already spawns as-is. Ignored for remote (sse/http). */ launcher?: "recipe" | "package-runner" | "path"; /** * Optional Nix recipe binding. When present, the runner resolves the * recipe's `/nix/store` out-path before subprocess spawn and substitutes * `${recipe:}` / `${recipe::}` / `${recipe::bin}` / * `${recipe::lib}` markers in `command`, `args`, and `env` values. * `` matches this declaration's `id` field. */ recipe?: AssetRecipe; /** Owner-authored instruction text from the materialized MCP.md body (preset custom instructions). */ instructions?: string; /** * Internal provenance, never authored in `skaile.yaml`: set when a sibling * `.instance.json` contributed **resolved secret values** to `env` (see * `foldInstanceEnv`). Those values are live plaintext credentials that must * stay in memory — the same reason `.instance.json` is never copied into * `.claude/skills/`. Any consumer that would persist a declaration to disk * (e.g. rendering it into a coding agent's MCP config) must skip a * declaration carrying this flag. */ instanceSecretsFolded?: boolean; } /** * Platform-level context describing the environment the agent operates in. * Rendered by the runner into a "## Platform" section of the system prompt. */ export interface AgentContextConfig { /** Platform identifier (e.g. "skaile"). Controls the rendered header sentence. */ platform?: string; /** Whether multiple users may interact in the same session. */ multi_user?: boolean; /** Session persistence model. */ session_model?: "persistent" | "ephemeral"; } export interface AgentConfig { /** * Agent definition reference. Resolved in order: * - Local path: ".skaile/agent" or "./my-agent" * - Catalog reference: "agent:" (resolved via asset manager catalog) * - Resource path: "ai-assets:///agents/" */ definition?: string; /** * Platform-level context (platform identifier, multi-user flag, session model). * Consumed by the runner to build the "## Platform" section of the system prompt. */ context?: AgentContextConfig; /** Agent runtime constraints. */ permissions?: AgentPermissions; /** Lifecycle hooks — shell commands run at specific points. */ hooks?: AgentHooks; /** Named subagent definitions available for delegation. */ subagents?: Record; /** * Control which framework fragments are included in the rendered system prompt. * true (default) — include built-in fragment * false — omit this fragment * string — path to a custom markdown file (relative to project root) * * Fragment IDs: "agent-mode" | "skill-discovery" | "connector-usage" | "handoff" */ fragments?: Record; /** * Additional markdown files appended at the end of every rendered agent system * prompt in this workspace. Paths relative to the project root (skaile.yaml). * * YAML key: `prompt-extensions` */ "prompt-extensions"?: string[]; /** * Free-form user-authored agent prompt. Plain text or markdown — no length * cap, no template substitution. Prepended immediately after the platform * context section and before the environment section in the assembled * system prompt (see runner's {@link assembleSystemPrompt}). * * Written by the platform's wake-time yaml serializer from the combined * project-level + session-level prompts stored on `SkaileConfigData.agent.prompt`. * Standalone CLI / forge sessions may set this directly in `skaile.yaml`. * * Spec: `docs/superpowers/specs/2026-05-13-platform-agent-prompt-design.md` * @since 2026-05 */ prompt?: string; } export interface AgentPermissions { /** Maximum agentic turns per query (default: 15). */ max_turns?: number; /** * Permission mode for Claude SDK driver. * 'auto' = bypassPermissions (default for automated flows). * 'interactive' = prompt for each tool use. */ permission_mode?: "auto" | "interactive"; /** Glob patterns the agent may NOT write to. Stacking: union across scopes. */ deny_write?: string[]; /** Glob patterns the agent may NOT read. Stacking: union across scopes. */ deny_read?: string[]; /** * Network egress policy. Codec is permissive — the platform decides * enforcement. */ network?: NetworkPolicy; } /** * Per-session egress policy: `open` allows everything, `off` allows only * platform-resolved LLM-provider endpoints, `allowlist` adds user-supplied * domains. `allowlist` field is ignored when `mode !== 'allowlist'`. */ export interface NetworkPolicy { mode: "open" | "off" | "allowlist"; allowlist?: string[]; } export interface AgentHooks { /** Run before a flow starts (after resources are connected). */ pre_flow?: HookEntry[]; /** Run after a flow completes (before resources disconnect). */ post_flow?: HookEntry[]; /** Run before each flow node executes. */ pre_node?: HookEntry[]; /** Run after each flow node completes successfully. */ post_node?: HookEntry[]; } export interface HookEntry { /** Human-readable hook name. */ name: string; /** Shell command to execute. */ run: string; /** Working directory relative to workspace root (default: workspace root). */ cwd?: string; /** Timeout in seconds (default: 60). */ timeout?: number; /** Continue flow if hook fails (default: false). */ continue_on_error?: boolean; } export interface SubagentConfig { /** What this subagent does. */ description: string; /** System prompt for the subagent. */ prompt: string; /** Tools this subagent may use (default: all). */ tools?: string[]; /** Tools this subagent may NOT use. */ disallowed_tools?: string[]; /** Model override. */ model?: string; } export interface WorkspaceLayoutConfig { /** Directories to ensure exist (created at scaffold time, verified at run time). */ directories?: string[]; /** Git configuration. */ git?: GitConfig; /** Post-scaffold setup commands (run once after `skaile init`). */ setup?: SetupEntry[]; /** Container configuration (Docker). */ container?: ContainerConfig; } export interface GitConfig { /** Initialize a git repo at scaffold time (default: true). */ init?: boolean; /** .gitignore entries. Stacking: concatenate + deduplicate. */ ignore?: string[]; } export interface SetupEntry { /** Human-readable name. */ name: string; /** Shell command to execute. */ run: string; /** Working directory relative to workspace root. */ cwd?: string; /** Continue if command fails (default: false). */ continue_on_error?: boolean; } export interface NixContainerConfig { /** * Nixpkgs package attribute names to include in the environment. * Example: ["nodejs_22", "bun", "git", "python3"] */ packages?: string[]; /** * Nixpkgs registry name used as a package prefix for `nix shell`. * MUST be a bare registry name like "nixpkgs" — not a channel path like * "nixpkgs/nixos-24.11" (which is not valid as a flake ref prefix). * Defaults to "nixpkgs". */ channel?: string; /** * Path to a Nix file (shell.nix / flake.nix) relative to the project root. * When set, `packages` and `channel` are ignored — the file is the source of truth. */ flake?: string; /** * Predefined named stack to resolve from the system stack registry (SKAILE_NIX_STACK_REGISTRY). * Overridden by `packages` if present. * Takes precedence over the parent `ContainerConfig.stack` field when nix mode is active. */ stack?: string; } export interface ContainerConfig { /** Enable Docker container generation. */ enabled?: boolean; /** Base Docker image. */ image?: string; /** System packages to install. */ packages?: string[]; /** Agent CLIs to install globally in the container. */ agent_clis?: string[]; /** Ports to expose. */ ports?: string[]; /** Environment variables to pass through. */ env?: string[]; /** Docker volume/bind mounts. */ mounts?: Array<{ type: "bind" | "volume"; source: string; target: string; }>; /** WebSocket port for IPC. */ ws_port?: number; /** * Named system stack. * - Docker mode: resolved to an image tag via `dockerImageMap` (SKAILE_DOCKER_IMAGE_MAP). * - Nix mode: resolved to a package list via `nixStackRegistry` (SKAILE_NIX_STACK_REGISTRY). * Overridden by `nix.stack` if both are present. */ stack?: string; /** Nix-specific environment configuration (nix session mode only). */ nix?: NixContainerConfig; } export interface SkWorkspaceConfig { /** Project name (defaults to directory basename). */ name?: string; /** Project description. */ description?: string; /** * Agent configuration profiles. The "default" profile provides runtime * defaults (framework, model, provider). Personal overrides live in * .skaile/settings.json and always take priority. * * YAML key: `agent-config` or `agent_config` */ agent_config?: Record; /** * Startup directives — executed when the workspace is launched. * Each item is either a string ("agent:name") or a map * ({ "system-prompt-override": "..." }). */ startup?: StartupDirective[]; /** * Asset dependencies using `kind:name@[#pin]` syntax. * Top-level flat list — sources and deps are separate concerns. * * YAML key: `dependencies` */ dependencies?: string[]; /** * Global cross-backend install set — the coding-agent backends a `skaile * install --global` fans `dependencies:` out to. Only meaningful in the * user-scope SSOT (`~/.skaile/skaile.yaml`). When absent, the backend set is * auto-detected from existing `~/.` dirs (see `resolveGlobalBackends`). * Each entry must be a `DriverTarget` (`claude-code` | `omp` | `codex`). * * YAML key: `global_backends` */ global_backends?: DriverTarget[]; /** * Github sources this project depends on. Each entry is `{url, pin?}`; the * cache slug is derived from the URL. `skaile install` clones any missing * entries; `skaile source add/remove/sync` are the CRUD surface. * * YAML key: `sources` */ sources?: SourceEntry[]; /** * Consumption-half: trusted store catalogs queried per dep. * * YAML key: `stores` */ stores?: StoreEntry[]; /** * Consumption-half: conflict-resolution overrides with required reason. * * YAML key: `overrides` */ overrides?: OverrideEntry[]; /** * Local patches applied during install (new format). * Maps "kind:name" → patch file path relative to project root. * * YAML key: `patches` */ patches?: Record; /** * @deprecated The top-level `mounts:` key is no longer supported as of Task 11. * * Move filesystem-projected storage backends under `connectors:` with a * `mount:` sub-block. See `docs/migration-mounts-to-connectors.md`. * * This field is kept on the type **only** so `config.ts` can detect a * present `mounts:` block and throw a migration error. It is never merged, * applied, or forwarded to any manager. `normalizeConfig` still populates it * when `mounts:` is present in the YAML so the detection check works. * * YAML key: `mounts` (rejected at runtime — do not use) */ mounts?: MountDeclaration[]; /** * Data connectors — tool-accessed backends. * * Each declaration's `driver` field is an *implicit* catalog ref * (`connector:`). The runtime resolves it through * `resolveRuntimeAssets()`, which scans every declared `repositories` entry * plus the implicit built-in `factory-assets` repo for matching `CONNECTOR.md` * manifests. Drivers that do not match any catalog entry produce a * `missing_driver` warning at session startup. * * YAML key: `connectors` */ connectors?: ConnectorDeclaration[]; /** * External MCP servers — injected into the Claude SDK driver at session startup. * Supports stdio subprocess, SSE, and HTTP transports. * * YAML key: `mcp_servers` */ mcp_servers?: McpServerDeclaration[]; /** Agent behavior — definition reference, permissions, hooks, subagents. */ agent?: AgentConfig; /** Workspace layout — directories, git, setup scripts, container. */ workspace?: WorkspaceLayoutConfig; /** * Secret provisioning configuration. * Controls how connector credentials are resolved at runtime. * * YAML key: `secrets` */ secrets?: SecretsConfig; /** * Telemetry configuration — passed through raw to the telemetry package. * Parsed by `resolveTelemetryConfig` in `@skaile/workspaces/telemetry`. * * YAML key: `telemetry` */ telemetry?: Record; /** * Session compaction settings -- controls when and how conversation * snapshots are created. * * YAML key: `compaction` */ compaction?: CompactionConfig; /** * Plugin specs to load into pluginRegistry — npm specifiers like * "@skaile/provider-fly@^0.1.0". Opt-in; no auto-discovery. YAML key: `plugins`. */ plugins?: string[]; /** Deploy target selection. YAML key: `deploy`. */ deploy?: DeployBlock; } /** Deploy target selection for `skaile deploy`. YAML key: `deploy`. Resolved through pluginRegistry at deploy time. */ export interface DeployBlock { /** Deploy target id (e.g. "local", "docker", "fly") — resolved against pluginRegistry at deploy time, not validated here. */ target: string; /** Target-specific config; validated by the target's configSchema at resolve time. */ config?: unknown; } export interface SecretsConfig { /** * How secrets are provided to the container. * "env" — read from process.env (default, for CLI/standalone) * "provisioned" — wait for secrets over transport bridge (platform containers) */ provider?: "env" | "provisioned"; /** Timeout in ms for waiting for provisioned secrets (default: 30000). */ timeoutMs?: number; } export interface CompactionConfig { /** Enable managed compaction (default: true). */ enabled?: boolean; /** Context fill percentage that triggers compaction (default: 80). */ thresholdPercent?: number; /** Minimum ms between compactions to prevent thrashing (default: 120000). */ minCooldownMs?: number; /** Enable manual compact command in expert mode (default: false). */ manualCompactEnabled?: boolean; } export declare const COMPACTION_DEFAULTS: Required; /** @deprecated Use AgentConfigProfile */ export interface RuntimeDefaults { framework?: string; driver?: string; provider?: string; model?: string; skills_dir?: string; agents_dir?: string; prompts_dir?: string; } /** @deprecated Use AiResourceEntry[] */ export interface AiResourcesConfig { sources?: AiResourceSource[]; requires?: string[]; auto_deploy?: boolean; } /** @deprecated Use AiResourceEntry */ export interface AiResourceSource { name: string; path: string; branch?: string; } export interface SkWorkspaceConfigFile { /** Absolute path to the config file. */ path: string; /** Workspace name (extracted from filename). */ name: string; /** Parsed config. */ config: SkWorkspaceConfig; /** * Diagnostics from decoding this file — legacy-shape warnings (camelCase keys, * flat agent-config, the old ai_resources object) and unrecognized * driver/provider/access values. Absent on configs synthesized in-memory. */ diagnostics?: Diagnostic[]; } /** Suffix for named workspace configs: `.skaile.yaml` */ export declare const SKAILE_YAML_SUFFIX = ".skaile.yaml"; /** Filename for the default workspace config when no name is given. */ export declare const SKAILE_YAML_DEFAULT = "skaile.yaml"; /** @deprecated Use SKAILE_YAML_SUFFIX */ export declare const SK_WORKSPACE_SUFFIX = ".skaile.yaml"; /** @deprecated Use SKAILE_YAML_DEFAULT */ export declare const SK_WORKSPACE_DEFAULT_NAME = "default"; /** * Return the canonical filename for a workspace config. * The default workspace resolves to `"skaile.yaml"`; named workspaces resolve to * `".skaile.yaml"`. * * @param name - Optional workspace name (omit or pass `"default"` for the primary config) * @returns Filename string (not a full path) * @docLink packages/core/workspace-config#workspace-config-filename */ export declare function workspaceConfigFilename(name?: string): string; /** * Return `true` if `filename` matches the workspace config naming convention * (`"skaile.yaml"` or any `"*.skaile.yaml"`). * * @param filename - Bare filename (no directory component) * @docLink packages/core/workspace-config#is-workspace-config-filename */ export declare function isWorkspaceConfigFilename(filename: string): boolean; /** * Extract the workspace name from a config filename. * `"skaile.yaml"` → `"default"`, `"staging.skaile.yaml"` → `"staging"`. * * @param filename - Bare filename produced by `workspaceConfigFilename` * @returns Workspace name string * @docLink packages/core/workspace-config#workspace-name-from-filename */ export declare function workspaceNameFromFilename(filename: string): string; /** * Load a single workspace config file from `dir`. * When `name` is omitted and no `skaile.yaml` exists, falls back to the sole * `*.skaile.yaml` file in the directory if exactly one is present. * * @param dir - Directory to search for the config file * @param name - Optional workspace name (omit for the default `skaile.yaml`) * @returns Parsed config file info, or `null` if no matching file was found * @docLink packages/core/workspace-config#load-sk-workspace-config */ export declare function loadSkWorkspaceConfig(dir: string, name?: string): SkWorkspaceConfigFile | null; /** * Serialize and write a workspace config to `dir`, creating the directory if needed. * * @param dir - Target directory (created recursively if absent) * @param config - Config object to serialize as YAML * @param name - Optional workspace name (omit for the default `skaile.yaml`) * @returns Absolute path of the written file * @docLink packages/core/workspace-config#save-sk-workspace-config */ export declare function saveSkWorkspaceConfig(dir: string, config: SkWorkspaceConfig, name?: string): string; /** * List all workspace config files present in `dir`, sorted alphabetically by name. * Returns an empty array when `dir` does not exist or is unreadable. * * @param dir - Directory to scan for `skaile.yaml` and `*.skaile.yaml` files * @returns Array of parsed config file descriptors * @docLink packages/core/workspace-config#list-sk-workspace-configs */ export declare function listSkWorkspaceConfigs(dir: string): SkWorkspaceConfigFile[]; /** * Resolve the effective workspace config by stacking configs across three scope levels: * user (`~/.skaile/`) < app (`opts.appDir`) < project (`projectDir`). * * Each level's config is merged with `mergeSkWorkspaceConfigs`, with higher-priority * scopes winning for scalars and unions applied for arrays (deny lists, directories, etc.). * Returns an empty object `{}` when no config files are found. * * @param projectDir - Root directory of the current project * @param opts - Optional overrides: `name` selects a named workspace (default: `"default"`), * `appDir` inserts an app-level config between user and project scopes * @returns Merged effective `SkWorkspaceConfig` * @docLink packages/core/workspace-config#resolve-sk-workspace-config */ export declare function resolveSkWorkspaceConfig(projectDir: string, opts?: { name?: string; appDir?: string; }): SkWorkspaceConfig; /** * Resolve **only** the user-scope global SSOT (`~/.skaile/skaile.yaml`), without * stacking any app/project config. This is the single source of truth for global * cross-backend installs (`skaile install --global`). Returns `{}` when the file * is absent (edge: no `~/.skaile/skaile.yaml` yet). Re-throws the legacy-key * error with the `migrate-skaile-manifest` hint, matching * {@link resolveSkWorkspaceConfig}. * * @returns The normalized user-scope config, or `{}` when absent. * @throws {Error} on legacy `repositories:` / `ai_resources:` keys. * @docLink packages/core/workspace-config#resolve-global-sk-workspace-config */ export declare function resolveGlobalSkWorkspaceConfig(): SkWorkspaceConfig; /** * Resolve the set of coding-agent backends a global install fans out to: * the explicit `global_backends:` list from `~/.skaile/skaile.yaml` when set, * otherwise every {@link DriverTarget} whose global backend root dir already * exists under `~` (auto-detection). May return an empty array when no backend * is declared and none is present on disk. * * @returns Deduplicated, declaration-order list of global backends. * @throws {Error} on legacy keys in `~/.skaile/skaile.yaml`. * @docLink packages/core/workspace-config#resolve-global-backends */ export declare function resolveGlobalBackends(): DriverTarget[]; /** * Walk upward from `startDir` looking for a `skaile.yaml` (or `*.skaile.yaml`). * Returns the directory containing the first match, or `undefined` if none found. * * Stops after 20 levels to avoid scanning all the way to `/`. * * @param startDir - Directory to start searching from (usually `process.cwd()`) * @returns Absolute path to the workspace root, or `undefined` * @docLink packages/core/workspace-config#find-workspace-root */ export declare function findWorkspaceRoot(startDir: string): string | undefined; /** * Deep-merge two workspace configs according to stacking rules. * `overlay` takes priority over `base` for scalar fields. * Arrays use concatenation + deduplication; deny lists always union. * Mounts and connectors are merged by `id` (overlay entry wins for same id). * Hooks are concatenated in order (base first, overlay appended). * * @param base - Lower-priority config (e.g. user or app scope) * @param overlay - Higher-priority config (e.g. project scope) * @returns Merged `SkWorkspaceConfig` * @docLink packages/core/workspace-config#merge-sk-workspace-configs */ export declare function mergeSkWorkspaceConfigs(base: SkWorkspaceConfig, overlay: SkWorkspaceConfig): SkWorkspaceConfig; /** Severity of a {@link Diagnostic} produced by {@link decodeSkaileYaml}. */ export type DiagnosticSeverity = "error" | "warning" | "info"; /** * A structured note about a decoded `skaile.yaml` — a syntax error, a tolerated * legacy/non-canonical shape that was normalized, or an unrecognized enum value. * {@link decodeSkaileYaml} never throws; it reports problems here so each caller * (platform editor, runner, CLI) decides how loud to be. */ export interface Diagnostic { /** Stable machine code, e.g. `"legacy_key_camelcase"` / `"unknown_driver"`. */ code: string; severity: DiagnosticSeverity; /** Human-readable, actionable message. */ message: string; /** Dotted path into the config the note refers to, when applicable. */ path?: string; } /** Result of {@link decodeSkaileYaml}: the normalized config plus any notes. */ export interface DecodeResult { config: SkWorkspaceConfig; diagnostics: Diagnostic[]; } /** * Normalize a raw parsed YAML object into the canonical SkWorkspaceConfig shape. * Pure structural pass; see {@link decodeSkaileYaml} for the diagnostics-aware * entry point. Tolerates backward-compatible fields: * - `agent-config` (hyphen) / `agent_config` (underscore) / `agentConfig` (camelCase) → `agent_config` * - flat agent-config (`{ driver, model }`) → `{ default: { driver, model } }` * * Hard cut: legacy top-level keys `repositories:` and `ai_resources:` (any case) * now throw — the migration is performed by the `migrate-skaile-manifest` skill. * @docLink packages/core/workspace-config#normalize-config */ export declare function normalizeConfig(raw: Record): SkWorkspaceConfig; /** * Decode `skaile.yaml` text into a normalized {@link SkWorkspaceConfig} plus * {@link Diagnostic}s. Total — never throws: a YAML syntax error or a non-object * root is reported as an `error`-severity diagnostic with an empty config. * Legacy/non-canonical shapes (camelCase keys, flat agent-config, the old * ai_resources object) are normalized and reported as `warning`s; unrecognized * driver/provider/access values are reported as `warning`s too. * * Pair with {@link encodeSkaileYaml} for round-tripping. The round-trip is * canonical-lossless on the typed model (`decode(encode(config)).config` deep- * equals `config`) but does not preserve comments or key order — use * `WorkspaceYamlEditor` for comment-preserving in-place edits. * @docLink packages/core/workspace-config#decode-skaile-yaml */ export declare function decodeSkaileYaml(text: string): DecodeResult; /** * Encode a {@link SkWorkspaceConfig} to canonical `skaile.yaml` text: * deterministic key order, the hyphenated `agent-config` key, only defined * fields. This is the single sanctioned writer — clients should build a typed * config and encode it here rather than hand-serializing YAML. * @docLink packages/core/workspace-config#encode-skaile-yaml */ export declare function encodeSkaileYaml(config: SkWorkspaceConfig): string; /** * Derive a single MCP declaration from a materialized on-disk subdir. * Returns `null` when the subdir has no `MCP.md` or when parsing fails * (warns without aborting the caller's scan). */ export declare function deriveSingleMaterializedMcpDeclaration(subDir: string, name: string): McpServerDeclaration | null; /** * Copy each materialized skill the platform materializer wrote to * `/.skaile/assets/skill//` into the active driver's native * skills dir (`///`) so native discovery surfaces * them to the LLM. Mirrors {@link loadMaterializedMcpDeclarations}: the scan * ALWAYS runs, independent of `dependencies:`. * * Only immediate subdirectories containing a `SKILL.md` are staged. The copy is * idempotent (`force: true` overwrites a previously staged copy on refresh). * `.instance.json` is NEVER copied — it can hold a materializer's * `resolvedSecrets`, which must not leak into `.claude/skills/`; Claude Code * skills do not consume it either. * * **Async by necessity.** Both `projectDir` and the driver's skills dir can sit * on (or beside) an rclone FUSE mount. The former synchronous `existsSync` + * `cpSync` pair parked the whole event loop for as long as the mount took to * answer — unbounded, and invisible to `try/catch` because a hang is not an * error. Every filesystem call here is now awaited and deadline-bounded, so a * wedged mount costs one failed staging attempt instead of the session. * * A failure staging one skill is caught per-directory, warned (path + message * only, never file contents), and skipped so one bad skill never aborts the scan. * * When `opts.declared` is provided, only skills whose dir name appears in it are * staged — undeclared on-disk dirs ("ghosts") are skipped so the effective set * equals `skaile.yaml` exactly (the canonical-contract invariant; the runner * always passes `declared`). Publisher-scope tolerance: `@pub/name` declared * matches a dir named `name`, and vice versa. When `opts.declared` is omitted the * scan stages every materialized skill (used by callers that do their own gating). * * @param projectDir - Workspace root containing `.skaile/assets/skill/` * @param skillsDir - Driver-relative skills dir, e.g. `.claude/skills` * @param opts - When `declared` is present, restrict staging to those names * @returns Names of the skills staged (empty if the source dir is absent) */ export declare function stageMaterializedSkills(projectDir: string, skillsDir: string, opts?: { declared?: string[]; }): Promise; export declare function loadMcpServerDeclarations(projectDir: string): Promise; /** * Resolve the `agent.definition` field from `skaile.yaml` in `projectDir` * to an absolute filesystem path. * * Resolution rules: * - Absent or empty → undefined * - Relative/absolute local path → resolve(projectDir, definition) * - `ai-assets://` → walk up for ai-assets/, join with * - `agent:` catalog refs → unresolved here (returns undefined; the * install pipeline materializes catalog agents into the workspace) * * Used by the runner to determine agentDir without requiring --agent-dir. * @docLink packages/core/workspace-config#resolve-agent-dir */ export declare function resolveAgentDir(projectDir: string): string | undefined; export {}; //# sourceMappingURL=workspace-config.d.ts.map