/** * Config file support for Hasna Skills * * Loads configuration from: * 1. Project-local: ./skills.config.json (highest priority) * 2. Global: ~/.hasna/skills/config.json (JSON format, lowest priority) * * Values from the project config override global config. */ /** * Environment variable that relocates the skills data directory. * * Owned by the @hasna/paths-based app-home resolver (app-home.ts), re-exported * here so every existing reader keeps agreeing on the name. */ export { DATA_DIR_ENV } from "./app-home.js"; /** * There is no deployment "mode" key, and no service address here either. * * Skills has one deployment story: you run it. Whether this CLI talks to a * server is not a product variant, it is one fact — whether a fleet credential * resolves. Nothing may be derived from a declared label, because a label can * disagree with the configuration it claims to describe. * * `apiUrl` used to live here as a sixth URL tier of this package's own. It is * retired (owner ruling 2026-09-04, hasna/apps#1720): the authority ladder is * `HASNA_SKILLS_API_URL` → the Keychain `api-url` item → * `~/.hasna/skills/config/credentials` → the fleet gateway, and it belongs to * @hasna/contracts so every Hasna CLI resolves it identically. * `skills setup --api-url ` still writes it — into the credentials file. * * Configs written by older versions may still carry a "mode" or "apiUrl" key on * disk. Those are refused rather than ignored — see lib/retired-settings.ts for * why silence is the worse of the two failures — and `skills config unset ` * removes them. */ export interface SkillsConfig { defaultAgent?: "claude" | "codex" | "gemini" | "pi" | "opencode" | "all"; defaultScope?: "global" | "project"; format?: "compact" | "json" | "csv"; extensionsDir?: string; } export type ConfigScope = "global" | "project"; /** * Subfolder of the data directory holding the installed skill corpus. * * ~/.hasna/skills is the skills *app* folder, matching every sibling Hasna app: * mementos keeps agents/ beside config.json and mementos.db, accounts keeps * profiles/ beside accounts.json, knowledge keeps artifacts/ and cache/ beside * auth.json. Each puts app data at the app root and content in a named subfolder. * * Skills used to be the exception, writing one folder per skill straight into the * app root next to config.json and skills.db. That is the only reason a denylist * of "entries that look like skills but aren't" ever had to exist; no sibling app * needs one. With the corpus under installed/, a skill may be named `config` or * `custom` without colliding with anything. */ export declare const INSTALLED_SKILLS_DIRNAME = "installed"; /** * Subfolder of the data directory holding the migrated corpus cache — the * owner-layout replacement for installed/ after `skills storage migrate` * (~/.hasna/skills/{skills,logs,outputs}). * * The marker file inside it (LAYOUT_MIGRATION_RECORD) is the authority: a * skills/ directory someone created by hand is not the corpus and never will * be treated as one (see isOwnerLayoutMigrated). */ export declare const SKILLS_CACHE_DIRNAME = "skills"; /** * Marker file inside the corpus cache proving the owner-layout migration ran; * also its record (see migrateOwnerLayout in home-migration.ts). */ export declare const LAYOUT_MIGRATION_RECORD = ".layout-migration.json"; /** * True once the owner layout has been migrated (the record is the authority). * * Lives here — rather than in home-migration.ts — because the canonical corpus * resolver in portable-skills.ts must consult it too, and home-migration.ts * depends on portable-skills.ts; this module sits below both. */ export declare function isOwnerLayoutMigrated(appDir: string): boolean; /** * Get the data directory for skills global config/data. * Default: ~/.hasna/skills/, overridable with $HASNA_SKILLS_DIR. * Legacy directories and configuration are never imported during resolution. */ export declare function getDataDir(): string; /** Resolve the same app data root without creating directories. */ export declare function getDataDirReadOnly(): string; /** * Get the config file path for a given scope, write-free (see getDataDirReadOnly). */ export declare function getConfigPathReadOnly(scope: ConfigScope): string; /** Read only the canonical global and explicitly selected project configuration. */ export declare function loadConfigReadOnly(): SkillsConfig; /** * Get the config file path for a given scope */ export declare function getConfigPath(scope: ConfigScope): string; /** * Load merged config: project-local overrides global */ export declare function loadConfig(): SkillsConfig; /** * Save a single config key-value pair to the specified scope */ export declare function saveConfig(key: string, value: string, scope?: ConfigScope): void; /** * Remove a single config key from the specified scope. * * This is also how a retired key written by an older version is removed: a file * carrying one is refused by every read, so refusing to unset it would leave an * operator with a config every command rejects and no supported repair. * * Returns whether the key was actually present, so callers can distinguish * "removed" from "there was nothing to remove" instead of guessing. */ export declare function unsetConfig(key: string, scope?: ConfigScope): boolean;