/** * Setup discovers enabled Pi resources in place through its package resolver. Configured packages * are never copied into another autoload root. For legacy npm installs unregistered with Pi only, * the explicit package.json pi.skills declaration remains a scaffold source (ADR-0074). * Names, ceilings and bytes are checked before they can enter the generated shell grant. */ import { type SkillDefinition } from "./definitions.ts"; import { isSafeCapability } from "./capabilities.ts"; export interface DiscoveredSkill { definition: SkillDefinition; /** The file verbatim. `init` copies it rather than regenerating it, so nothing is lost in a round trip. */ text: string; path: string; /** Already enabled by Pi; setup must reference it rather than making a competing copy. */ referenced?: boolean; } /** Why a declared skill was refused before it could be planned. Each has a different fix. */ export type RefusalReason = /** The name cannot be a capability id, a line in a sourced file, or a path segment. */ "unsafe-name" /** A declared `allowed-tools` entry cannot be one either — R-78, the sibling of R-77. */ | "unsafe-capability" /** The declaration claims `tool:*` or `agent:*`: root authority, which a package may not hand itself. */ | "wildcard" /** The bytes are not valid UTF-8, so "copied verbatim" could not be honoured. */ | "not-utf8"; export interface RefusedSkill { /** The `pi.skills` entry or the definition name, whichever the operator can act on. */ subject: string; reason: RefusalReason; /** The offending id(s), when the reason names any. */ detail: string[]; } export interface SkillPackage { name: string; version: string; /** Paths `pi.skills` named that could not be read as a `SKILL.md`, so the report can say so out loud. */ unreadable: string[]; /** Skills refused for a reason that would otherwise reach a generated file. Never silently dropped. */ refused: RefusedSkill[]; skills: DiscoveredSkill[]; } /** * May this definition's name be written into a capability id, a shell file and a path? * * **Measured before it was written, and it was a defect in this module's first version (R-77).** A * definition's identity is its directory name, and `init` interpolates that name into three places at once: * `agent:` inside a **comma-separated** `PI_DADDY_GRANT`, a `.pi/grants.env` an operator **sources**, * and the path it writes the copy to. An installed package with a directory called `a,tool:bash` produced * * ``` * export PI_DADDY_GRANT="agent:a,tool:bash,tool:delegate,tool:read" * ``` * * — `tool:bash` in an operator's grant, declared by nobody. A quote character reaches a file that gets * `source`d, and a name of `..` writes outside `.pi/skills/`. One rule closes all three, and it is * deliberately a **whitelist**: the safe set here is small and the unsafe set is the rest of Unicode. * * The first character must be alphanumeric, so `..` and dotfiles are refused along with everything else. */ export declare function isSafeName(name: string): boolean; /** * May this DECLARED capability id be written into the generated grant? — R-78. * * **R-77's other half, and the reason a whitelist beats a blocklist twice.** R-77 was closed on the name * channel; the `allowed-tools` *value* travels to the identical interpolation site and was unchecked, so a * package declaring * * ```yaml * allowed-tools: Read,ext:x";touch /tmp/pwned;PI_DADDY_GRANT=" * ``` * * produced a `.pi/grants.env` that executed arbitrary code the moment the operator ran the `source` line * `init` itself prints. Reproduced end to end before this existed. `ceilingForDefinition` passes `ext:`, * `skill:` and `agent:` entries through **as written** by design (a translation table would invent or drop * grants), which is right for the enforcement path — the catalog refuses what it does not know — and is * exactly why the check has to be here, at the boundary that *generates* rather than the one that enforces. * * The grammar is the one the README documents: `tool:`, `skill:`, `agent:`, * `workspace:`, and `ext:/` where `` may be npm-scoped. No wildcards — those are refused * separately and loudly, because "you tried to grant yourself everything" is a different fact from "that is * not a name". * * `workspace:` was absent until 2026-08-21, so the boundary that GENERATES grants could not emit the one * capability ADR-0035's breaking change made mandatory: a package needing to route was reported as declaring * something that "is not a name". A namespace has to be added here as well as to the enforcing path, and * that is the whole lesson of the review this came out of. */ export { isSafeCapability }; /** Read one installed package, if it declares skills. `null` means "not a skill package", not an error. */ export declare function readSkillPackage(packageDir: string): Promise; /** * Every installed package under `/node_modules` that declares `pi.skills`, sorted by name. * * Top level and one scope deep, which is what npm's layout has. Nothing recurses into a dependency's own * `node_modules`: a transitive skill package is not something an operator asked to install definitions * from, and scaffolding one into their project would be a surprise wearing a helpful face. */ /** * Every place a pi package can be installed, most specific first (R-75). * * **Two roots, because pi has two install paths and only one of them is `npm install`.** `pi install * npm:principal-pi-skills` — the documented, pi-native way, and the only one that registers the package so * pi will auto-load its extension — puts it in `$PI_CODING_AGENT_DIR/npm/node_modules`, and leaves the * project without a `node_modules` at all. Searching only `/node_modules` therefore found **nothing** * for an operator who followed pi's own instructions, and said "install principal-pi-skills" to somebody * who just had. * * Measured, in a fresh `PI_CODING_AGENT_DIR` with an empty project: `pi install` populates the agent root * and creates no project root. * * Project first when both exist, because a package pinned in the repository is the one the team agreed on. */ export declare function skillPackageRoots(cwd: string): string[]; export declare function discoverSkillPackages(cwd: string): Promise; //# sourceMappingURL=skill-packages.d.ts.map