@anytio/pspm 0.12.0 → 0.14.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1 @@
1
+ {"version":3,"file":"symlinks-BTw8X0GG.js","names":["REGISTRY_SPECIFIER_PATTERN","parseRegistrySpecifier","semver","parseRegistrySpecifier"],"sources":["../src/lib/encryption.ts","../src/lib/ignore.ts","../src/lib/integrity.ts","../src/lib/lockfile.ts","../src/lib/manifest.ts","../../../packages/shared/skill-types/src/specifier.ts","../src/lib/resolver-api.ts","../src/lib/resolver-format.ts","../src/lib/version.ts","../src/lib/resolver.ts","../src/lib/specifier.ts","../src/agents.ts","../src/manifest.ts","../src/github.ts","../src/lockfile.ts","../src/symlinks.ts"],"sourcesContent":["import {\n createCipheriv,\n createDecipheriv,\n randomBytes,\n scryptSync,\n} from \"node:crypto\";\n\nconst ALGORITHM = \"aes-256-gcm\";\nconst KEY_LENGTH = 32; // 256 bits\nconst IV_LENGTH = 16; // 128 bits\nconst SALT_LENGTH = 32; // 256 bits\nconst SCRYPT_COST = 16384; // N parameter\nconst SCRYPT_BLOCK_SIZE = 8; // r parameter\nconst SCRYPT_PARALLELISM = 1; // p parameter\nconst AUTH_TAG_LENGTH = 16; // 128 bits\n\n/**\n * Encryption metadata stored alongside encrypted packages.\n * All fields except the key itself are safe to store publicly.\n */\nexport interface EncryptionMetadata {\n algorithm: \"aes-256-gcm\";\n kdf: \"scrypt\";\n /** Hex-encoded salt used for key derivation */\n salt: string;\n /** Hex-encoded initialization vector */\n iv: string;\n /** Hex-encoded GCM authentication tag */\n authTag: string;\n /** The scope this was encrypted for (e.g., \"@user/alice\" or \"@org/acme\") */\n scope: string;\n}\n\n/**\n * Derive an AES-256 key from a passphrase using scrypt.\n */\nfunction deriveKey(passphrase: string, salt: Buffer): Buffer {\n return scryptSync(passphrase, salt, KEY_LENGTH, {\n N: SCRYPT_COST,\n r: SCRYPT_BLOCK_SIZE,\n p: SCRYPT_PARALLELISM,\n });\n}\n\n/**\n * Encrypt a buffer using AES-256-GCM with a passphrase.\n *\n * @param data - The plaintext buffer to encrypt\n * @param passphrase - The encryption passphrase\n * @param scope - The scope identifier (e.g., \"@user/alice\" or \"@org/acme\")\n * @returns The encrypted buffer and metadata needed for decryption\n */\nexport function encryptBuffer(\n data: Buffer,\n passphrase: string,\n scope: string,\n): { encrypted: Buffer; metadata: EncryptionMetadata } {\n const salt = randomBytes(SALT_LENGTH);\n const iv = randomBytes(IV_LENGTH);\n const key = deriveKey(passphrase, salt);\n\n const cipher = createCipheriv(ALGORITHM, key, iv, {\n authTagLength: AUTH_TAG_LENGTH,\n });\n const encrypted = Buffer.concat([cipher.update(data), cipher.final()]);\n const authTag = cipher.getAuthTag();\n\n return {\n encrypted,\n metadata: {\n algorithm: ALGORITHM,\n kdf: \"scrypt\",\n salt: salt.toString(\"hex\"),\n iv: iv.toString(\"hex\"),\n authTag: authTag.toString(\"hex\"),\n scope,\n },\n };\n}\n\n/**\n * Decrypt a buffer using AES-256-GCM with a passphrase.\n *\n * @param encrypted - The encrypted buffer\n * @param passphrase - The encryption passphrase\n * @param metadata - The encryption metadata (salt, iv, authTag)\n * @returns The decrypted plaintext buffer\n * @throws Error if decryption fails (wrong passphrase or corrupted data)\n */\nexport function decryptBuffer(\n encrypted: Buffer,\n passphrase: string,\n metadata: EncryptionMetadata,\n): Buffer {\n const salt = Buffer.from(metadata.salt, \"hex\");\n const iv = Buffer.from(metadata.iv, \"hex\");\n const authTag = Buffer.from(metadata.authTag, \"hex\");\n const key = deriveKey(passphrase, salt);\n\n const decipher = createDecipheriv(ALGORITHM, key, iv, {\n authTagLength: AUTH_TAG_LENGTH,\n });\n decipher.setAuthTag(authTag);\n\n const decrypted = Buffer.concat([\n decipher.update(encrypted),\n decipher.final(),\n ]);\n\n return decrypted;\n}\n","/**\n * Ignore file handling for PSPM publish/pack\n *\n * Similar to npm's .npmignore behavior:\n * - If .pspmignore exists, use it\n * - Otherwise, fallback to .gitignore\n * - Always ignore node_modules and .git regardless\n */\n\nimport { readFile } from \"node:fs/promises\";\nimport { join } from \"node:path\";\nimport ignore, { type Ignore } from \"ignore\";\n\n/**\n * Files/directories that are always ignored regardless of ignore file contents\n */\nconst ALWAYS_IGNORED = [\n \"node_modules\",\n \".git\",\n \".pspm-publish\", // temp directory used during publish\n];\n\n/**\n * Result of loading ignore patterns\n */\nexport interface IgnoreLoadResult {\n /** The ignore instance with loaded patterns */\n ig: Ignore;\n /** Which file the patterns came from (null if using defaults only) */\n source: \".pspmignore\" | \".gitignore\" | null;\n /** Raw patterns loaded from the file (excluding defaults) */\n patterns: string[];\n}\n\n/**\n * Load ignore patterns from .pspmignore or .gitignore\n *\n * Priority:\n * 1. .pspmignore (if exists)\n * 2. .gitignore (if exists)\n * 3. Default patterns only (node_modules, .git)\n *\n * @param cwd - The directory to look for ignore files (defaults to process.cwd())\n * @returns An ignore instance and the source file used\n */\nexport async function loadIgnorePatterns(\n cwd: string = process.cwd(),\n): Promise<IgnoreLoadResult> {\n const ig = ignore();\n\n // Always add default ignores\n ig.add(ALWAYS_IGNORED);\n\n // Try .pspmignore first\n const pspmIgnorePath = join(cwd, \".pspmignore\");\n try {\n const content = await readFile(pspmIgnorePath, \"utf-8\");\n const patterns = parseIgnorePatterns(content);\n ig.add(patterns);\n return { ig, source: \".pspmignore\", patterns };\n } catch {\n // .pspmignore not found, try .gitignore\n }\n\n // Fallback to .gitignore\n const gitIgnorePath = join(cwd, \".gitignore\");\n try {\n const content = await readFile(gitIgnorePath, \"utf-8\");\n const patterns = parseIgnorePatterns(content);\n ig.add(patterns);\n return { ig, source: \".gitignore\", patterns };\n } catch {\n // No .gitignore either, use defaults only\n }\n\n return { ig, source: null, patterns: [] };\n}\n\n/**\n * Create rsync exclude arguments from ignore patterns\n *\n * @param ig - The ignore instance\n * @returns Array of --exclude='pattern' arguments for rsync\n */\nexport function getExcludeArgsForRsync(patterns: string[]): string {\n // Always include the essential excludes\n const allPatterns = [...new Set([...ALWAYS_IGNORED, ...patterns])];\n\n return allPatterns.map((p) => `--exclude='${p}'`).join(\" \");\n}\n\n/**\n * Create tar exclude arguments from ignore patterns\n *\n * @param patterns - Array of patterns to exclude\n * @returns String of --exclude='pattern' arguments for tar\n */\nexport function getExcludeArgsForTar(patterns: string[]): string {\n // Same as rsync\n const allPatterns = [...new Set([...ALWAYS_IGNORED, ...patterns])];\n\n return allPatterns.map((p) => `--exclude='${p}'`).join(\" \");\n}\n\n/**\n * Parse an ignore file content into an array of patterns\n * Filters out comments and empty lines\n *\n * @param content - The content of an ignore file\n * @returns Array of patterns\n */\nexport function parseIgnorePatterns(content: string): string[] {\n return content\n .split(\"\\n\")\n .map((line) => line.trim())\n .filter((line) => line && !line.startsWith(\"#\"));\n}\n\nexport { ALWAYS_IGNORED };\n","import { createHash } from \"node:crypto\";\n\n/**\n * Calculate integrity hash for a buffer.\n * Uses SHA-256 with base64 encoding, prefixed with \"sha256-\".\n *\n * @param data - The buffer to hash\n * @returns Integrity string (e.g., \"sha256-abc123...\")\n */\nexport function calculateIntegrity(data: Buffer): string {\n const hash = createHash(\"sha256\").update(data).digest(\"base64\");\n return `sha256-${hash}`;\n}\n","/**\n * PSPM Lockfile Schema URL for IDE validation\n */\nexport const PSPM_LOCKFILE_SCHEMA_URL =\n \"https://pspm.dev/schema/v1/pspm-lock.json\";\n\n/**\n * PSPM Lockfile format (pspm-lock.json)\n * Similar to package-lock.json for npm.\n *\n * Migration notes:\n * - v1 used \"skill-lock.json\" with `skills` key.\n * - v2 uses \"pspm-lock.json\" with `packages` key.\n * - v3 adds `githubPackages` key for GitHub dependencies.\n * - v4 adds `dependencies` field to entries for recursive resolution.\n * Also adds `localPackages` for local file: protocol packages.\n */\nexport interface PspmLockfile {\n /** JSON Schema URL for IDE validation */\n $schema?: string;\n /** Lockfile format version */\n lockfileVersion: 1 | 2 | 3 | 4 | 5;\n /** Registry URL used for resolution */\n registryUrl: string;\n /** Installed packages from registry (v2+ format) */\n packages?: Record<string, PspmLockfileEntry>;\n /** Installed packages from GitHub (v3+ format) */\n githubPackages?: Record<string, GitHubLockfileEntry>;\n /** Installed packages from local directories (v4+ format) */\n localPackages?: Record<string, LocalLockfileEntry>;\n /** Installed packages from well-known endpoints (v5+ format) */\n wellKnownPackages?: Record<string, WellKnownLockfileEntry>;\n /** Installed skills (v1 format, deprecated) */\n skills?: Record<string, PspmLockfileEntry>;\n}\n\n/**\n * Lockfile entry for a local package.\n * Key format in localPackages: \"file:../path\" or \"file:/absolute/path\"\n */\nexport interface LocalLockfileEntry {\n /** Always \"local\" for local packages */\n version: \"local\";\n /** Original path from the specifier (relative or absolute) */\n path: string;\n /** Resolved absolute path to the local skill directory */\n resolvedPath: string;\n /** Skill name (last segment of path) */\n name: string;\n}\n\n/**\n * Client-side encryption metadata stored in lockfile entries.\n */\nexport interface LockfileEncryptionMetadata {\n algorithm: \"aes-256-gcm\";\n kdf: \"scrypt\";\n salt: string;\n iv: string;\n authTag: string;\n scope: string;\n}\n\n/**\n * Lockfile entry for a single package from registry.\n */\nexport interface PspmLockfileEntry {\n /** Resolved version */\n version: string;\n /** Download URL used to fetch the package */\n resolved: string;\n /** Integrity hash for verification (sha256-...) */\n integrity: string;\n /** Deprecation message if this version is deprecated */\n deprecated?: string;\n /** Dependencies: package name -> resolved version (v4+) */\n dependencies?: Record<string, string>;\n /** Client-side encryption metadata (present if package is encrypted) */\n encryption?: LockfileEncryptionMetadata;\n}\n\n/**\n * Lockfile entry for a GitHub package.\n * Key format in githubPackages: \"github:owner/repo[/path]\"\n */\nexport interface GitHubLockfileEntry extends PspmLockfileEntry {\n /** Resolved Git commit SHA */\n gitCommit: string;\n /** Original Git ref (branch, tag, or \"latest\") */\n gitRef: string;\n}\n\n/**\n * Lockfile entry for a well-known package.\n * Key format in wellKnownPackages: \"https://hostname#skill-name\"\n */\nexport interface WellKnownLockfileEntry {\n /** Always \"well-known\" for well-known packages */\n version: \"well-known\";\n /** URL to the SKILL.md file */\n resolved: string;\n /** Integrity hash of all file contents */\n integrity: string;\n /** Source hostname */\n hostname: string;\n /** Skill name */\n name: string;\n /** List of files included */\n files: string[];\n}\n","/**\n * PSPM Manifest format (pspm.json)\n *\n * This is the dedicated manifest format for prompt skill packages,\n * separate from package.json used for npm/node dependencies.\n */\n\n/**\n * Version requirements for the skill\n */\nexport interface PspmManifestRequirements {\n /** Minimum Claude model version required (e.g., \">=3.5\") */\n claude?: string;\n /** Minimum PSPM CLI version required (e.g., \">=0.1.0\") */\n pspm?: string;\n}\n\n/**\n * Agent configuration for skill symlinks\n */\nexport interface AgentConfig {\n /** Directory where skills should be symlinked (e.g., \".claude/skills\") */\n skillsDir: string;\n}\n\n/**\n * Built-in agent types with predefined configurations\n */\nexport type BuiltInAgent =\n | \"adal\"\n | \"amp\"\n | \"antigravity\"\n | \"augment\"\n | \"claude-code\"\n | \"cline\"\n | \"codebuddy\"\n | \"codex\"\n | \"command-code\"\n | \"continue\"\n | \"cortex\"\n | \"crush\"\n | \"cursor\"\n | \"droid\"\n | \"gemini-cli\"\n | \"github-copilot\"\n | \"goose\"\n | \"iflow-cli\"\n | \"junie\"\n | \"kilo\"\n | \"kimi-cli\"\n | \"kiro-cli\"\n | \"kode\"\n | \"mcpjam\"\n | \"mistral-vibe\"\n | \"mux\"\n | \"neovate\"\n | \"openclaw\"\n | \"opencode\"\n | \"openhands\"\n | \"pi\"\n | \"pochi\"\n | \"qoder\"\n | \"qwen-code\"\n | \"replit\"\n | \"roo\"\n | \"trae\"\n | \"trae-cn\"\n | \"universal\"\n | \"windsurf\"\n | \"zencoder\";\n\n/**\n * PSPM Manifest schema for pspm.json\n *\n * @example\n * ```json\n * {\n * \"$schema\": \"https://pspm.dev/schema/pspm.json\",\n * \"name\": \"my-skill\",\n * \"version\": \"1.0.0\",\n * \"description\": \"A skill for code reviews\",\n * \"author\": \"username\",\n * \"license\": \"MIT\",\n * \"type\": \"skill\",\n * \"capabilities\": [\"code-review\", \"typescript\"],\n * \"main\": \"SKILL.md\",\n * \"requirements\": {\n * \"claude\": \">=3.5\",\n * \"pspm\": \">=0.1.0\"\n * },\n * \"files\": [\"SKILL.md\", \"runtime/\", \"scripts/\", \"data/\"],\n * \"dependencies\": {},\n * \"private\": false\n * }\n * ```\n */\nexport interface PspmManifest {\n /** JSON Schema URL for validation */\n $schema?: string;\n\n /** Package name (no @ prefix needed, username is derived from logged-in user) */\n name: string;\n\n /** Semantic version string */\n version: string;\n\n /** Human-readable description */\n description?: string;\n\n /** Package author */\n author?: string;\n\n /** License identifier (e.g., \"MIT\", \"Apache-2.0\") */\n license?: string;\n\n /** Package type (always \"skill\" for prompt skill packages) */\n type?: \"skill\";\n\n /** List of capabilities/tags for discovery */\n capabilities?: string[];\n\n /** Main entry point file (default: \"SKILL.md\") */\n main?: string;\n\n /** Version requirements */\n requirements?: PspmManifestRequirements;\n\n /** Files to include in the published package */\n files?: string[];\n\n /** Skill dependencies (format: \"@user/{username}/{name}\": \"^1.0.0\") */\n dependencies?: Record<string, string>;\n\n /**\n * GitHub skill dependencies (format: \"github:owner/repo/path\": \"ref\")\n *\n * @example\n * ```json\n * {\n * \"githubDependencies\": {\n * \"github:vercel-labs/agent-skills/skills/react-best-practices\": \"main\",\n * \"github:myorg/prompts/team/frontend/linter\": \"v2.0.0\"\n * }\n * }\n * ```\n */\n githubDependencies?: Record<string, string>;\n\n /**\n * Local skill dependencies (format: \"file:../path\": \"*\")\n *\n * Used for local development and testing before publishing.\n * Creates symlinks for instant updates during development.\n *\n * @example\n * ```json\n * {\n * \"localDependencies\": {\n * \"file:../my-local-skill\": \"*\",\n * \"file:/absolute/path/to/skill\": \"*\"\n * }\n * }\n * ```\n */\n localDependencies?: Record<string, string>;\n\n /**\n * Well-known skill dependencies from HTTPS domains.\n *\n * Skills hosted at /.well-known/skills/ on any domain.\n * Key is the base URL, value is an array of skill names (or \"*\" for all).\n *\n * @example\n * ```json\n * {\n * \"wellKnownDependencies\": {\n * \"https://acme.com\": [\"code-review\", \"api-design\"],\n * \"https://docs.stripe.com\": \"*\"\n * }\n * }\n * ```\n */\n wellKnownDependencies?: Record<string, string[] | string>;\n\n /**\n * Custom agent configuration overrides.\n * Built-in agents (claude-code, cursor, codex) have default configs.\n * Custom agents can also be defined here.\n *\n * @example\n * ```json\n * {\n * \"agents\": {\n * \"claude-code\": { \"skillsDir\": \".claude/skills\" },\n * \"my-custom\": { \"skillsDir\": \".myagent/prompts\" }\n * }\n * }\n * ```\n */\n agents?: Partial<Record<BuiltInAgent, AgentConfig>> &\n Record<string, AgentConfig>;\n\n /** If true, prevents publishing to registry */\n private?: boolean;\n}\n\n/**\n * Result of detecting and reading a manifest\n */\nexport interface ManifestDetectionResult {\n /** The detected manifest type */\n type: \"pspm.json\" | \"package.json\";\n\n /** The parsed manifest content */\n manifest: PspmManifest;\n\n /** The file path that was read */\n path: string;\n}\n\n/**\n * Default file patterns to include when publishing\n */\nexport const DEFAULT_SKILL_FILES = [\n \"SKILL.md\",\n \"runtime\",\n \"scripts\",\n \"data\",\n] as const;\n\n/**\n * Schema URL for pspm.json (versioned)\n */\nexport const PSPM_SCHEMA_URL = \"https://pspm.dev/schema/v1/pspm.json\";\n\n/**\n * Validate that a manifest has required fields\n */\nexport function validateManifest(\n manifest: Partial<PspmManifest>,\n): { valid: true } | { valid: false; error: string } {\n if (!manifest.name) {\n return { valid: false, error: \"Manifest must have a 'name' field\" };\n }\n\n if (!manifest.version) {\n return { valid: false, error: \"Manifest must have a 'version' field\" };\n }\n\n // Validate name format (lowercase, alphanumeric, hyphens, underscores)\n // Name can be bare (\"my-skill\") or qualified (\"@user/owner/my-skill\", \"@org/owner/my-skill\")\n const parts = manifest.name.split(\"/\");\n const bareName = parts[parts.length - 1];\n if (!/^[a-z][a-z0-9_-]*$/.test(bareName)) {\n return {\n valid: false,\n error:\n \"Name must start with a lowercase letter and contain only lowercase letters, numbers, hyphens, and underscores\",\n };\n }\n\n // Validate version is valid semver (basic check)\n if (!/^\\d+\\.\\d+\\.\\d+/.test(manifest.version)) {\n return {\n valid: false,\n error: \"Version must be a valid semantic version (e.g., 1.0.0)\",\n };\n }\n\n return { valid: true };\n}\n","// =============================================================================\n// Registry Namespace Types\n// =============================================================================\n\n/**\n * Supported registry namespace types.\n *\n * - \"user\" — Individual user skills: @user/{username}/{skillname}\n * - \"org\" — Organization skills: @org/{orgname}/{skillname}\n * - \"github\" — GitHub-indexed skills: @github/{owner}/{repo}/{skillname}\n */\nexport type NamespaceType = \"user\" | \"org\" | \"github\";\n\n/**\n * Parsed registry specifier (unified across all namespaces).\n *\n * @example\n * - @user/bsheng/vite-slides -> { namespace: \"user\", owner: \"bsheng\", name: \"vite-slides\" }\n * - @org/anyt/code-review@^2.0.0 -> { namespace: \"org\", owner: \"anyt\", name: \"code-review\", versionRange: \"^2.0.0\" }\n * - @github/microsoft/skills/azure-ai -> { namespace: \"github\", owner: \"microsoft\", name: \"skills\", subname: \"azure-ai\" }\n */\nexport interface RegistrySpecifier {\n namespace: NamespaceType;\n /** username for @user, orgname for @org, GitHub owner for @github */\n owner: string;\n /** skill name for @user/@org, repo name for @github */\n name: string;\n /** skill name within the repo (only for @github) */\n subname?: string;\n versionRange?: string;\n}\n\n// =============================================================================\n// Registry Specifier Parsing\n// =============================================================================\n\n/**\n * Unified registry specifier regex pattern.\n *\n * Matches:\n * - @user/{owner}/{name}[@version]\n * - @org/{owner}/{name}[@version]\n * - @github/{owner}/{repo}/{skillname}[@version]\n *\n * Group 1: namespace (user|org|github)\n * Group 2: owner\n * Group 3: name (skill name for user/org, repo name for github)\n * Group 4: optional subname (skill name within repo, github only)\n * Group 5: optional @version\n */\nconst REGISTRY_SPECIFIER_PATTERN =\n /^@(user|org|github)\\/([a-zA-Z0-9_-]+)\\/([a-zA-Z0-9._-]+)(?:\\/([a-z][a-z0-9-]*))?(?:@(.+))?$/;\n\n/**\n * Parse a registry specifier string (any namespace).\n *\n * @param specifier - The specifier string\n * @returns Parsed specifier or null if invalid\n *\n * @example\n * ```typescript\n * parseRegistrySpecifier(\"@user/bsheng/my-skill@^1.0.0\")\n * // => { namespace: \"user\", owner: \"bsheng\", name: \"my-skill\", versionRange: \"^1.0.0\" }\n *\n * parseRegistrySpecifier(\"@org/anyt/code-review\")\n * // => { namespace: \"org\", owner: \"anyt\", name: \"code-review\" }\n *\n * parseRegistrySpecifier(\"@github/microsoft/skills/azure-ai@1.0.0\")\n * // => { namespace: \"github\", owner: \"microsoft\", name: \"skills\", subname: \"azure-ai\", versionRange: \"1.0.0\" }\n * ```\n */\nexport function parseRegistrySpecifier(\n specifier: string,\n): RegistrySpecifier | null {\n const match = specifier.match(REGISTRY_SPECIFIER_PATTERN);\n\n if (!match) {\n return null;\n }\n\n const namespace = match[1] as NamespaceType;\n const owner = match[2];\n const name = match[3];\n const subname = match[4];\n const versionRange = match[5];\n\n if (!owner || !name) {\n return null;\n }\n\n // @github requires a subname (skill within repo)\n if (namespace === \"github\" && !subname) {\n return null;\n }\n\n // @user and @org should not have a subname\n if (namespace !== \"github\" && subname) {\n return null;\n }\n\n return {\n namespace,\n owner,\n name,\n subname: subname || undefined,\n versionRange: versionRange || undefined,\n };\n}\n\n/**\n * Generate a full registry identifier string.\n *\n * @example\n * ```typescript\n * generateRegistryIdentifier({ namespace: \"user\", owner: \"bsheng\", name: \"my-skill\" })\n * // => \"@user/bsheng/my-skill\"\n *\n * generateRegistryIdentifier({ namespace: \"github\", owner: \"microsoft\", name: \"skills\", subname: \"azure-ai\", versionRange: \"1.0.0\" })\n * // => \"@github/microsoft/skills/azure-ai@1.0.0\"\n * ```\n */\nexport function generateRegistryIdentifier(\n spec: Pick<\n RegistrySpecifier,\n \"namespace\" | \"owner\" | \"name\" | \"subname\" | \"versionRange\"\n >,\n): string {\n let base = `@${spec.namespace}/${spec.owner}/${spec.name}`;\n if (spec.subname) {\n base += `/${spec.subname}`;\n }\n if (spec.versionRange) {\n base += `@${spec.versionRange}`;\n }\n return base;\n}\n\n/**\n * Check if a string is a registry specifier (starts with @user/, @org/, or @github/).\n */\nexport function isRegistrySpecifier(specifier: string): boolean {\n return (\n specifier.startsWith(\"@user/\") ||\n specifier.startsWith(\"@org/\") ||\n specifier.startsWith(\"@github/\")\n );\n}\n\n/**\n * Get the effective skill name from a registry specifier.\n * For @user/@org this is `name`, for @github this is `subname`.\n */\nexport function getRegistrySkillName(spec: RegistrySpecifier): string {\n return spec.subname ?? spec.name;\n}\n\n// =============================================================================\n// Backward-Compatible Aliases (@user namespace only)\n// =============================================================================\n\n/**\n * Parsed skill specifier (@user namespace only).\n * @deprecated Use `RegistrySpecifier` instead for multi-namespace support.\n */\nexport interface SkillSpecifier {\n username: string;\n name: string;\n versionRange?: string;\n}\n\n/**\n * Parse a skill specifier string (@user namespace only, backward compat).\n * @deprecated Use `parseRegistrySpecifier` instead.\n */\nexport function parseSkillSpecifier(specifier: string): SkillSpecifier | null {\n const result = parseRegistrySpecifier(specifier);\n if (!result || result.namespace !== \"user\") {\n return null;\n }\n return {\n username: result.owner,\n name: result.name,\n versionRange: result.versionRange,\n };\n}\n\n/**\n * Generate a full skill identifier string (@user namespace).\n * @deprecated Use `generateRegistryIdentifier` instead.\n */\nexport function generateSkillIdentifier(\n username: string,\n name: string,\n version?: string,\n): string {\n return generateRegistryIdentifier({\n namespace: \"user\",\n owner: username,\n name,\n versionRange: version,\n });\n}\n\n// =============================================================================\n// GitHub Specifier Support (github: protocol, direct download)\n// =============================================================================\n\n/**\n * Parsed GitHub specifier\n *\n * Format: github:{owner}/{repo}[/{path}][@{ref}]\n *\n * @example\n * - github:vercel-labs/agent-skills\n * - github:vercel-labs/agent-skills@main\n * - github:vercel-labs/agent-skills/skills/react-best-practices\n * - github:vercel-labs/agent-skills/skills/react-best-practices@main\n * - github:myorg/prompts/team/frontend/code-review@v2.0.0\n */\nexport interface GitHubSpecifier {\n /** Repository owner */\n owner: string;\n /** Repository name */\n repo: string;\n /** Optional path within the repository (e.g., \"skills/react-best-practices\") */\n path?: string;\n /** Git ref (branch, tag, or commit SHA). Defaults to default branch if not specified. */\n ref?: string;\n}\n\n/**\n * GitHub specifier regex pattern\n * Matches: github:{owner}/{repo}[/{path}][@{ref}]\n *\n * Group 1: owner\n * Group 2: repo\n * Group 3: optional /path (with leading slash)\n * Group 4: optional @ref\n */\nconst GITHUB_SPECIFIER_PATTERN =\n /^github:([a-zA-Z0-9_-]+)\\/([a-zA-Z0-9_.-]+)(\\/[^@]+)?(?:@(.+))?$/;\n\n/**\n * Parse a GitHub specifier string.\n *\n * @param specifier - The specifier string (e.g., \"github:owner/repo/path@ref\")\n * @returns Parsed specifier or null if invalid\n *\n * @example\n * ```typescript\n * parseGitHubSpecifier(\"github:vercel-labs/agent-skills/skills/react@main\")\n * // => { owner: \"vercel-labs\", repo: \"agent-skills\", path: \"skills/react\", ref: \"main\" }\n *\n * parseGitHubSpecifier(\"github:myorg/prompts\")\n * // => { owner: \"myorg\", repo: \"prompts\", path: undefined, ref: undefined }\n * ```\n */\nexport function parseGitHubSpecifier(\n specifier: string,\n): GitHubSpecifier | null {\n const match = specifier.match(GITHUB_SPECIFIER_PATTERN);\n\n if (!match) {\n return null;\n }\n\n const owner = match[1];\n const repo = match[2];\n const pathWithSlash = match[3];\n const ref = match[4];\n\n if (!owner || !repo) {\n return null;\n }\n\n return {\n owner,\n repo,\n // Remove leading slash from path\n path: pathWithSlash ? pathWithSlash.slice(1) : undefined,\n ref: ref || undefined,\n };\n}\n\n/**\n * Format a GitHubSpecifier back to string format.\n *\n * @param spec - The GitHub specifier object\n * @returns Formatted string (e.g., \"github:owner/repo/path@ref\")\n */\nexport function formatGitHubSpecifier(spec: GitHubSpecifier): string {\n let result = `github:${spec.owner}/${spec.repo}`;\n if (spec.path) {\n result += `/${spec.path}`;\n }\n if (spec.ref) {\n result += `@${spec.ref}`;\n }\n return result;\n}\n\n/**\n * Extract skill name from GitHub specifier.\n * Uses the last segment of the path, or the repo name if no path.\n *\n * @param spec - The GitHub specifier object\n * @returns Skill name (e.g., \"react-best-practices\" or \"prompts\")\n *\n * @example\n * ```typescript\n * getGitHubSkillName({ owner: \"vercel-labs\", repo: \"agent-skills\", path: \"skills/react\" })\n * // => \"react\"\n *\n * getGitHubSkillName({ owner: \"myorg\", repo: \"prompts\" })\n * // => \"prompts\"\n * ```\n */\nexport function getGitHubSkillName(spec: GitHubSpecifier): string {\n if (spec.path) {\n const segments = spec.path.split(\"/\").filter(Boolean);\n return segments[segments.length - 1];\n }\n return spec.repo;\n}\n\n/**\n * Check if a string is a GitHub specifier\n */\nexport function isGitHubSpecifier(specifier: string): boolean {\n return specifier.startsWith(\"github:\");\n}\n","/**\n * Namespace-aware API helpers for the dependency resolver.\n *\n * Dispatches list/get calls across `@user`, `@org`, and `@github` namespaces\n * so the main resolver can be written without per-namespace branches.\n */\n\nimport type { RegistrySpecifier } from \"@anytio/skill-types\";\nimport {\n getGithubSkillVersion,\n getOrgSkillVersion,\n getSkillVersion,\n listGithubSkillVersions,\n listOrgSkillVersions,\n listSkillVersions,\n} from \"@/api-client\";\nimport type { VersionDetails } from \"./resolver-types\";\n\n/**\n * Fetch the list of versions for a package across all namespaces.\n */\nexport async function fetchVersionList(\n parsed: RegistrySpecifier,\n): Promise<{ version: string }[] | undefined> {\n if (parsed.namespace === \"github\" && parsed.subname) {\n const resp = await listGithubSkillVersions(\n parsed.owner,\n parsed.name,\n parsed.subname,\n );\n return resp.status === 200 ? resp.data : undefined;\n }\n if (parsed.namespace === \"org\") {\n const resp = await listOrgSkillVersions(parsed.owner, parsed.name);\n return resp.status === 200\n ? (resp.data as { version: string }[])\n : undefined;\n }\n const resp = await listSkillVersions(parsed.owner, parsed.name);\n return resp.status === 200 ? (resp.data as { version: string }[]) : undefined;\n}\n\n/**\n * Fetch package details for a specific version across all namespaces.\n */\nexport async function fetchVersionDetails(\n parsed: RegistrySpecifier,\n version: string,\n): Promise<VersionDetails | null> {\n if (parsed.namespace === \"github\" && parsed.subname) {\n const resp = await getGithubSkillVersion(\n parsed.owner,\n parsed.name,\n parsed.subname,\n version,\n );\n return resp.status === 200 && resp.data ? resp.data : null;\n }\n if (parsed.namespace === \"org\") {\n const resp = await getOrgSkillVersion(parsed.owner, parsed.name, version);\n return resp.status === 200 && resp.data ? resp.data : null;\n }\n const resp = await getSkillVersion(parsed.owner, parsed.name, version);\n return resp.status === 200 && resp.data\n ? (resp.data as unknown as VersionDetails)\n : null;\n}\n","/**\n * Error formatting helpers + topological sort for the dependency resolver.\n */\n\nimport type {\n DependencyGraph,\n ResolutionError,\n VersionConflict,\n} from \"./resolver-types\";\n\n/**\n * Topologically sort packages using Kahn's algorithm.\n * Packages with no dependencies are installed first.\n *\n * @param graph - The dependency graph\n * @returns Sorted list of package names\n */\nexport function topologicalSort(graph: DependencyGraph): string[] {\n const inDegree = new Map<string, number>();\n const dependents = new Map<string, string[]>();\n\n for (const name of graph.nodes.keys()) {\n inDegree.set(name, 0);\n dependents.set(name, []);\n }\n\n for (const [name, node] of graph.nodes.entries()) {\n for (const depName of Object.keys(node.dependencies)) {\n if (graph.nodes.has(depName)) {\n inDegree.set(name, (inDegree.get(name) ?? 0) + 1);\n if (!dependents.has(depName)) {\n dependents.set(depName, []);\n }\n dependents.get(depName)?.push(name);\n }\n }\n }\n\n const queue: string[] = [];\n for (const [name, degree] of inDegree.entries()) {\n if (degree === 0) {\n queue.push(name);\n }\n }\n\n const sorted: string[] = [];\n while (queue.length > 0) {\n const current = queue.shift();\n if (!current) continue;\n sorted.push(current);\n\n const deps = dependents.get(current) ?? [];\n for (const dependent of deps) {\n const newDegree = (inDegree.get(dependent) ?? 1) - 1;\n inDegree.set(dependent, newDegree);\n if (newDegree === 0 && !sorted.includes(dependent)) {\n queue.push(dependent);\n }\n }\n }\n\n return sorted;\n}\n\n/**\n * Compute installation order from lockfile packages.\n * Dependencies are installed before dependents.\n *\n * @param packages - Lockfile packages with dependencies field\n * @returns Sorted list of package names\n */\nexport function computeInstallOrder(\n packages: Record<string, { dependencies?: Record<string, string> }>,\n): string[] {\n const visited = new Set<string>();\n const order: string[] = [];\n\n function visit(name: string) {\n if (visited.has(name)) return;\n visited.add(name);\n\n const entry = packages[name];\n if (entry?.dependencies) {\n for (const dep of Object.keys(entry.dependencies)) {\n visit(dep);\n }\n }\n order.push(name);\n }\n\n for (const name of Object.keys(packages)) {\n visit(name);\n }\n\n return order;\n}\n\n/**\n * Format resolution errors for display.\n *\n * @param errors - Resolution errors\n * @returns Formatted error messages\n */\nexport function formatResolutionErrors(errors: ResolutionError[]): string[] {\n return errors.map((error) => {\n switch (error.type) {\n case \"circular_dependency\":\n return `Circular dependency: ${error.path?.join(\" -> \") ?? error.package}`;\n case \"max_depth_exceeded\":\n return `Max depth exceeded at: ${error.path?.join(\" -> \") ?? error.package}`;\n case \"no_satisfying_version\":\n return error.message;\n case \"package_not_found\":\n return `Package not found: ${error.package}`;\n case \"fetch_error\":\n return error.message;\n default:\n return error.message;\n }\n });\n}\n\n/**\n * Format version conflicts for display.\n *\n * @param conflicts - Version conflicts\n * @returns Formatted conflict messages\n */\nexport function formatVersionConflicts(conflicts: VersionConflict[]): string[] {\n return conflicts.map((conflict) => {\n const requirements = conflict.ranges\n .map((r) => `${r.dependent} needs ${r.range}`)\n .join(\", \");\n return `No version of ${conflict.package} satisfies: ${requirements}`;\n });\n}\n\n/**\n * Print resolution errors to console.\n *\n * @param errors - Resolution errors\n * @param conflicts - Version conflicts\n */\nexport function printResolutionErrors(\n errors: ResolutionError[],\n conflicts: VersionConflict[] = [],\n): void {\n if (errors.length > 0) {\n console.error(\"\\nResolution errors:\");\n for (const msg of formatResolutionErrors(errors)) {\n console.error(` - ${msg}`);\n }\n }\n\n if (conflicts.length > 0) {\n console.error(\"\\nVersion conflicts:\");\n for (const msg of formatVersionConflicts(conflicts)) {\n console.error(` - ${msg}`);\n }\n }\n}\n","import * as semver from \"semver\";\n\n/**\n * Resolve the best matching version from a list of available versions.\n *\n * @param range - The version range to match (e.g., \"^1.0.0\", \"~2.1.0\", \"*\")\n * @param availableVersions - List of available version strings\n * @returns The best matching version or null if none found\n */\nexport function resolveVersion(\n range: string,\n availableVersions: string[],\n): string | null {\n const sorted = availableVersions\n .filter((v) => semver.valid(v))\n .sort((a, b) => semver.rcompare(a, b));\n\n if (!range || range === \"latest\" || range === \"*\") {\n return sorted[0] ?? null;\n }\n\n return semver.maxSatisfying(sorted, range);\n}\n\n/**\n * Check if a version satisfies a given range.\n */\nexport function versionSatisfies(version: string, range: string): boolean {\n return semver.satisfies(version, range);\n}\n\n/**\n * Normalize a version range string.\n * Converts \"latest\" or empty to \"*\".\n */\nexport function normalizeVersionRange(range?: string): string {\n if (!range || range === \"latest\") {\n return \"*\";\n }\n return range;\n}\n\n/**\n * Compare two versions.\n * Returns: -1 if a < b, 0 if a === b, 1 if a > b\n */\nexport function compareVersions(a: string, b: string): number {\n return semver.compare(a, b);\n}\n\n/**\n * Check if version a is greater than version b.\n */\nexport function isNewerVersion(a: string, b: string): boolean {\n return semver.gt(a, b);\n}\n\n/**\n * Get the latest version from a list.\n */\nexport function getLatestVersion(versions: string[]): string | null {\n const valid = versions.filter((v) => semver.valid(v));\n if (valid.length === 0) return null;\n return valid.sort((a, b) => semver.rcompare(a, b))[0] ?? null;\n}\n\n/**\n * Find the highest version that satisfies ALL given ranges.\n * Used for pnpm-style dependency resolution where multiple dependents\n * may require the same package with different version constraints.\n *\n * @param ranges - Array of semver ranges to satisfy (e.g., [\"^1.0.0\", \">=1.2.0\"])\n * @param availableVersions - List of available version strings\n * @returns The highest version satisfying all ranges, or null if none found\n */\nexport function findHighestSatisfying(\n ranges: string[],\n availableVersions: string[],\n): string | null {\n const sorted = availableVersions\n .filter((v) => semver.valid(v))\n .sort((a, b) => semver.rcompare(a, b));\n\n if (sorted.length === 0) return null;\n\n // Normalize ranges\n const normalizedRanges = ranges.map((r) =>\n !r || r === \"latest\" || r === \"*\" ? \"*\" : r,\n );\n\n // Find highest version satisfying all ranges\n for (const version of sorted) {\n const satisfiesAll = normalizedRanges.every((range) =>\n semver.satisfies(version, range),\n );\n if (satisfiesAll) {\n return version;\n }\n }\n\n return null;\n}\n\n/**\n * Intersect multiple semver ranges to find if they're compatible.\n * Returns true if there exists at least one version that could satisfy all ranges.\n *\n * @param ranges - Array of semver ranges to check\n * @returns True if ranges can be satisfied together\n */\nexport function rangesIntersect(ranges: string[]): boolean {\n if (ranges.length === 0) return true;\n if (ranges.length === 1) return true;\n\n // Normalize ranges\n const normalizedRanges = ranges.map((r) =>\n !r || r === \"latest\" || r === \"*\" ? \"*\" : r,\n );\n\n // Check if all ranges intersect by seeing if any version could satisfy all\n // We use a subset of the range to find intersections\n try {\n const intersection = normalizedRanges.reduce((acc, range) => {\n if (acc === \"*\") return range;\n if (range === \"*\") return acc;\n return semver.intersects(acc, range) ? `${acc} ${range}` : \"\";\n }, \"*\");\n return intersection !== \"\";\n } catch {\n return false;\n }\n}\n","/**\n * Recursive Dependency Resolver for PSPM\n *\n * Implements pnpm-style dependency resolution:\n * - Highest satisfying version strategy\n * - 5-depth limit to prevent deep trees\n * - Circular dependency detection\n * - Topological sort for installation order\n */\n\nimport { parseRegistrySpecifier } from \"@anytio/skill-types\";\nimport { configure } from \"@/api-client\";\nimport { fetchVersionDetails, fetchVersionList } from \"./resolver-api\";\nimport {\n computeInstallOrder,\n formatResolutionErrors,\n formatVersionConflicts,\n printResolutionErrors,\n topologicalSort,\n} from \"./resolver-format\";\nimport type {\n DependencyGraph,\n DependencyNode,\n ResolutionResult,\n ResolverConfig,\n VersionDetails,\n} from \"./resolver-types\";\nimport { MAX_DEPENDENCY_DEPTH } from \"./resolver-types\";\nimport { findHighestSatisfying } from \"./version\";\n\nexport type {\n DependencyGraph,\n DependencyNode,\n ResolutionError,\n ResolutionErrorType,\n ResolutionResult,\n ResolverConfig,\n VersionConflict,\n} from \"./resolver-types\";\nexport {\n computeInstallOrder,\n formatResolutionErrors,\n formatVersionConflicts,\n MAX_DEPENDENCY_DEPTH,\n printResolutionErrors,\n topologicalSort,\n};\n\ninterface QueueItem {\n name: string;\n versionRange: string;\n depth: number;\n dependent: string;\n path: string[];\n}\n\ninterface CollectedRange {\n range: string;\n dependent: string;\n depth: number;\n}\n\nfunction buildNode(\n name: string,\n version: string,\n versionRange: string,\n details: VersionDetails,\n depth: number,\n dependent: string,\n): DependencyNode {\n const manifest = details.manifest as\n | { dependencies?: Record<string, string> }\n | undefined;\n return {\n name,\n version,\n versionRange,\n downloadUrl: details.downloadUrl,\n integrity: `sha256-${Buffer.from(details.checksum, \"hex\").toString(\"base64\")}`,\n depth,\n dependencies: manifest?.dependencies ?? {},\n dependents: [dependent],\n isDirect: depth === 0,\n deprecated: details.deprecationMessage ?? undefined,\n };\n}\n\n/**\n * Resolve dependencies recursively using BFS.\n *\n * Algorithm:\n * 1. Queue root dependencies at depth=0\n * 2. For each package, collect all version ranges from dependents\n * 3. Find highest version satisfying ALL ranges\n * 4. Fetch package details including its dependencies\n * 5. Queue transitive dependencies at depth+1\n * 6. Topologically sort for installation order\n *\n * @param rootDeps - Direct dependencies: name -> version range\n * @param config - Resolver configuration\n * @returns Resolution result with graph and install order\n */\nexport async function resolveRecursive(\n rootDeps: Record<string, string>,\n config: ResolverConfig,\n): Promise<ResolutionResult> {\n const graph: DependencyGraph = {\n nodes: new Map(),\n roots: Object.keys(rootDeps),\n errors: [],\n conflicts: [],\n };\n\n configure({\n registryUrl: config.registryUrl,\n apiKey: config.apiKey,\n });\n\n const rangesByPackage = new Map<string, CollectedRange[]>();\n const queue: QueueItem[] = [];\n\n for (const [name, range] of Object.entries(rootDeps)) {\n queue.push({\n name,\n versionRange: range,\n depth: 0,\n dependent: \"root\",\n path: [],\n });\n }\n\n const processing = new Set<string>();\n\n await collectRanges(queue, rangesByPackage, processing, graph, config);\n await resolveFinalVersions(rangesByPackage, graph);\n\n const installOrder = topologicalSort(graph);\n const success = graph.errors.length === 0 && graph.conflicts.length === 0;\n\n return {\n success,\n graph,\n installOrder,\n };\n}\n\nasync function collectRanges(\n queue: QueueItem[],\n rangesByPackage: Map<string, CollectedRange[]>,\n processing: Set<string>,\n graph: DependencyGraph,\n config: ResolverConfig,\n): Promise<void> {\n while (queue.length > 0) {\n const item = queue.shift();\n if (!item) continue;\n const { name, versionRange, depth, dependent, path } = item;\n\n if (depth > config.maxDepth) {\n graph.errors.push({\n type: \"max_depth_exceeded\",\n package: name,\n message: `Maximum dependency depth (${config.maxDepth}) exceeded at: ${[...path, name].join(\" -> \")}`,\n path: [...path, name],\n });\n continue;\n }\n\n if (path.includes(name)) {\n graph.errors.push({\n type: \"circular_dependency\",\n package: name,\n message: `Circular dependency detected: ${[...path, name].join(\" -> \")}`,\n path: [...path, name],\n });\n continue;\n }\n\n if (!rangesByPackage.has(name)) {\n rangesByPackage.set(name, []);\n }\n rangesByPackage.get(name)?.push({\n range: versionRange,\n dependent,\n depth,\n });\n\n if (processing.has(name)) {\n continue;\n }\n processing.add(name);\n\n const parsed = parseRegistrySpecifier(name);\n if (!parsed) {\n graph.errors.push({\n type: \"package_not_found\",\n package: name,\n message: `Invalid package name format: ${name}`,\n });\n continue;\n }\n\n try {\n const versionsData = await fetchVersionList(parsed);\n if (!versionsData) {\n graph.errors.push({\n type: \"package_not_found\",\n package: name,\n message: `Package ${name} not found in registry`,\n });\n continue;\n }\n\n if (versionsData.length === 0) {\n graph.errors.push({\n type: \"package_not_found\",\n package: name,\n message: `Package ${name} has no versions`,\n });\n continue;\n }\n\n const availableVersions = versionsData.map((v) => v.version);\n const resolvedVersion = findHighestSatisfying(\n [versionRange],\n availableVersions,\n );\n\n if (!resolvedVersion) {\n graph.errors.push({\n type: \"no_satisfying_version\",\n package: name,\n message: `No version of ${name} satisfies: ${versionRange}`,\n });\n continue;\n }\n\n const versionData = await fetchVersionDetails(parsed, resolvedVersion);\n if (!versionData) {\n graph.errors.push({\n type: \"fetch_error\",\n package: name,\n message: `Failed to fetch ${name}@${resolvedVersion}`,\n });\n continue;\n }\n\n const node = buildNode(\n name,\n resolvedVersion,\n versionRange,\n versionData,\n depth,\n dependent,\n );\n graph.nodes.set(name, node);\n\n for (const [depName, depRange] of Object.entries(node.dependencies)) {\n queue.push({\n name: depName,\n versionRange: depRange,\n depth: depth + 1,\n dependent: name,\n path: [...path, name],\n });\n }\n } catch (error) {\n const message = error instanceof Error ? error.message : \"Unknown error\";\n graph.errors.push({\n type: \"fetch_error\",\n package: name,\n message: `Error fetching ${name}: ${message}`,\n });\n }\n }\n}\n\nasync function resolveFinalVersions(\n rangesByPackage: Map<string, CollectedRange[]>,\n graph: DependencyGraph,\n): Promise<void> {\n for (const [name, ranges] of rangesByPackage.entries()) {\n const node = graph.nodes.get(name);\n if (!node) continue;\n\n const uniqueDependents = [...new Set(ranges.map((r) => r.dependent))];\n node.dependents = uniqueDependents;\n\n const allRanges = ranges.map((r) => r.range);\n const parsed = parseRegistrySpecifier(name);\n if (!parsed) continue;\n\n try {\n const versions = await fetchVersionList(parsed);\n if (!versions) continue;\n\n const availableVersions = versions.map((v) => v.version);\n const finalVersion = findHighestSatisfying(allRanges, availableVersions);\n\n if (!finalVersion) {\n graph.conflicts.push({\n package: name,\n ranges: ranges.map((r) => ({\n dependent: r.dependent,\n range: r.range,\n })),\n availableVersions,\n });\n graph.errors.push({\n type: \"no_satisfying_version\",\n package: name,\n message: `No version of ${name} satisfies all requirements: ${allRanges.join(\", \")}`,\n });\n continue;\n }\n\n if (finalVersion !== node.version) {\n const versionData = await fetchVersionDetails(parsed, finalVersion);\n if (versionData) {\n node.version = finalVersion;\n node.downloadUrl = versionData.downloadUrl;\n node.integrity = `sha256-${Buffer.from(versionData.checksum, \"hex\").toString(\"base64\")}`;\n node.deprecated = versionData.deprecationMessage ?? undefined;\n\n const manifest = versionData.manifest as\n | { dependencies?: Record<string, string> }\n | undefined;\n node.dependencies = manifest?.dependencies ?? {};\n }\n }\n } catch {\n // Already have a node, keep it\n }\n }\n}\n","// =============================================================================\n// Registry Namespace Types\n// =============================================================================\n\n/**\n * Supported registry namespace types.\n *\n * - \"user\" — Individual user skills: @user/{username}/{skillname}\n * - \"org\" — Organization skills: @org/{orgname}/{skillname}\n * - \"github\" — GitHub-indexed skills: @github/{owner}/{repo}/{skillname}\n */\nexport type NamespaceType = \"user\" | \"org\" | \"github\";\n\n/**\n * Parsed registry specifier (unified across all namespaces).\n *\n * @example\n * - @user/bsheng/vite-slides -> { namespace: \"user\", owner: \"bsheng\", name: \"vite-slides\" }\n * - @org/anyt/code-review@^2.0.0 -> { namespace: \"org\", owner: \"anyt\", name: \"code-review\", versionRange: \"^2.0.0\" }\n * - @github/microsoft/skills/azure-ai -> { namespace: \"github\", owner: \"microsoft\", name: \"skills\", subname: \"azure-ai\" }\n */\nexport interface RegistrySpecifier {\n namespace: NamespaceType;\n /** username for @user, orgname for @org, GitHub owner for @github */\n owner: string;\n /** skill name for @user/@org, repo name for @github */\n name: string;\n /** skill name within the repo (only for @github) */\n subname?: string;\n versionRange?: string;\n}\n\n// =============================================================================\n// Registry Specifier Parsing\n// =============================================================================\n\n/**\n * Unified registry specifier regex pattern.\n *\n * Matches:\n * - @user/{owner}/{name}[@version]\n * - @org/{owner}/{name}[@version]\n * - @github/{owner}/{repo}/{skillname}[@version]\n *\n * Group 1: namespace (user|org|github)\n * Group 2: owner\n * Group 3: name (skill name for user/org, repo name for github)\n * Group 4: optional subname (skill name within repo, github only)\n * Group 5: optional @version\n */\nconst REGISTRY_SPECIFIER_PATTERN =\n /^@(user|org|github)\\/([a-zA-Z0-9_-]+)\\/([a-zA-Z0-9._-]+)(?:\\/([a-z][a-z0-9-]*))?(?:@(.+))?$/;\n\n/**\n * Parse a registry specifier string (any namespace).\n *\n * @param specifier - The specifier string\n * @returns Parsed specifier or null if invalid\n */\nexport function parseRegistrySpecifier(\n specifier: string,\n): RegistrySpecifier | null {\n const match = specifier.match(REGISTRY_SPECIFIER_PATTERN);\n\n if (!match) {\n return null;\n }\n\n const namespace = match[1] as NamespaceType;\n const owner = match[2];\n const name = match[3];\n const subname = match[4];\n const versionRange = match[5];\n\n if (!owner || !name) {\n return null;\n }\n\n // @github requires a subname (skill within repo)\n if (namespace === \"github\" && !subname) {\n return null;\n }\n\n // @user and @org should not have a subname\n if (namespace !== \"github\" && subname) {\n return null;\n }\n\n return {\n namespace,\n owner,\n name,\n subname: subname || undefined,\n versionRange: versionRange || undefined,\n };\n}\n\n/**\n * Generate a full registry identifier string.\n */\nexport function generateRegistryIdentifier(\n spec: Pick<\n RegistrySpecifier,\n \"namespace\" | \"owner\" | \"name\" | \"subname\" | \"versionRange\"\n >,\n): string {\n let base = `@${spec.namespace}/${spec.owner}/${spec.name}`;\n if (spec.subname) {\n base += `/${spec.subname}`;\n }\n if (spec.versionRange) {\n base += `@${spec.versionRange}`;\n }\n return base;\n}\n\n/**\n * Check if a string is a registry specifier (starts with @user/, @org/, or @github/).\n */\nexport function isRegistrySpecifier(specifier: string): boolean {\n return (\n specifier.startsWith(\"@user/\") ||\n specifier.startsWith(\"@org/\") ||\n specifier.startsWith(\"@github/\")\n );\n}\n\n/**\n * Get the effective skill name from a registry specifier.\n * For @user/@org this is `name`, for @github this is `subname`.\n */\nexport function getRegistrySkillName(spec: RegistrySpecifier): string {\n return spec.subname ?? spec.name;\n}\n\n// =============================================================================\n// Backward-Compatible Aliases (@user namespace only)\n// =============================================================================\n\n/**\n * Parsed skill specifier (@user namespace only).\n * @deprecated Use `RegistrySpecifier` instead for multi-namespace support.\n */\nexport interface SkillSpecifier {\n username: string;\n name: string;\n versionRange?: string;\n}\n\n/**\n * Parse a skill specifier string (@user namespace only, backward compat).\n * @deprecated Use `parseRegistrySpecifier` instead.\n */\nexport function parseSkillSpecifier(specifier: string): SkillSpecifier | null {\n const result = parseRegistrySpecifier(specifier);\n if (!result || result.namespace !== \"user\") {\n return null;\n }\n return {\n username: result.owner,\n name: result.name,\n versionRange: result.versionRange,\n };\n}\n\n/**\n * Generate a full skill identifier string (@user namespace).\n * @deprecated Use `generateRegistryIdentifier` instead.\n */\nexport function generateSkillIdentifier(\n username: string,\n name: string,\n version?: string,\n): string {\n return generateRegistryIdentifier({\n namespace: \"user\",\n owner: username,\n name,\n versionRange: version,\n });\n}\n\n// =============================================================================\n// GitHub Specifier Support (github: protocol, direct download)\n// =============================================================================\n\n/**\n * Parsed GitHub specifier\n *\n * Format: github:{owner}/{repo}[/{path}][@{ref}]\n *\n * @example\n * - github:vercel-labs/agent-skills\n * - github:vercel-labs/agent-skills@main\n * - github:vercel-labs/agent-skills/skills/react-best-practices\n * - github:vercel-labs/agent-skills/skills/react-best-practices@main\n * - github:myorg/prompts/team/frontend/code-review@v2.0.0\n */\nexport interface GitHubSpecifier {\n /** Repository owner */\n owner: string;\n /** Repository name */\n repo: string;\n /** Optional path within the repository (e.g., \"skills/react-best-practices\") */\n path?: string;\n /** Git ref (branch, tag, or commit SHA). Defaults to default branch if not specified. */\n ref?: string;\n}\n\n/**\n * GitHub specifier regex pattern\n * Matches: github:{owner}/{repo}[/{path}][@{ref}]\n *\n * Group 1: owner\n * Group 2: repo\n * Group 3: optional /path (with leading slash)\n * Group 4: optional @ref\n */\nconst GITHUB_SPECIFIER_PATTERN =\n /^github:([a-zA-Z0-9_-]+)\\/([a-zA-Z0-9_.-]+)(\\/[^@]+)?(?:@(.+))?$/;\n\n/**\n * Parse a GitHub specifier string.\n *\n * @param specifier - The specifier string (e.g., \"github:owner/repo/path@ref\")\n * @returns Parsed specifier or null if invalid\n *\n * @example\n * ```typescript\n * parseGitHubSpecifier(\"github:vercel-labs/agent-skills/skills/react@main\")\n * // => { owner: \"vercel-labs\", repo: \"agent-skills\", path: \"skills/react\", ref: \"main\" }\n *\n * parseGitHubSpecifier(\"github:myorg/prompts\")\n * // => { owner: \"myorg\", repo: \"prompts\", path: undefined, ref: undefined }\n * ```\n */\nexport function parseGitHubSpecifier(\n specifier: string,\n): GitHubSpecifier | null {\n const match = specifier.match(GITHUB_SPECIFIER_PATTERN);\n\n if (!match) {\n return null;\n }\n\n const [, owner, repo, pathWithSlash, ref] = match;\n if (!owner || !repo) {\n return null;\n }\n\n return {\n owner,\n repo,\n // Remove leading slash from path\n path: pathWithSlash ? pathWithSlash.slice(1) : undefined,\n ref: ref || undefined,\n };\n}\n\n/**\n * Format a GitHubSpecifier back to string format.\n *\n * @param spec - The GitHub specifier object\n * @returns Formatted string (e.g., \"github:owner/repo/path@ref\")\n */\nexport function formatGitHubSpecifier(spec: GitHubSpecifier): string {\n let result = `github:${spec.owner}/${spec.repo}`;\n if (spec.path) {\n result += `/${spec.path}`;\n }\n if (spec.ref) {\n result += `@${spec.ref}`;\n }\n return result;\n}\n\n/**\n * Extract skill name from GitHub specifier.\n * Uses the last segment of the path, or the repo name if no path.\n *\n * @param spec - The GitHub specifier object\n * @returns Skill name (e.g., \"react-best-practices\" or \"prompts\")\n *\n * @example\n * ```typescript\n * getGitHubSkillName({ owner: \"vercel-labs\", repo: \"agent-skills\", path: \"skills/react\" })\n * // => \"react\"\n *\n * getGitHubSkillName({ owner: \"myorg\", repo: \"prompts\" })\n * // => \"prompts\"\n * ```\n */\nexport function getGitHubSkillName(spec: GitHubSpecifier): string {\n if (spec.path) {\n const segments = spec.path.split(\"/\").filter(Boolean);\n const lastSegment = segments[segments.length - 1];\n if (lastSegment) {\n return lastSegment;\n }\n }\n return spec.repo;\n}\n\n/**\n * Check if a string is a GitHub specifier (github: prefix)\n */\nexport function isGitHubSpecifier(specifier: string): boolean {\n return specifier.startsWith(\"github:\");\n}\n\n// =============================================================================\n// GitHub URL and Shorthand Support\n// =============================================================================\n\n/**\n * GitHub URL patterns\n *\n * Matches:\n * - https://github.com/owner/repo/tree/branch/path/to/skill\n * - https://github.com/owner/repo/tree/branch\n * - https://github.com/owner/repo\n * - https://github.com/owner/repo.git\n */\nconst GITHUB_URL_TREE_PATTERN =\n /^https?:\\/\\/github\\.com\\/([^/]+)\\/([^/]+)\\/tree\\/([^/]+)(?:\\/(.+))?$/;\nconst GITHUB_URL_PATTERN =\n /^https?:\\/\\/github\\.com\\/([^/]+)\\/([^/]+?)(?:\\.git)?\\/?$/;\n\n/**\n * GitHub shorthand pattern: owner/repo or owner/repo/path\n * Must not contain :, not start with . / or @\n */\nconst GITHUB_SHORTHAND_PATTERN =\n /^([a-zA-Z0-9_-]+)\\/([a-zA-Z0-9_.-]+)(?:\\/(.+))?$/;\n\n/**\n * Check if a string is a GitHub URL (https://github.com/...)\n */\nexport function isGitHubUrl(input: string): boolean {\n return /^https?:\\/\\/github\\.com\\/[^/]+\\/[^/]+/.test(input);\n}\n\n/**\n * Parse a GitHub URL into a GitHubSpecifier.\n */\nexport function parseGitHubUrl(input: string): GitHubSpecifier | null {\n // Try tree URL first (more specific)\n const treeMatch = input.match(GITHUB_URL_TREE_PATTERN);\n if (treeMatch) {\n const [, owner, repo, ref, path] = treeMatch;\n if (!owner || !repo || !ref) return null;\n return {\n owner,\n repo,\n ref,\n path: path || undefined,\n };\n }\n\n // Plain repo URL\n const repoMatch = input.match(GITHUB_URL_PATTERN);\n if (repoMatch) {\n const [, owner, repo] = repoMatch;\n if (!owner || !repo) return null;\n return { owner, repo };\n }\n\n return null;\n}\n\n/**\n * Check if a string is a GitHub shorthand (owner/repo or owner/repo/path).\n */\nexport function isGitHubShorthand(input: string): boolean {\n if (\n input.includes(\":\") ||\n input.startsWith(\".\") ||\n input.startsWith(\"/\") ||\n input.startsWith(\"@\")\n ) {\n return false;\n }\n return GITHUB_SHORTHAND_PATTERN.test(input);\n}\n\n/**\n * Parse a GitHub shorthand into a GitHubSpecifier.\n */\nexport function parseGitHubShorthand(input: string): GitHubSpecifier | null {\n if (\n input.includes(\":\") ||\n input.startsWith(\".\") ||\n input.startsWith(\"/\") ||\n input.startsWith(\"@\")\n ) {\n return null;\n }\n\n const match = input.match(GITHUB_SHORTHAND_PATTERN);\n if (!match) return null;\n\n const [, owner, repo, path] = match;\n if (!owner || !repo) return null;\n\n return {\n owner,\n repo,\n path: path || undefined,\n };\n}\n","/**\n * Agent configuration for skill symlinks.\n *\n * Defines where different AI agents expect skills to be located.\n */\n\nimport { checkbox } from \"@inquirer/prompts\";\nimport type { AgentConfig, BuiltInAgent } from \"./lib/index\";\n\n/**\n * Agent metadata for display purposes.\n */\nexport interface AgentInfo {\n /** Human-readable name for display */\n displayName: string;\n /** Skills directory path (project-level, relative to project root) */\n skillsDir: string;\n /** Global skills directory path (relative to home directory) */\n globalSkillsDir: string;\n}\n\n/**\n * Default agent configurations with display names.\n * These can be overridden in pspm.json under the \"agents\" key.\n */\nexport const AGENT_INFO: Record<BuiltInAgent, AgentInfo> = {\n adal: {\n displayName: \"AdaL\",\n skillsDir: \".adal/skills\",\n globalSkillsDir: \".adal/skills\",\n },\n amp: {\n displayName: \"Amp\",\n skillsDir: \".agents/skills\",\n globalSkillsDir: \".config/agents/skills\",\n },\n antigravity: {\n displayName: \"Antigravity\",\n skillsDir: \".agent/skills\",\n globalSkillsDir: \".gemini/antigravity/skills\",\n },\n augment: {\n displayName: \"Augment\",\n skillsDir: \".augment/skills\",\n globalSkillsDir: \".augment/skills\",\n },\n \"claude-code\": {\n displayName: \"Claude Code\",\n skillsDir: \".claude/skills\",\n globalSkillsDir: \".claude/skills\",\n },\n cline: {\n displayName: \"Cline\",\n skillsDir: \".agents/skills\",\n globalSkillsDir: \".agents/skills\",\n },\n codebuddy: {\n displayName: \"CodeBuddy\",\n skillsDir: \".codebuddy/skills\",\n globalSkillsDir: \".codebuddy/skills\",\n },\n codex: {\n displayName: \"Codex\",\n skillsDir: \".agents/skills\",\n globalSkillsDir: \".codex/skills\",\n },\n \"command-code\": {\n displayName: \"Command Code\",\n skillsDir: \".commandcode/skills\",\n globalSkillsDir: \".commandcode/skills\",\n },\n continue: {\n displayName: \"Continue\",\n skillsDir: \".continue/skills\",\n globalSkillsDir: \".continue/skills\",\n },\n cortex: {\n displayName: \"Cortex Code\",\n skillsDir: \".cortex/skills\",\n globalSkillsDir: \".snowflake/cortex/skills\",\n },\n crush: {\n displayName: \"Crush\",\n skillsDir: \".crush/skills\",\n globalSkillsDir: \".config/crush/skills\",\n },\n cursor: {\n displayName: \"Cursor\",\n skillsDir: \".agents/skills\",\n globalSkillsDir: \".cursor/skills\",\n },\n droid: {\n displayName: \"Droid\",\n skillsDir: \".factory/skills\",\n globalSkillsDir: \".factory/skills\",\n },\n \"gemini-cli\": {\n displayName: \"Gemini CLI\",\n skillsDir: \".agents/skills\",\n globalSkillsDir: \".gemini/skills\",\n },\n \"github-copilot\": {\n displayName: \"GitHub Copilot\",\n skillsDir: \".agents/skills\",\n globalSkillsDir: \".copilot/skills\",\n },\n goose: {\n displayName: \"Goose\",\n skillsDir: \".goose/skills\",\n globalSkillsDir: \".config/goose/skills\",\n },\n \"iflow-cli\": {\n displayName: \"iFlow CLI\",\n skillsDir: \".iflow/skills\",\n globalSkillsDir: \".iflow/skills\",\n },\n junie: {\n displayName: \"Junie\",\n skillsDir: \".junie/skills\",\n globalSkillsDir: \".junie/skills\",\n },\n kilo: {\n displayName: \"Kilo Code\",\n skillsDir: \".kilocode/skills\",\n globalSkillsDir: \".kilocode/skills\",\n },\n \"kimi-cli\": {\n displayName: \"Kimi Code CLI\",\n skillsDir: \".agents/skills\",\n globalSkillsDir: \".config/agents/skills\",\n },\n \"kiro-cli\": {\n displayName: \"Kiro CLI\",\n skillsDir: \".kiro/skills\",\n globalSkillsDir: \".kiro/skills\",\n },\n kode: {\n displayName: \"Kode\",\n skillsDir: \".kode/skills\",\n globalSkillsDir: \".kode/skills\",\n },\n mcpjam: {\n displayName: \"MCPJam\",\n skillsDir: \".mcpjam/skills\",\n globalSkillsDir: \".mcpjam/skills\",\n },\n \"mistral-vibe\": {\n displayName: \"Mistral Vibe\",\n skillsDir: \".vibe/skills\",\n globalSkillsDir: \".vibe/skills\",\n },\n mux: {\n displayName: \"Mux\",\n skillsDir: \".mux/skills\",\n globalSkillsDir: \".mux/skills\",\n },\n neovate: {\n displayName: \"Neovate\",\n skillsDir: \".neovate/skills\",\n globalSkillsDir: \".neovate/skills\",\n },\n openclaw: {\n displayName: \"OpenClaw\",\n skillsDir: \"skills\",\n globalSkillsDir: \".openclaw/skills\",\n },\n opencode: {\n displayName: \"OpenCode\",\n skillsDir: \".agents/skills\",\n globalSkillsDir: \".config/opencode/skills\",\n },\n openhands: {\n displayName: \"OpenHands\",\n skillsDir: \".openhands/skills\",\n globalSkillsDir: \".openhands/skills\",\n },\n pi: {\n displayName: \"Pi\",\n skillsDir: \".pi/skills\",\n globalSkillsDir: \".pi/agent/skills\",\n },\n pochi: {\n displayName: \"Pochi\",\n skillsDir: \".pochi/skills\",\n globalSkillsDir: \".pochi/skills\",\n },\n qoder: {\n displayName: \"Qoder\",\n skillsDir: \".qoder/skills\",\n globalSkillsDir: \".qoder/skills\",\n },\n \"qwen-code\": {\n displayName: \"Qwen Code\",\n skillsDir: \".qwen/skills\",\n globalSkillsDir: \".qwen/skills\",\n },\n replit: {\n displayName: \"Replit\",\n skillsDir: \".agents/skills\",\n globalSkillsDir: \".config/agents/skills\",\n },\n roo: {\n displayName: \"Roo Code\",\n skillsDir: \".roo/skills\",\n globalSkillsDir: \".roo/skills\",\n },\n trae: {\n displayName: \"Trae\",\n skillsDir: \".trae/skills\",\n globalSkillsDir: \".trae/skills\",\n },\n \"trae-cn\": {\n displayName: \"Trae CN\",\n skillsDir: \".trae/skills\",\n globalSkillsDir: \".trae-cn/skills\",\n },\n universal: {\n displayName: \"Universal\",\n skillsDir: \".agents/skills\",\n globalSkillsDir: \".config/agents/skills\",\n },\n windsurf: {\n displayName: \"Windsurf\",\n skillsDir: \".windsurf/skills\",\n globalSkillsDir: \".codeium/windsurf/skills\",\n },\n zencoder: {\n displayName: \"Zencoder\",\n skillsDir: \".zencoder/skills\",\n globalSkillsDir: \".zencoder/skills\",\n },\n};\n\n/**\n * Default agent configurations (AgentConfig format).\n */\nexport const DEFAULT_AGENT_CONFIGS: Record<BuiltInAgent, AgentConfig> =\n Object.fromEntries(\n Object.entries(AGENT_INFO).map(([key, info]) => [\n key,\n { skillsDir: info.skillsDir },\n ]),\n ) as Record<BuiltInAgent, AgentConfig>;\n\n/**\n * All built-in agent names in display order.\n */\nexport const ALL_AGENTS: BuiltInAgent[] = Object.keys(\n AGENT_INFO,\n).sort() as BuiltInAgent[];\n\n/**\n * Resolve agent configuration by name.\n *\n * @param name - Agent name (built-in or custom)\n * @param overrides - Custom agent configurations from pspm.json\n * @param global - If true, return global paths instead of project paths\n * @returns Agent configuration or null if not found\n *\n * @example\n * ```typescript\n * resolveAgentConfig(\"claude-code\")\n * // => { skillsDir: \".claude/skills\" }\n *\n * resolveAgentConfig(\"claude-code\", undefined, true)\n * // => { skillsDir: \".claude/skills\" } (global path, used relative to ~)\n *\n * resolveAgentConfig(\"my-custom\", { \"my-custom\": { skillsDir: \".myagent/prompts\" } })\n * // => { skillsDir: \".myagent/prompts\" }\n * ```\n */\nexport function resolveAgentConfig(\n name: string,\n overrides?: Record<string, AgentConfig>,\n global?: boolean,\n): AgentConfig | null {\n // Check overrides first (not applicable for global)\n if (!global && overrides?.[name]) {\n return overrides[name];\n }\n\n // Check built-in defaults\n if (name in AGENT_INFO) {\n const info = AGENT_INFO[name as BuiltInAgent];\n return {\n skillsDir: global ? info.globalSkillsDir : info.skillsDir,\n };\n }\n\n return null;\n}\n\n/**\n * Parse comma-separated agent names from CLI argument.\n *\n * @param agentArg - Comma-separated agent names (e.g., \"claude-code,cursor\")\n * @returns Array of agent names, or [\"none\"] if skipping symlinks\n *\n * @example\n * ```typescript\n * parseAgentArg(\"claude-code,cursor\")\n * // => [\"claude-code\", \"cursor\"]\n *\n * parseAgentArg(\"none\")\n * // => [\"none\"]\n *\n * parseAgentArg(undefined)\n * // => [...ALL_AGENTS]\n * ```\n */\nexport function parseAgentArg(agentArg?: string): string[] {\n if (!agentArg) {\n return [...ALL_AGENTS];\n }\n\n if (agentArg === \"none\") {\n return [\"none\"];\n }\n\n return agentArg\n .split(\",\")\n .map((a) => a.trim())\n .filter(Boolean);\n}\n\n/**\n * Get all available agent names (built-in + custom).\n */\nexport function getAvailableAgents(\n overrides?: Record<string, AgentConfig>,\n): string[] {\n const builtIn = Object.keys(DEFAULT_AGENT_CONFIGS);\n const custom = overrides ? Object.keys(overrides) : [];\n return [...new Set([...builtIn, ...custom])];\n}\n\n/**\n * Prompt user to select which agents to install skills to.\n *\n * @returns Array of selected agent names\n */\nexport async function promptForAgents(): Promise<string[]> {\n const choices = ALL_AGENTS.map((agent) => ({\n name: `${AGENT_INFO[agent].displayName} (${AGENT_INFO[agent].skillsDir})`,\n value: agent,\n checked: true, // All selected by default\n }));\n\n const selected = await checkbox({\n message: \"Select agents to install skills to\",\n choices,\n });\n\n if (selected.length === 0) {\n return [\"none\"];\n }\n\n return selected;\n}\n","import { readFile, writeFile } from \"node:fs/promises\";\nimport { homedir } from \"node:os\";\nimport { join } from \"node:path\";\nimport { isGlobalMode } from \"./config\";\nimport type { PspmManifest } from \"./lib/index\";\n\n/**\n * Get the manifest file path\n * Global: ~/.pspm/pspm.json\n * Project: ./pspm.json\n */\nexport function getManifestPath(): string {\n if (isGlobalMode()) {\n return join(homedir(), \".pspm\", \"pspm.json\");\n }\n return join(process.cwd(), \"pspm.json\");\n}\n\n/**\n * Read the manifest file (pspm.json)\n * Returns null if file doesn't exist\n */\nexport async function readManifest(): Promise<PspmManifest | null> {\n try {\n const content = await readFile(getManifestPath(), \"utf-8\");\n return JSON.parse(content) as PspmManifest;\n } catch {\n return null;\n }\n}\n\n/**\n * Write the manifest file (pspm.json)\n */\nexport async function writeManifest(manifest: PspmManifest): Promise<void> {\n const content = JSON.stringify(manifest, null, 2);\n await writeFile(getManifestPath(), `${content}\\n`);\n}\n\n/**\n * Create a minimal manifest with just dependencies\n * Similar to how npm creates package.json with just dependencies when you run `npm add`\n * This is for consuming packages, not publishing - so only dependencies are needed\n */\nexport async function createMinimalManifest(): Promise<PspmManifest> {\n return {\n dependencies: {},\n } as PspmManifest;\n}\n\n/**\n * Ensure manifest exists, creating a minimal one if needed\n * Returns the manifest (existing or newly created)\n */\nexport async function ensureManifest(): Promise<PspmManifest> {\n let manifest = await readManifest();\n\n if (!manifest) {\n manifest = await createMinimalManifest();\n await writeManifest(manifest);\n }\n\n return manifest;\n}\n\n/**\n * Add a dependency to the manifest\n * Creates the manifest if it doesn't exist\n *\n * @param skillName - Full skill name (e.g., \"@user/alice/my-skill\")\n * @param versionRange - Version range to save (e.g., \"^1.0.0\")\n */\nexport async function addDependency(\n skillName: string,\n versionRange: string,\n): Promise<void> {\n const manifest = await ensureManifest();\n\n // Initialize dependencies if not present\n if (!manifest.dependencies) {\n manifest.dependencies = {};\n }\n\n // Add or update the dependency\n manifest.dependencies[skillName] = versionRange;\n\n await writeManifest(manifest);\n}\n\n/**\n * Remove a dependency from the manifest\n *\n * @param skillName - Full skill name (e.g., \"@user/alice/my-skill\")\n * @returns true if dependency was removed, false if it didn't exist\n */\nexport async function removeDependency(skillName: string): Promise<boolean> {\n const manifest = await readManifest();\n\n if (!manifest?.dependencies?.[skillName]) {\n return false;\n }\n\n delete manifest.dependencies[skillName];\n await writeManifest(manifest);\n return true;\n}\n\n/**\n * Get all dependencies from the manifest\n * Returns empty object if manifest doesn't exist or has no dependencies\n */\nexport async function getDependencies(): Promise<Record<string, string>> {\n const manifest = await readManifest();\n return manifest?.dependencies ?? {};\n}\n\n/**\n * Get all GitHub dependencies from the manifest\n * Returns empty object if manifest doesn't exist or has no GitHub dependencies\n */\nexport async function getGitHubDependencies(): Promise<Record<string, string>> {\n const manifest = await readManifest();\n return manifest?.githubDependencies ?? {};\n}\n\n/**\n * Add a GitHub dependency to the manifest\n * Creates the manifest if it doesn't exist\n *\n * @param specifier - GitHub specifier (e.g., \"github:owner/repo/path\")\n * @param ref - Git ref (branch, tag, or \"latest\")\n */\nexport async function addGitHubDependency(\n specifier: string,\n ref: string,\n): Promise<void> {\n const manifest = await ensureManifest();\n\n // Initialize githubDependencies if not present\n if (!manifest.githubDependencies) {\n manifest.githubDependencies = {};\n }\n\n // Add or update the dependency\n manifest.githubDependencies[specifier] = ref;\n\n await writeManifest(manifest);\n}\n\n/**\n * Remove a GitHub dependency from the manifest\n *\n * @param specifier - GitHub specifier (e.g., \"github:owner/repo/path\")\n * @returns true if dependency was removed, false if it didn't exist\n */\nexport async function removeGitHubDependency(\n specifier: string,\n): Promise<boolean> {\n const manifest = await readManifest();\n\n if (!manifest?.githubDependencies?.[specifier]) {\n return false;\n }\n\n delete manifest.githubDependencies[specifier];\n await writeManifest(manifest);\n return true;\n}\n\n// =============================================================================\n// Local Dependency Support\n// =============================================================================\n\n/**\n * Add a local dependency to the manifest\n * Creates the manifest if it doesn't exist\n *\n * @param specifier - Local specifier (e.g., \"file:../my-skill\")\n * @param version - Always \"*\" for local packages\n */\nexport async function addLocalDependency(\n specifier: string,\n version = \"*\",\n): Promise<void> {\n const manifest = await ensureManifest();\n\n // Initialize localDependencies if not present\n if (!manifest.localDependencies) {\n manifest.localDependencies = {};\n }\n\n // Add or update the dependency\n manifest.localDependencies[specifier] = version;\n\n await writeManifest(manifest);\n}\n\n// =============================================================================\n// Well-Known Dependency Support\n// =============================================================================\n\n/**\n * Add a well-known dependency to the manifest\n *\n * @param baseUrl - The well-known base URL (e.g., \"https://acme.com\")\n * @param skillNames - Skill names to add (e.g., [\"code-review\"])\n */\nexport async function addWellKnownDependency(\n baseUrl: string,\n skillNames: string[],\n): Promise<void> {\n const manifest = await ensureManifest();\n\n if (!manifest.wellKnownDependencies) {\n manifest.wellKnownDependencies = {};\n }\n\n const existing = manifest.wellKnownDependencies[baseUrl];\n if (Array.isArray(existing)) {\n // Merge with existing, dedup\n const merged = [...new Set([...existing, ...skillNames])];\n manifest.wellKnownDependencies[baseUrl] = merged;\n } else {\n manifest.wellKnownDependencies[baseUrl] = skillNames;\n }\n\n await writeManifest(manifest);\n}\n","/**\n * GitHub package download and extraction support.\n *\n * Downloads skill packages from GitHub repositories and extracts them\n * to .pspm/skills/_github/{owner}/{repo}/{path}/\n */\n\nimport { cp, lstat, mkdir, readdir, rm, writeFile } from \"node:fs/promises\";\nimport { join } from \"node:path\";\nimport type { GitHubSpecifier } from \"./lib/index\";\nimport { calculateIntegrity } from \"./lib/index\";\n\n/**\n * Result of downloading a GitHub package.\n */\nexport interface GitHubDownloadResult {\n /** Downloaded tarball as buffer */\n buffer: Buffer;\n /** Resolved commit SHA */\n commit: string;\n /** Integrity hash (sha256-...) */\n integrity: string;\n}\n\n/**\n * Error thrown when GitHub API rate limit is hit.\n */\nexport class GitHubRateLimitError extends Error {\n constructor() {\n super(\n \"GitHub API rate limit exceeded. Set GITHUB_TOKEN environment variable for higher limits.\",\n );\n this.name = \"GitHubRateLimitError\";\n }\n}\n\n/**\n * Error thrown when GitHub repository/ref is not found.\n */\nexport class GitHubNotFoundError extends Error {\n constructor(spec: GitHubSpecifier) {\n const path = spec.path ? `/${spec.path}` : \"\";\n const ref = spec.ref ? `@${spec.ref}` : \"\";\n super(\n `GitHub repository not found: ${spec.owner}/${spec.repo}${path}${ref}`,\n );\n this.name = \"GitHubNotFoundError\";\n }\n}\n\n/**\n * Error thrown when the specified path doesn't exist in the repository.\n */\nexport class GitHubPathNotFoundError extends Error {\n constructor(spec: GitHubSpecifier, availablePaths?: string[]) {\n const pathInfo = availablePaths?.length\n ? `\\nAvailable paths in repository root:\\n ${availablePaths.join(\"\\n \")}`\n : \"\";\n super(\n `Path \"${spec.path}\" not found in ${spec.owner}/${spec.repo}${pathInfo}`,\n );\n this.name = \"GitHubPathNotFoundError\";\n }\n}\n\n/**\n * Get GitHub API headers, including authentication if available.\n */\nfunction getGitHubHeaders(): Record<string, string> {\n const headers: Record<string, string> = {\n Accept: \"application/vnd.github+json\",\n \"X-GitHub-Api-Version\": \"2022-11-28\",\n \"User-Agent\": \"pspm-cli\",\n };\n\n const token = process.env.GITHUB_TOKEN;\n if (token) {\n headers.Authorization = `Bearer ${token}`;\n }\n\n return headers;\n}\n\n/**\n * Resolve a Git ref (branch/tag) to a commit SHA.\n *\n * @param owner - Repository owner\n * @param repo - Repository name\n * @param ref - Branch, tag, or commit SHA (defaults to default branch)\n * @returns Resolved commit SHA\n */\nexport async function resolveGitHubRef(\n owner: string,\n repo: string,\n ref?: string,\n): Promise<string> {\n const headers = getGitHubHeaders();\n\n // Use a local variable to avoid parameter reassignment\n let resolvedRef = ref;\n\n // If no ref specified, get the default branch first\n if (!resolvedRef || resolvedRef === \"latest\") {\n const repoUrl = `https://api.github.com/repos/${owner}/${repo}`;\n const repoResponse = await fetch(repoUrl, { headers });\n\n if (repoResponse.status === 404) {\n throw new GitHubNotFoundError({ owner, repo });\n }\n\n if (repoResponse.status === 403) {\n const remaining = repoResponse.headers.get(\"x-ratelimit-remaining\");\n if (remaining === \"0\") {\n throw new GitHubRateLimitError();\n }\n }\n\n if (!repoResponse.ok) {\n throw new Error(`GitHub API error: ${repoResponse.status}`);\n }\n\n const repoData = (await repoResponse.json()) as { default_branch: string };\n resolvedRef = repoData.default_branch;\n }\n\n // Get the commit SHA for the ref\n const commitUrl = `https://api.github.com/repos/${owner}/${repo}/commits/${resolvedRef}`;\n const commitResponse = await fetch(commitUrl, { headers });\n\n if (commitResponse.status === 404) {\n throw new GitHubNotFoundError({ owner, repo, ref });\n }\n\n if (commitResponse.status === 403) {\n const remaining = commitResponse.headers.get(\"x-ratelimit-remaining\");\n if (remaining === \"0\") {\n throw new GitHubRateLimitError();\n }\n }\n\n if (!commitResponse.ok) {\n throw new Error(`GitHub API error: ${commitResponse.status}`);\n }\n\n const commitData = (await commitResponse.json()) as { sha: string };\n return commitData.sha;\n}\n\n/**\n * Download a GitHub repository tarball.\n *\n * @param spec - GitHub specifier with owner, repo, and optional ref\n * @returns Download result with buffer, commit SHA, and integrity hash\n */\nexport async function downloadGitHubPackage(\n spec: GitHubSpecifier,\n): Promise<GitHubDownloadResult> {\n const headers = getGitHubHeaders();\n\n // Resolve the ref to a commit SHA\n const commit = await resolveGitHubRef(spec.owner, spec.repo, spec.ref);\n\n // Download the tarball\n const tarballUrl = `https://api.github.com/repos/${spec.owner}/${spec.repo}/tarball/${commit}`;\n const response = await fetch(tarballUrl, {\n headers,\n redirect: \"follow\",\n });\n\n if (response.status === 404) {\n throw new GitHubNotFoundError(spec);\n }\n\n if (response.status === 403) {\n const remaining = response.headers.get(\"x-ratelimit-remaining\");\n if (remaining === \"0\") {\n throw new GitHubRateLimitError();\n }\n }\n\n if (!response.ok) {\n throw new Error(`Failed to download GitHub tarball: ${response.status}`);\n }\n\n const buffer = Buffer.from(await response.arrayBuffer());\n const integrity = calculateIntegrity(buffer);\n\n return { buffer, commit, integrity };\n}\n\n/**\n * Extract a GitHub package to the skills directory.\n *\n * For subpath specifiers, extracts only the specified subdirectory.\n * Full path structure is preserved under .pspm/skills/_github/.\n *\n * @param spec - GitHub specifier\n * @param buffer - Downloaded tarball buffer\n * @param skillsDir - Base skills directory (.pspm/skills)\n * @returns Path to extracted skill (relative to project root)\n */\nexport async function extractGitHubPackage(\n spec: GitHubSpecifier,\n buffer: Buffer,\n skillsDir: string,\n): Promise<string> {\n // Determine destination path\n const destPath = spec.path\n ? join(skillsDir, \"_github\", spec.owner, spec.repo, spec.path)\n : join(skillsDir, \"_github\", spec.owner, spec.repo);\n\n // Create a temp directory for extraction\n const tempDir = join(skillsDir, \"_github\", \".temp\", `${Date.now()}`);\n await mkdir(tempDir, { recursive: true });\n\n const tempFile = join(tempDir, \"archive.tgz\");\n\n try {\n // Write tarball to temp file\n await writeFile(tempFile, buffer);\n\n // Extract tarball\n const { exec } = await import(\"node:child_process\");\n const { promisify } = await import(\"node:util\");\n const execAsync = promisify(exec);\n\n await execAsync(`tar -xzf \"${tempFile}\" -C \"${tempDir}\"`);\n\n // Find the extracted directory (GitHub tarballs have a top-level dir like \"owner-repo-sha\")\n const entries = await readdir(tempDir);\n const extractedDir = entries.find(\n (e) => e !== \"archive.tgz\" && !e.startsWith(\".\"),\n );\n\n if (!extractedDir) {\n throw new Error(\"Failed to find extracted directory in tarball\");\n }\n\n const sourcePath = join(tempDir, extractedDir);\n\n // Determine what to copy - either a subpath or the entire repo\n const copySource = spec.path ? join(sourcePath, spec.path) : sourcePath;\n\n // If a subpath is specified, verify it exists in the repo\n if (spec.path) {\n const pathExists = await lstat(copySource).catch(() => null);\n if (!pathExists) {\n // List available directories in repo root for helpful error message\n const rootEntries = await readdir(sourcePath);\n const dirs = [];\n for (const entry of rootEntries) {\n const stat = await lstat(join(sourcePath, entry)).catch(() => null);\n if (stat?.isDirectory() && !entry.startsWith(\".\")) {\n dirs.push(entry);\n }\n }\n throw new GitHubPathNotFoundError(spec, dirs);\n }\n }\n\n // Remove existing destination and create fresh\n await rm(destPath, { recursive: true, force: true });\n await mkdir(destPath, { recursive: true });\n\n // Copy the contents\n await cp(copySource, destPath, { recursive: true });\n\n // Return the relative path from project root\n return spec.path\n ? `.pspm/skills/_github/${spec.owner}/${spec.repo}/${spec.path}`\n : `.pspm/skills/_github/${spec.owner}/${spec.repo}`;\n } finally {\n // Clean up temp directory\n await rm(tempDir, { recursive: true, force: true });\n }\n}\n\n/**\n * Get a short display name for a GitHub package.\n *\n * @param spec - GitHub specifier\n * @param commit - Resolved commit SHA (first 7 chars will be shown)\n * @returns Display string like \"github:owner/repo/path (ref@abc1234)\"\n */\nexport function getGitHubDisplayName(\n spec: GitHubSpecifier,\n commit?: string,\n): string {\n let name = `github:${spec.owner}/${spec.repo}`;\n if (spec.path) {\n name += `/${spec.path}`;\n }\n\n if (spec.ref || commit) {\n const ref = spec.ref || \"HEAD\";\n const shortCommit = commit ? commit.slice(0, 7) : \"\";\n name += ` (${ref}${shortCommit ? `@${shortCommit}` : \"\"})`;\n }\n\n return name;\n}\n","import { mkdir, readFile, stat, writeFile } from \"node:fs/promises\";\nimport { dirname } from \"node:path\";\nimport {\n getLegacyLockfilePath,\n getLockfilePath,\n getRegistryUrl,\n} from \"./config\";\nimport {\n type GitHubLockfileEntry,\n type LocalLockfileEntry,\n PSPM_LOCKFILE_SCHEMA_URL,\n type PspmLockfile,\n type PspmLockfileEntry,\n type WellKnownLockfileEntry,\n} from \"./lib/index\";\n\n// Re-export types for backward compatibility\nexport type {\n GitHubLockfileEntry,\n LocalLockfileEntry,\n PspmLockfile,\n PspmLockfileEntry,\n WellKnownLockfileEntry,\n};\n\n/**\n * Check if legacy lockfile exists (skill-lock.json)\n */\nasync function hasLegacyLockfile(): Promise<boolean> {\n try {\n await stat(getLegacyLockfilePath());\n return true;\n } catch {\n return false;\n }\n}\n\n/**\n * Migrate legacy lockfile (skill-lock.json) to new format (pspm-lock.json)\n * Returns true if migration was performed\n */\nexport async function migrateLockfileIfNeeded(): Promise<boolean> {\n const legacyPath = getLegacyLockfilePath();\n const newPath = getLockfilePath();\n\n // Check if legacy exists and new doesn't\n try {\n await stat(legacyPath);\n } catch {\n // No legacy file, nothing to migrate\n return false;\n }\n\n try {\n await stat(newPath);\n // New file already exists, don't overwrite\n return false;\n } catch {\n // New file doesn't exist, migrate\n }\n\n try {\n const content = await readFile(legacyPath, \"utf-8\");\n const oldLockfile = JSON.parse(content) as PspmLockfile;\n\n // Convert v1 to v2 format\n const newLockfile: PspmLockfile = {\n lockfileVersion: 2,\n registryUrl: oldLockfile.registryUrl,\n packages: oldLockfile.skills ?? {},\n };\n\n await writeFile(newPath, `${JSON.stringify(newLockfile, null, 2)}\\n`);\n console.log(\"Migrated lockfile: skill-lock.json → pspm-lock.json\");\n\n // Keep the old file for safety (user can delete it)\n return true;\n } catch {\n return false;\n }\n}\n\n/**\n * Read the lockfile, automatically checking for legacy format\n */\nexport async function readLockfile(): Promise<PspmLockfile | null> {\n const lockfilePath = getLockfilePath();\n\n try {\n const content = await readFile(lockfilePath, \"utf-8\");\n const lockfile = JSON.parse(content) as PspmLockfile;\n\n // Normalize v1 -> v2 in memory (skills -> packages)\n if (\n lockfile.lockfileVersion === 1 &&\n lockfile.skills &&\n !lockfile.packages\n ) {\n return {\n ...lockfile,\n lockfileVersion: 2,\n packages: lockfile.skills,\n };\n }\n\n return lockfile;\n } catch {\n // Try legacy path\n if (await hasLegacyLockfile()) {\n try {\n const content = await readFile(getLegacyLockfilePath(), \"utf-8\");\n const legacyLockfile = JSON.parse(content) as PspmLockfile;\n // Return normalized v2 format\n return {\n lockfileVersion: 2,\n registryUrl: legacyLockfile.registryUrl,\n packages: legacyLockfile.skills ?? {},\n };\n } catch {\n return null;\n }\n }\n return null;\n }\n}\n\n/**\n * Write the lockfile (v4 format if any package has dependencies, otherwise v3)\n */\nexport async function writeLockfile(lockfile: PspmLockfile): Promise<void> {\n const lockfilePath = getLockfilePath();\n await mkdir(dirname(lockfilePath), { recursive: true });\n\n const packages = lockfile.packages ?? lockfile.skills ?? {};\n\n // Check if any package has dependencies to determine version\n const hasDependencies = Object.values(packages).some(\n (pkg) => pkg.dependencies && Object.keys(pkg.dependencies).length > 0,\n );\n const version = hasDependencies ? 4 : 3;\n\n const normalized: PspmLockfile = {\n $schema: PSPM_LOCKFILE_SCHEMA_URL,\n lockfileVersion: version,\n registryUrl: lockfile.registryUrl,\n packages,\n };\n\n // Only include githubPackages if there are entries\n if (\n lockfile.githubPackages &&\n Object.keys(lockfile.githubPackages).length > 0\n ) {\n normalized.githubPackages = lockfile.githubPackages;\n }\n\n // Only include localPackages if there are entries\n if (\n lockfile.localPackages &&\n Object.keys(lockfile.localPackages).length > 0\n ) {\n normalized.localPackages = lockfile.localPackages;\n }\n\n // Only include wellKnownPackages if there are entries\n if (\n lockfile.wellKnownPackages &&\n Object.keys(lockfile.wellKnownPackages).length > 0\n ) {\n normalized.wellKnownPackages = lockfile.wellKnownPackages;\n }\n\n await writeFile(lockfilePath, `${JSON.stringify(normalized, null, 2)}\\n`);\n}\n\n/**\n * Create a new empty lockfile (v4 format)\n */\nexport async function createEmptyLockfile(): Promise<PspmLockfile> {\n const registryUrl = await getRegistryUrl();\n return {\n lockfileVersion: 4,\n registryUrl,\n packages: {},\n };\n}\n\n/**\n * Get packages from lockfile (handles both v1 and v2)\n */\nfunction getPackages(\n lockfile: PspmLockfile,\n): Record<string, PspmLockfileEntry> {\n return lockfile.packages ?? lockfile.skills ?? {};\n}\n\n/**\n * Add a skill to the lockfile\n */\nexport async function addToLockfile(\n fullName: string,\n entry: PspmLockfileEntry,\n): Promise<void> {\n let lockfile = await readLockfile();\n if (!lockfile) {\n lockfile = await createEmptyLockfile();\n }\n\n const packages = getPackages(lockfile);\n packages[fullName] = entry;\n lockfile.packages = packages;\n\n await writeLockfile(lockfile);\n}\n\n/**\n * Add a skill to the lockfile with dependencies (v4 format)\n */\nexport async function addToLockfileWithDeps(\n fullName: string,\n entry: PspmLockfileEntry,\n dependencies?: Record<string, string>,\n): Promise<void> {\n let lockfile = await readLockfile();\n if (!lockfile) {\n lockfile = await createEmptyLockfile();\n }\n\n const packages = getPackages(lockfile);\n const entryWithDeps = { ...entry };\n if (dependencies && Object.keys(dependencies).length > 0) {\n entryWithDeps.dependencies = dependencies;\n }\n packages[fullName] = entryWithDeps;\n lockfile.packages = packages;\n\n await writeLockfile(lockfile);\n}\n\n/**\n * Remove a skill from the lockfile\n */\nexport async function removeFromLockfile(fullName: string): Promise<boolean> {\n const lockfile = await readLockfile();\n if (!lockfile) {\n return false;\n }\n\n const packages = getPackages(lockfile);\n if (!packages[fullName]) {\n return false;\n }\n\n delete packages[fullName];\n lockfile.packages = packages;\n await writeLockfile(lockfile);\n return true;\n}\n\n/**\n * List all skills in the lockfile\n */\nexport async function listLockfileSkills(): Promise<\n Array<{ name: string; entry: PspmLockfileEntry }>\n> {\n const lockfile = await readLockfile();\n if (!lockfile) {\n return [];\n }\n\n const packages = getPackages(lockfile);\n return Object.entries(packages).map(([name, entry]) => ({\n name,\n entry: entry as PspmLockfileEntry,\n }));\n}\n\n// =============================================================================\n// GitHub Package Support\n// =============================================================================\n\n/**\n * Add a GitHub package to the lockfile\n */\nexport async function addGitHubToLockfile(\n specifier: string,\n entry: GitHubLockfileEntry,\n): Promise<void> {\n let lockfile = await readLockfile();\n if (!lockfile) {\n lockfile = await createEmptyLockfile();\n }\n\n if (!lockfile.githubPackages) {\n lockfile.githubPackages = {};\n }\n\n lockfile.githubPackages[specifier] = entry;\n await writeLockfile(lockfile);\n}\n\n/**\n * Remove a GitHub package from the lockfile\n */\nexport async function removeGitHubFromLockfile(\n specifier: string,\n): Promise<boolean> {\n const lockfile = await readLockfile();\n if (!lockfile?.githubPackages?.[specifier]) {\n return false;\n }\n\n delete lockfile.githubPackages[specifier];\n await writeLockfile(lockfile);\n return true;\n}\n\n/**\n * List all GitHub packages in the lockfile\n */\nexport async function listLockfileGitHubPackages(): Promise<\n Array<{ specifier: string; entry: GitHubLockfileEntry }>\n> {\n const lockfile = await readLockfile();\n if (!lockfile?.githubPackages) {\n return [];\n }\n\n return Object.entries(lockfile.githubPackages).map(([specifier, entry]) => ({\n specifier,\n entry: entry as GitHubLockfileEntry,\n }));\n}\n\n// =============================================================================\n// Local Package Support\n// =============================================================================\n\n/**\n * Add a local package to the lockfile\n */\nexport async function addLocalToLockfile(\n specifier: string,\n entry: LocalLockfileEntry,\n): Promise<void> {\n let lockfile = await readLockfile();\n if (!lockfile) {\n lockfile = await createEmptyLockfile();\n }\n\n if (!lockfile.localPackages) {\n lockfile.localPackages = {};\n }\n\n lockfile.localPackages[specifier] = entry;\n await writeLockfile(lockfile);\n}\n\n// =============================================================================\n// Well-Known Package Support\n// =============================================================================\n\n/**\n * Add a well-known package to the lockfile\n */\nexport async function addWellKnownToLockfile(\n specifier: string,\n entry: WellKnownLockfileEntry,\n): Promise<void> {\n let lockfile = await readLockfile();\n if (!lockfile) {\n lockfile = await createEmptyLockfile();\n }\n\n if (!lockfile.wellKnownPackages) {\n lockfile.wellKnownPackages = {};\n }\n\n lockfile.wellKnownPackages[specifier] = entry;\n await writeLockfile(lockfile);\n}\n\n/**\n * List all well-known packages in the lockfile\n */\nexport async function listLockfileWellKnownPackages(): Promise<\n Array<{ specifier: string; entry: WellKnownLockfileEntry }>\n> {\n const lockfile = await readLockfile();\n if (!lockfile?.wellKnownPackages) {\n return [];\n }\n\n return Object.entries(lockfile.wellKnownPackages).map(\n ([specifier, entry]) => ({\n specifier,\n entry: entry as WellKnownLockfileEntry,\n }),\n );\n}\n","/**\n * Symlink management for agent skill directories.\n *\n * Creates relative symlinks from agent-specific directories (e.g., .claude/skills/)\n * to the central .pspm/skills/ directory for portability.\n */\n\nimport { lstat, mkdir, readlink, rm, symlink } from \"node:fs/promises\";\nimport { dirname, join, relative } from \"node:path\";\nimport { resolveAgentConfig } from \"./agents\";\nimport type { AgentConfig } from \"./lib/index\";\n\n/**\n * Options for creating agent symlinks.\n */\nexport interface CreateSymlinksOptions {\n /** Agent names to create symlinks for */\n agents: string[];\n /** Project root directory (or home directory for global installs) */\n projectRoot: string;\n /** Custom agent configuration overrides from pspm.json */\n agentConfigs?: Record<string, AgentConfig>;\n /** If true, use global agent paths (relative to home directory) */\n global?: boolean;\n}\n\n/**\n * Information about an installed skill for symlinking.\n */\nexport interface SkillInfo {\n /** Skill name (used as symlink name) */\n name: string;\n /** Path to skill within .pspm/skills/ (relative to project root) */\n sourcePath: string;\n}\n\n/**\n * Create symlinks for all skills to specified agent directories.\n *\n * @param skills - List of skills to create symlinks for\n * @param options - Symlink creation options\n */\nexport async function createAgentSymlinks(\n skills: SkillInfo[],\n options: CreateSymlinksOptions,\n): Promise<void> {\n const { agents, projectRoot, agentConfigs } = options;\n\n // Skip if \"none\" agent\n if (agents.length === 1 && agents[0] === \"none\") {\n return;\n }\n\n for (const agentName of agents) {\n const config = resolveAgentConfig(agentName, agentConfigs, options.global);\n\n if (!config) {\n console.warn(`Warning: Unknown agent \"${agentName}\", skipping symlinks`);\n continue;\n }\n\n const agentSkillsDir = join(projectRoot, config.skillsDir);\n\n // Create agent skills directory\n await mkdir(agentSkillsDir, { recursive: true });\n\n for (const skill of skills) {\n const symlinkPath = join(agentSkillsDir, skill.name);\n const targetPath = join(projectRoot, skill.sourcePath);\n\n // Calculate relative path from symlink location to target\n const relativeTarget = relative(dirname(symlinkPath), targetPath);\n\n await createSymlink(symlinkPath, relativeTarget, skill.name);\n }\n }\n}\n\n/**\n * Create a single symlink, handling existing files/symlinks.\n *\n * @param symlinkPath - Absolute path where symlink will be created\n * @param target - Relative path to target (relative to symlink's parent dir)\n * @param skillName - Name for logging\n */\nasync function createSymlink(\n symlinkPath: string,\n target: string,\n skillName: string,\n): Promise<void> {\n try {\n // Check if something exists at the symlink path\n const stats = await lstat(symlinkPath).catch(() => null);\n\n if (stats) {\n if (stats.isSymbolicLink()) {\n // Check if it points to the correct target\n const existingTarget = await readlink(symlinkPath);\n if (existingTarget === target) {\n // Already correct, nothing to do\n return;\n }\n // Remove incorrect symlink\n await rm(symlinkPath);\n } else {\n // Regular file or directory exists - warn and skip\n console.warn(\n `Warning: File exists at symlink path for \"${skillName}\", skipping: ${symlinkPath}`,\n );\n return;\n }\n }\n\n // Create the symlink\n await symlink(target, symlinkPath);\n } catch (error) {\n const message = error instanceof Error ? error.message : String(error);\n console.warn(\n `Warning: Failed to create symlink for \"${skillName}\": ${message}`,\n );\n }\n}\n\n/**\n * Remove symlinks for a skill from all agent directories.\n *\n * @param skillName - Name of the skill (symlink name)\n * @param options - Symlink options\n */\nexport async function removeAgentSymlinks(\n skillName: string,\n options: CreateSymlinksOptions,\n): Promise<void> {\n const { agents, projectRoot, agentConfigs } = options;\n\n // Skip if \"none\" agent\n if (agents.length === 1 && agents[0] === \"none\") {\n return;\n }\n\n for (const agentName of agents) {\n const config = resolveAgentConfig(agentName, agentConfigs);\n\n if (!config) {\n continue;\n }\n\n const symlinkPath = join(projectRoot, config.skillsDir, skillName);\n\n try {\n const stats = await lstat(symlinkPath).catch(() => null);\n\n if (stats?.isSymbolicLink()) {\n await rm(symlinkPath);\n }\n } catch {\n // Ignore errors - symlink may not exist\n }\n }\n}\n\n/**\n * Get the source path for a registry skill within .pspm/skills/.\n *\n * @param ownerOrNamespace - Skill author username, or namespace prefix\n * @param skillNameOrOwner - Skill name (2-arg form) or owner (3-arg form)\n * @param skillName - Skill name (3-arg form only)\n * @returns Relative path from project root (e.g., \".pspm/skills/alice/my-skill\")\n */\nexport function getRegistrySkillPath(\n ownerOrNamespace: string,\n skillNameOrOwner: string,\n skillName?: string,\n): string {\n if (skillName !== undefined) {\n // 3-arg form: namespace, owner, skillName\n // e.g., (\"user\", \"alice\", \"my-skill\") -> \".pspm/skills/alice/my-skill\"\n // e.g., (\"org\", \"anyt\", \"code-review\") -> \".pspm/skills/_org/anyt/code-review\"\n // e.g., (\"github\", \"microsoft\", \"skills/azure-ai\") -> \".pspm/skills/_github-registry/microsoft/skills/azure-ai\"\n const namespace = ownerOrNamespace;\n const owner = skillNameOrOwner;\n if (namespace === \"org\") {\n return `.pspm/skills/_org/${owner}/${skillName}`;\n }\n if (namespace === \"github\") {\n // skillName contains \"repo/subname\" for @github namespace\n return `.pspm/skills/_github-registry/${owner}/${skillName}`;\n }\n // \"user\" namespace uses flat structure for backward compat\n return `.pspm/skills/${owner}/${skillName}`;\n }\n // 2-arg legacy form: username, skillName\n return `.pspm/skills/${ownerOrNamespace}/${skillNameOrOwner}`;\n}\n\n/**\n * Get the source path for a GitHub skill within .pspm/skills/.\n *\n * @param owner - GitHub repository owner\n * @param repo - GitHub repository name\n * @param path - Optional path within the repository\n * @returns Relative path from project root (e.g., \".pspm/skills/_github/owner/repo/path\")\n */\nexport function getGitHubSkillPath(\n owner: string,\n repo: string,\n path?: string,\n): string {\n if (path) {\n return `.pspm/skills/_github/${owner}/${repo}/${path}`;\n }\n return `.pspm/skills/_github/${owner}/${repo}`;\n}\n\n/**\n * Get the source path for a local skill within .pspm/skills/.\n *\n * @param skillName - Skill name\n * @returns Relative path from project root (e.g., \".pspm/skills/_local/my-skill\")\n */\nexport function getLocalSkillPath(skillName: string): string {\n return `.pspm/skills/_local/${skillName}`;\n}\n\n/**\n * Get the source path for a well-known skill within .pspm/skills/.\n *\n * @param hostname - Source hostname (e.g., \"acme.com\")\n * @param skillName - Skill name\n * @returns Relative path from project root (e.g., \".pspm/skills/_wellknown/acme.com/my-skill\")\n */\nexport function getWellKnownSkillPath(\n hostname: string,\n skillName: string,\n): string {\n return `.pspm/skills/_wellknown/${hostname}/${skillName}`;\n}\n\n/**\n * Check which agents have symlinks for a given skill.\n *\n * @param skillName - Name of the skill (symlink name)\n * @param agents - Agent names to check\n * @param projectRoot - Project root directory\n * @param agentConfigs - Custom agent configurations\n * @returns Array of agent names that have valid symlinks\n */\nexport async function getLinkedAgents(\n skillName: string,\n agents: string[],\n projectRoot: string,\n agentConfigs?: Record<string, AgentConfig>,\n): Promise<string[]> {\n const linkedAgents: string[] = [];\n\n for (const agentName of agents) {\n const config = resolveAgentConfig(agentName, agentConfigs);\n if (!config) continue;\n\n const symlinkPath = join(projectRoot, config.skillsDir, skillName);\n\n try {\n const stats = await lstat(symlinkPath);\n if (stats.isSymbolicLink()) {\n linkedAgents.push(agentName);\n }\n } catch {\n // Symlink doesn't exist\n }\n }\n\n return linkedAgents;\n}\n"],"mappings":"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ACH,aAAa;EACb,WAAW;EACX,iBAAiB;EAClB;CACD,aAAa;EACX,aAAa;EACb,WAAW;EACX,iBAAiB;EAClB;CACD,SAAS;EACP,aAAa;EACb,WAAW;EACX,iBAAiB;EAClB;CACD,eAAe;EACb,aAAa;EACb,WAAW;EACX,iBAAiB;EAClB;CACD,OAAO;EACL,aAAa;EACb,WAAW;EACX,iBAAiB;EAClB;CACD,WAAW;EACT,aAAa;EACb,WAAW;EACX,iBAAiB;EAClB;CACD,OAAO;EACL,aAAa;EACb,WAAW;EACX,iBAAiB;EAClB;CACD,gBAAgB;EACd,aAAa;EACb,WAAW;EACX,iBAAiB;EAClB;CACD,UAAU;EACR,aAAa;EACb,WAAW;EACX,iBAAiB;EAClB;CACD,QAAQ;EACN,aAAa;EACb,WAAW;EACX,iBAAiB;EAClB;CACD,OAAO;EACL,aAAa;EACb,WAAW;EACX,iBAAiB;EAClB;CACD,QAAQ;EACN,aAAa;EACb,WAAW;EACX,iBAAiB;EAClB;CACD,OAAO;EACL,aAAa;EACb,WAAW;EACX,iBAAiB;EAClB;CACD,cAAc;EACZ,aAAa;EACb,WAAW;EACX,iBAAiB;EAClB;CACD,kBAAkB;EAChB,aAAa;EACb,WAAW;EACX,iBAAiB;EAClB;CACD,OAAO;EACL,aAAa;EACb,WAAW;EACX,iBAAiB;EAClB;CACD,aAAa;EACX,aAAa;EACb,WAAW;EACX,iBAAiB;EAClB;CACD,OAAO;EACL,aAAa;EACb,WAAW;EACX,iBAAiB;EAClB;CACD,MAAM;EACJ,aAAa;EACb,WAAW;EACX,iBAAiB;EAClB;CACD,YAAY;EACV,aAAa;EACb,WAAW;EACX,iBAAiB;EAClB;CACD,YAAY;EACV,aAAa;EACb,WAAW;EACX,iBAAiB;EAClB;CACD,MAAM;EACJ,aAAa;EACb,WAAW;EACX,iBAAiB;EAClB;CACD,QAAQ;EACN,aAAa;EACb,WAAW;EACX,iBAAiB;EAClB;CACD,gBAAgB;EACd,aAAa;EACb,WAAW;EACX,iBAAiB;EAClB;CACD,KAAK;EACH,aAAa;EACb,WAAW;EACX,iBAAiB;EAClB;CACD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"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@anytio/pspm",
3
- "version": "0.12.0",
3
+ "version": "0.14.0",
4
4
  "description": "CLI for PSPM - Package manager for AI agent skills",
5
5
  "author": "anyt.io",
6
6
  "license": "Artistic-2.0",
@@ -37,7 +37,7 @@
37
37
  "LICENSE"
38
38
  ],
39
39
  "dependencies": {
40
- "@inquirer/prompts": "^8.3.2",
40
+ "@inquirer/prompts": "^8.4.2",
41
41
  "commander": "^14.0.3",
42
42
  "ignore": "^7.0.5",
43
43
  "ini": "^6.0.0",
@@ -45,26 +45,28 @@
45
45
  "semver": "^7.7.4"
46
46
  },
47
47
  "devDependencies": {
48
- "@biomejs/biome": "2.4.7",
48
+ "@biomejs/biome": "2.4.14",
49
49
  "@types/ini": "^4.1.1",
50
- "@types/node": "^25.5.0",
50
+ "@types/node": "^25.6.0",
51
51
  "@types/semver": "^7.7.1",
52
- "tsup": "^8.5.1",
52
+ "tsdown": "^0.21.10",
53
53
  "tsx": "^4.21.0",
54
- "typescript": "^5.9.3",
55
- "vitest": "^4.1.0",
56
- "@repo/skill-registry": "0.0.1",
57
- "@repo/types": "0.0.1"
54
+ "typescript": "^6.0.3",
55
+ "vitest": "^4.1.5",
56
+ "@anytio/skill-types": "0.0.1",
57
+ "@anytio/skill-registry": "0.0.1",
58
+ "@anytio/errors": "0.0.0",
59
+ "@anytio/typescript-config": "0.0.0"
58
60
  },
59
61
  "engines": {
60
62
  "node": ">=20.0.0"
61
63
  },
62
64
  "scripts": {
63
65
  "dev": "tsx --tsconfig tsconfig.json src/index.ts",
64
- "build": "tsup",
66
+ "build": "tsdown",
65
67
  "start": "node dist/index.js",
66
- "link": "pnpm build && pnpm link --global",
67
- "unlink": "pnpm unlink --global",
68
+ "link:local": "pnpm build && ln -sf $(pwd)/bin/pspm.js $(pnpm bin --global)/pspm-local && echo 'Linked pspm-local → local build'",
69
+ "unlink:local": "rm -f $(pnpm bin --global)/pspm-local && echo 'Removed pspm-local'",
68
70
  "typecheck": "tsc --noEmit",
69
71
  "lint": "biome lint src",
70
72
  "check": "biome check src",