@cleocode/caamp 1.9.0 → 2026.3.74
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.
- package/README.md +2 -2
- package/dist/{chunk-ER3FIOTM.js → chunk-J7UN457C.js} +2 -2
- package/dist/chunk-J7UN457C.js.map +1 -0
- package/dist/{chunk-MFWBR2NY.js → chunk-O7IVK5JY.js} +2 -2
- package/dist/chunk-O7IVK5JY.js.map +1 -0
- package/dist/{chunk-TRIXT4T7.js → chunk-TI6WOJDG.js} +1 -1
- package/dist/chunk-TI6WOJDG.js.map +1 -0
- package/dist/{chunk-OLJZ23W3.js → chunk-ZF4W3K5H.js} +36 -3
- package/dist/chunk-ZF4W3K5H.js.map +1 -0
- package/dist/cli.d.ts +0 -0
- package/dist/cli.js +18 -12
- package/dist/cli.js.map +1 -1
- package/dist/{hooks-LV6VU7QJ.js → hooks-E2XQ7TQG.js} +3 -3
- package/dist/{hooks-LV6VU7QJ.js.map → hooks-E2XQ7TQG.js.map} +0 -0
- package/dist/index.d.ts +3492 -198
- package/dist/index.js +5 -5
- package/dist/index.js.map +1 -1
- package/dist/{injector-P2OL6RK3.js → injector-NSDP5Z2P.js} +3 -3
- package/dist/{injector-P2OL6RK3.js.map → injector-NSDP5Z2P.js.map} +0 -0
- package/package.json +2 -2
- package/providers/hook-mappings.json +0 -0
- package/providers/registry.json +0 -0
- package/dist/chunk-ER3FIOTM.js.map +0 -1
- package/dist/chunk-MFWBR2NY.js.map +0 -1
- package/dist/chunk-OLJZ23W3.js.map +0 -1
- package/dist/chunk-TRIXT4T7.js.map +0 -1
package/README.md
CHANGED
|
@@ -130,7 +130,7 @@ Recommendation criteria flags:
|
|
|
130
130
|
--select <index> # select from ranked CHOOSE list
|
|
131
131
|
```
|
|
132
132
|
|
|
133
|
-
LAFS canonical spec: https://github.com/kryptobaseddev/lafs
|
|
133
|
+
LAFS canonical spec: https://github.com/kryptobaseddev/lafs/blob/main/lafs.md
|
|
134
134
|
|
|
135
135
|
### MCP Servers
|
|
136
136
|
|
|
@@ -251,7 +251,7 @@ Each provider uses a different key name for MCP server configuration:
|
|
|
251
251
|
| [Security Policy](https://codluv.gitbook.io/caamp/contributing/security) | Private vulnerability disclosure process |
|
|
252
252
|
| [LAFS Compliance Profile](https://codluv.gitbook.io/caamp/api-and-reference/lafs-compliance) | CAAMP-specific LAFS adoption scope and compliance mapping |
|
|
253
253
|
| [Agents Directory Standard](https://codluv.gitbook.io/caamp/api-and-reference/agents-directory-standard) | `.agents/` standard directory structure |
|
|
254
|
-
| [LAFS Specification](https://github.com/kryptobaseddev/lafs
|
|
254
|
+
| [LAFS Specification](https://github.com/kryptobaseddev/lafs/blob/main/lafs.md) | Canonical cross-language LLM-agent-first protocol |
|
|
255
255
|
| [Technical Specification](claudedocs/specs/CAAMP-SPEC.md) | RFC 2119 spec covering all subsystems |
|
|
256
256
|
| [Vision & Architecture](claudedocs/VISION.md) | Project vision, design philosophy, and architecture |
|
|
257
257
|
| [Gap Analysis & Roadmap](claudedocs/GAP-ANALYSIS.md) | Current state vs plan, v0.2.0+ roadmap |
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import {
|
|
2
2
|
resolveRegistryTemplatePath
|
|
3
|
-
} from "./chunk-
|
|
3
|
+
} from "./chunk-TI6WOJDG.js";
|
|
4
4
|
|
|
5
5
|
// src/core/hooks/types.ts
|
|
6
6
|
var HOOK_CATEGORIES = ["session", "prompt", "tool", "agent", "context"];
|
|
@@ -241,4 +241,4 @@ export {
|
|
|
241
241
|
resolveNativeEvent,
|
|
242
242
|
getHookMappingsVersion
|
|
243
243
|
};
|
|
244
|
-
//# sourceMappingURL=chunk-
|
|
244
|
+
//# sourceMappingURL=chunk-J7UN457C.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/core/hooks/types.ts","../src/core/hooks/normalizer.ts"],"sourcesContent":["/**\n * CAAMP Hooks Normalizer - Type Definitions\n *\n * Defines the canonical CAAMP hook event taxonomy and provider mapping types.\n * CAAMP provides a unified hook interface across all providers — consumers\n * use canonical event names, and the normalizer translates to/from\n * provider-native names.\n */\n\n// ── Canonical Hook Events ───────────────────────────────────────────\n\n/**\n * All supported hook category names as a readonly tuple.\n *\n * @remarks\n * Categories group related canonical events for filtering and display.\n * Each canonical event belongs to exactly one category. The tuple is\n * `as const` so it can be used to derive the {@link HookCategory} union type.\n *\n * @public\n */\nexport const HOOK_CATEGORIES = [\"session\", \"prompt\", \"tool\", \"agent\", \"context\"] as const;\n\n/**\n * Union type of valid hook category strings derived from {@link HOOK_CATEGORIES}.\n *\n * @remarks\n * Used to classify canonical events into logical groups such as `\"session\"`,\n * `\"prompt\"`, `\"tool\"`, `\"agent\"`, and `\"context\"`.\n *\n * @public\n */\nexport type HookCategory = (typeof HOOK_CATEGORIES)[number];\n\n/**\n * All CAAMP canonical hook event names as a readonly tuple.\n *\n * @remarks\n * This is the single source of truth for the canonical event taxonomy.\n * Provider-native events are mapped to and from these canonical names\n * by the normalizer. The tuple is `as const` so it can derive the\n * {@link CanonicalHookEvent} union type.\n *\n * @public\n */\nexport const CANONICAL_HOOK_EVENTS = [\n \"SessionStart\",\n \"SessionEnd\",\n \"PromptSubmit\",\n \"ResponseComplete\",\n \"PreToolUse\",\n \"PostToolUse\",\n \"PostToolUseFailure\",\n \"PermissionRequest\",\n \"SubagentStart\",\n \"SubagentStop\",\n \"PreModel\",\n \"PostModel\",\n \"PreCompact\",\n \"PostCompact\",\n \"Notification\",\n \"ConfigChange\",\n] as const;\n\n/**\n * Union type of valid canonical hook event names derived from {@link CANONICAL_HOOK_EVENTS}.\n *\n * @remarks\n * Every provider mapping references these canonical names. Use `toNative()`\n * to translate a canonical event to a provider-specific name, and `toCanonical()`\n * for the reverse direction.\n *\n * @public\n */\nexport type CanonicalHookEvent = (typeof CANONICAL_HOOK_EVENTS)[number];\n\n/**\n * Definition of a canonical hook event including its category and behavior.\n *\n * @remarks\n * Each canonical event has a category for grouping, a human-readable description,\n * and a `canBlock` flag indicating whether a hook handler can prevent the\n * associated action from proceeding.\n *\n * @public\n */\nexport interface CanonicalEventDefinition {\n /** The lifecycle category this event belongs to (e.g. `\"session\"`, `\"tool\"`). */\n category: HookCategory;\n /** Human-readable description of when this event fires. */\n description: string;\n /** Whether a hook handler can block or cancel the associated action. */\n canBlock: boolean;\n}\n\n// ── Provider Hook System Types ──────────────────────────────────────\n\n/**\n * The type of hook system a provider uses.\n *\n * @remarks\n * - `\"config\"` — hooks defined in a configuration file (e.g. Claude Code `.claude/settings.json`)\n * - `\"plugin\"` — hooks implemented via a plugin/extension system\n * - `\"none\"` — provider does not support hooks\n *\n * @public\n */\nexport type HookSystemType = \"config\" | \"plugin\" | \"none\";\n\n/**\n * The mechanism a provider uses to execute hook handlers.\n *\n * @remarks\n * - `\"command\"` — shell command execution\n * - `\"http\"` — HTTP webhook callback\n * - `\"prompt\"` — LLM prompt injection\n * - `\"agent\"` — sub-agent delegation\n * - `\"plugin\"` — native plugin API call\n *\n * @public\n */\nexport type HookHandlerType = \"command\" | \"http\" | \"prompt\" | \"agent\" | \"plugin\";\n\n/**\n * Mapping of a single canonical event to a provider's native representation.\n *\n * @remarks\n * Each entry in a provider's hook profile maps one canonical event to the\n * provider's native event name. If `supported` is `false`, the provider\n * does not fire this event. Optional `notes` capture caveats or limitations.\n *\n * @public\n */\nexport interface HookMapping {\n /** The provider-native event name, or `null` if the event has no native equivalent. */\n nativeName: string | null;\n /** Whether this canonical event is supported by the provider. */\n supported: boolean;\n /**\n * Optional notes about support limitations or behavioral differences.\n *\n * @defaultValue `undefined`\n */\n notes?: string;\n}\n\n/**\n * Complete hook profile for a single provider.\n *\n * @remarks\n * Describes the provider's hook system type, configuration location, supported\n * handler types, and the full mapping of canonical events to native names.\n * This is the primary data structure loaded from `providers/hook-mappings.json`.\n *\n * @public\n */\nexport interface ProviderHookProfile {\n /** The type of hook system the provider uses (`\"config\"`, `\"plugin\"`, or `\"none\"`). */\n hookSystem: HookSystemType;\n /** Filesystem path template to the provider's hook configuration file, or `null`. */\n hookConfigPath: string | null;\n /** The configuration format used for hooks (e.g. `\"json\"`, `\"yaml\"`), or `null`. */\n hookFormat: string | null;\n /** The handler execution mechanisms this provider supports. */\n handlerTypes: HookHandlerType[];\n /** Whether the provider's hook system is considered experimental or unstable. */\n experimental: boolean;\n /** Mapping of every canonical event to this provider's native representation. */\n mappings: Record<CanonicalHookEvent, HookMapping>;\n /** Native event names that exist only in this provider with no canonical equivalent. */\n providerOnlyEvents: string[];\n}\n\n// ── Normalization Result Types ──────────────────────────────────────\n\n/**\n * A fully resolved hook event with both canonical and native names.\n *\n * @remarks\n * Returned by batch translation functions. Contains all the context needed\n * to register or invoke a hook handler: the canonical name for CAAMP logic,\n * the native name for provider-specific calls, and metadata about category\n * and blocking behavior.\n *\n * @public\n */\nexport interface NormalizedHookEvent {\n /** The CAAMP canonical event name. */\n canonical: CanonicalHookEvent;\n /** The provider-native event name. */\n native: string;\n /** The provider this event was resolved for. */\n providerId: string;\n /** The lifecycle category of this event. */\n category: HookCategory;\n /** Whether a handler for this event can block the associated action. */\n canBlock: boolean;\n}\n\n/**\n * Result of querying whether a provider supports a specific canonical event.\n *\n * @remarks\n * Returned by `getHookSupport()`. Includes the native name translation\n * and any notes about support limitations. When `supported` is `false`,\n * `native` will be `null`.\n *\n * @public\n */\nexport interface HookSupportResult {\n /** The canonical event that was queried. */\n canonical: CanonicalHookEvent;\n /** Whether the provider supports this event. */\n supported: boolean;\n /** The provider-native event name, or `null` if unsupported. */\n native: string | null;\n /**\n * Optional notes about support caveats.\n *\n * @defaultValue `undefined`\n */\n notes?: string;\n}\n\n/**\n * Aggregated hook support summary for a single provider.\n *\n * @remarks\n * Provides a high-level view of a provider's hook capabilities including\n * counts, coverage percentage, and lists of supported/unsupported events.\n * Useful for CLI display and provider comparison features.\n *\n * @public\n */\nexport interface ProviderHookSummary {\n /** The provider identifier. */\n providerId: string;\n /** The type of hook system the provider uses. */\n hookSystem: HookSystemType;\n /** Whether the provider's hook system is experimental. */\n experimental: boolean;\n /** Number of canonical events this provider supports. */\n supportedCount: number;\n /** Total number of canonical events in the taxonomy. */\n totalCanonical: number;\n /** List of canonical events this provider supports. */\n supported: CanonicalHookEvent[];\n /** List of canonical events this provider does not support. */\n unsupported: CanonicalHookEvent[];\n /** Native events unique to this provider with no canonical mapping. */\n providerOnly: string[];\n /** Percentage of canonical events supported (0-100). */\n coverage: number;\n}\n\n/**\n * Cross-provider hook support matrix comparing multiple providers.\n *\n * @remarks\n * Built by `buildHookMatrix()`. Provides a two-dimensional view of which\n * canonical events are supported by which providers, with native name\n * translations. Used to render comparison tables in the CLI.\n *\n * @public\n */\nexport interface CrossProviderMatrix {\n /** The canonical events included in this matrix (rows). */\n events: CanonicalHookEvent[];\n /** The provider IDs included in this matrix (columns). */\n providers: string[];\n /** Nested record mapping each canonical event to each provider's hook mapping. */\n matrix: Record<CanonicalHookEvent, Record<string, HookMapping>>;\n}\n\n// ── Hook Mappings Data File Types ───────────────────────────────────\n\n/**\n * Schema for the `providers/hook-mappings.json` data file.\n *\n * @remarks\n * This interface represents the top-level structure of the hook mappings\n * JSON file that serves as the single source of truth for all provider\n * hook configurations. It is loaded and cached by the normalizer module.\n *\n * @public\n */\nexport interface HookMappingsFile {\n /** Semver version string of the hook mappings schema. */\n version: string;\n /** ISO 8601 date string of the last update to mappings data. */\n lastUpdated: string;\n /** Human-readable description of the mappings file purpose. */\n description: string;\n /** Definitions for every canonical event in the taxonomy. */\n canonicalEvents: Record<CanonicalHookEvent, CanonicalEventDefinition>;\n /** Hook profiles keyed by provider ID. */\n providerMappings: Record<string, ProviderHookProfile>;\n}\n","/**\n * CAAMP Hooks Normalizer\n *\n * Translates between CAAMP canonical hook events and provider-native\n * event names. Provides query functions for hook support, cross-provider\n * comparison, and event normalization.\n *\n * This module follows the same pattern as `src/core/mcp/transforms.ts` —\n * a translation layer that lets consumers use one canonical interface\n * while CAAMP handles provider-specific differences.\n */\n\nimport { readFileSync } from \"node:fs\";\nimport { dirname, join } from \"node:path\";\nimport { fileURLToPath } from \"node:url\";\nimport { resolveRegistryTemplatePath } from \"../paths/standard.js\";\nimport type {\n CanonicalEventDefinition,\n CanonicalHookEvent,\n CrossProviderMatrix,\n HookCategory,\n HookMapping,\n HookMappingsFile,\n HookSupportResult,\n HookSystemType,\n NormalizedHookEvent,\n ProviderHookProfile,\n ProviderHookSummary,\n} from \"./types.js\";\nimport { CANONICAL_HOOK_EVENTS, HOOK_CATEGORIES } from \"./types.js\";\n\n// ── Data Loading ────────────────────────────────────────────────────\n\nlet _mappings: HookMappingsFile | null = null;\n\nfunction findMappingsPath(): string {\n const thisDir = dirname(fileURLToPath(import.meta.url));\n // src/core/hooks/ -> providers/hook-mappings.json\n return join(thisDir, \"..\", \"..\", \"..\", \"providers\", \"hook-mappings.json\");\n}\n\nfunction loadMappings(): HookMappingsFile {\n if (_mappings) return _mappings;\n const raw = readFileSync(findMappingsPath(), \"utf-8\");\n _mappings = JSON.parse(raw) as HookMappingsFile;\n return _mappings;\n}\n\n/**\n * Reset the cached hook mappings data.\n *\n * @remarks\n * Clears the in-memory cache so the next query function call will\n * re-read `providers/hook-mappings.json` from disk. This is primarily\n * intended for test isolation — production code should not need to call this.\n *\n * @example\n * ```typescript\n * import { resetHookMappings, getHookMappingsVersion } from \"./normalizer.js\";\n *\n * // Force a fresh load from disk\n * resetHookMappings();\n * const version = getHookMappingsVersion();\n * ```\n *\n * @public\n */\nexport function resetHookMappings(): void {\n _mappings = null;\n}\n\n// ── Core Query Functions ────────────────────────────────────────────\n\n/**\n * Get the canonical event definition (category, description, canBlock).\n *\n * @remarks\n * Looks up the definition from the hook mappings data file. The returned\n * object contains the event's category, human-readable description, and\n * whether handlers can block the associated action.\n *\n * @param event - The canonical event name to look up.\n * @returns The event definition containing category, description, and canBlock flag.\n *\n * @example\n * ```typescript\n * import { getCanonicalEvent } from \"./normalizer.js\";\n *\n * const def = getCanonicalEvent(\"PreToolUse\");\n * console.log(def.category); // \"tool\"\n * console.log(def.canBlock); // true\n * ```\n *\n * @public\n */\nexport function getCanonicalEvent(event: CanonicalHookEvent): CanonicalEventDefinition {\n const data = loadMappings();\n return data.canonicalEvents[event];\n}\n\n/**\n * Get all canonical event definitions.\n *\n * @remarks\n * Returns the complete map of canonical event names to their definitions.\n * Useful for iterating over the full taxonomy or building UI displays\n * of all available events.\n *\n * @returns A record mapping every canonical event name to its definition.\n *\n * @example\n * ```typescript\n * import { getAllCanonicalEvents } from \"./normalizer.js\";\n *\n * const events = getAllCanonicalEvents();\n * for (const [name, def] of Object.entries(events)) {\n * console.log(`${name}: ${def.description}`);\n * }\n * ```\n *\n * @public\n */\nexport function getAllCanonicalEvents(): Record<CanonicalHookEvent, CanonicalEventDefinition> {\n return loadMappings().canonicalEvents;\n}\n\n/**\n * Get canonical events filtered by category.\n *\n * @remarks\n * Filters the canonical event list to only those belonging to the specified\n * category. Useful for displaying events grouped by lifecycle phase\n * (e.g. all `\"tool\"` events or all `\"session\"` events).\n *\n * @param category - The hook category to filter by (e.g. `\"session\"`, `\"tool\"`).\n * @returns Array of canonical event names that belong to the specified category.\n *\n * @example\n * ```typescript\n * import { getCanonicalEventsByCategory } from \"./normalizer.js\";\n *\n * const toolEvents = getCanonicalEventsByCategory(\"tool\");\n * // [\"PreToolUse\", \"PostToolUse\", \"PostToolUseFailure\"]\n * ```\n *\n * @public\n */\nexport function getCanonicalEventsByCategory(category: HookCategory): CanonicalHookEvent[] {\n const data = loadMappings();\n return CANONICAL_HOOK_EVENTS.filter(\n (event) => data.canonicalEvents[event].category === category,\n );\n}\n\n/**\n * Get the full hook profile for a provider.\n *\n * @remarks\n * Returns the complete hook configuration for a provider including its\n * hook system type, config path, handler types, and all event mappings.\n * Returns `undefined` if the provider has no hook mappings defined.\n *\n * @param providerId - The provider identifier (e.g. `\"claude-code\"`, `\"gemini-cli\"`).\n * @returns The provider's hook profile, or `undefined` if not found.\n *\n * @example\n * ```typescript\n * import { getProviderHookProfile } from \"./normalizer.js\";\n *\n * const profile = getProviderHookProfile(\"claude-code\");\n * if (profile) {\n * console.log(profile.hookSystem); // \"config\"\n * console.log(profile.experimental); // false\n * }\n * ```\n *\n * @public\n */\nexport function getProviderHookProfile(providerId: string): ProviderHookProfile | undefined {\n const data = loadMappings();\n return data.providerMappings[providerId];\n}\n\n/**\n * Get all provider IDs that have hook mappings.\n *\n * @remarks\n * Returns the list of provider identifiers present in the hook mappings\n * data file. This reflects all providers for which CAAMP has hook\n * translation data, regardless of whether they support any events.\n *\n * @returns Array of provider ID strings.\n *\n * @example\n * ```typescript\n * import { getMappedProviderIds } from \"./normalizer.js\";\n *\n * const ids = getMappedProviderIds();\n * // [\"claude-code\", \"gemini-cli\", \"cursor\", \"kimi\", ...]\n * ```\n *\n * @public\n */\nexport function getMappedProviderIds(): string[] {\n return Object.keys(loadMappings().providerMappings);\n}\n\n// ── Normalization: Canonical → Native ───────────────────────────────\n\n/**\n * Translate a CAAMP canonical event name to the provider's native name.\n *\n * @remarks\n * This is the primary forward-translation function. Given a canonical\n * event and a provider ID, it returns the provider-specific event name\n * that should be used when configuring hooks for that provider.\n * Returns `null` if the provider is unknown or does not support the event.\n *\n * @param canonical - The CAAMP canonical event name to translate.\n * @param providerId - The target provider identifier.\n * @returns The native event name, or `null` if unsupported.\n *\n * @example\n * ```typescript\n * import { toNative } from \"./normalizer.js\";\n *\n * toNative(\"PreToolUse\", \"claude-code\"); // \"PreToolUse\"\n * toNative(\"PreToolUse\", \"gemini-cli\"); // \"BeforeTool\"\n * toNative(\"PreToolUse\", \"cursor\"); // \"preToolUse\"\n * toNative(\"PreToolUse\", \"kimi\"); // null\n * ```\n *\n * @public\n */\nexport function toNative(\n canonical: CanonicalHookEvent,\n providerId: string,\n): string | null {\n const profile = getProviderHookProfile(providerId);\n if (!profile) return null;\n const mapping = profile.mappings[canonical];\n return mapping?.supported ? mapping.nativeName : null;\n}\n\n/**\n * Translate a provider-native event name to the CAAMP canonical name.\n *\n * @remarks\n * This is the reverse-translation function. Scans all event mappings\n * for the given provider to find a canonical match for the native name.\n * Returns `null` if no mapping is found or the provider is unknown.\n *\n * @param nativeName - The provider-native event name to look up.\n * @param providerId - The provider identifier to search within.\n * @returns The canonical event name, or `null` if no mapping exists.\n *\n * @example\n * ```typescript\n * import { toCanonical } from \"./normalizer.js\";\n *\n * toCanonical(\"BeforeTool\", \"gemini-cli\"); // \"PreToolUse\"\n * toCanonical(\"stop\", \"cursor\"); // \"ResponseComplete\"\n * toCanonical(\"UserPromptSubmit\", \"claude-code\"); // \"PromptSubmit\"\n * ```\n *\n * @public\n */\nexport function toCanonical(\n nativeName: string,\n providerId: string,\n): CanonicalHookEvent | null {\n const profile = getProviderHookProfile(providerId);\n if (!profile) return null;\n\n for (const [canonical, mapping] of Object.entries(profile.mappings)) {\n if (mapping.supported && mapping.nativeName === nativeName) {\n return canonical as CanonicalHookEvent;\n }\n }\n return null;\n}\n\n/**\n * Batch-translate multiple canonical events to native names for a provider.\n *\n * @remarks\n * Translates an array of canonical events in a single call, returning\n * only the events that the provider actually supports. Each result\n * includes category and blocking metadata. Unsupported events are\n * silently excluded from the output.\n *\n * @param canonicals - Array of canonical event names to translate.\n * @param providerId - The target provider identifier.\n * @returns Array of normalized events (only supported ones included).\n *\n * @example\n * ```typescript\n * import { toNativeBatch } from \"./normalizer.js\";\n *\n * const events = toNativeBatch(\n * [\"PreToolUse\", \"PostToolUse\", \"ConfigChange\"],\n * \"claude-code\",\n * );\n * // Returns NormalizedHookEvent[] for supported events only\n * ```\n *\n * @public\n */\nexport function toNativeBatch(\n canonicals: CanonicalHookEvent[],\n providerId: string,\n): NormalizedHookEvent[] {\n const data = loadMappings();\n const profile = data.providerMappings[providerId];\n if (!profile) return [];\n\n const results: NormalizedHookEvent[] = [];\n for (const canonical of canonicals) {\n const mapping = profile.mappings[canonical];\n if (mapping?.supported && mapping.nativeName) {\n results.push({\n canonical,\n native: mapping.nativeName,\n providerId,\n category: data.canonicalEvents[canonical].category,\n canBlock: data.canonicalEvents[canonical].canBlock,\n });\n }\n }\n return results;\n}\n\n// ── Support Queries ─────────────────────────────────────────────────\n\n/**\n * Check if a provider supports a specific canonical hook event.\n *\n * @remarks\n * A quick boolean check that avoids returning the full mapping details.\n * Returns `false` if the provider is unknown or the event is not supported.\n *\n * @param canonical - The canonical event name to check.\n * @param providerId - The provider identifier to check against.\n * @returns `true` if the provider supports this canonical event, `false` otherwise.\n *\n * @example\n * ```typescript\n * import { supportsHook } from \"./normalizer.js\";\n *\n * supportsHook(\"PreToolUse\", \"claude-code\"); // true\n * supportsHook(\"PreToolUse\", \"kimi\"); // false\n * ```\n *\n * @public\n */\nexport function supportsHook(\n canonical: CanonicalHookEvent,\n providerId: string,\n): boolean {\n const profile = getProviderHookProfile(providerId);\n if (!profile) return false;\n return profile.mappings[canonical]?.supported ?? false;\n}\n\n/**\n * Get full hook support details for a canonical event on a provider.\n *\n * @remarks\n * Returns a structured result with the native name translation and any\n * notes about support limitations. Unlike `supportsHook()`, this always\n * returns a result object even when the provider is unknown (with\n * `supported: false`).\n *\n * @param canonical - The canonical event name to query.\n * @param providerId - The provider identifier to query against.\n * @returns Support result including native name and optional notes.\n *\n * @example\n * ```typescript\n * import { getHookSupport } from \"./normalizer.js\";\n *\n * const result = getHookSupport(\"PreToolUse\", \"claude-code\");\n * console.log(result.supported); // true\n * console.log(result.native); // \"PreToolUse\"\n * ```\n *\n * @public\n */\nexport function getHookSupport(\n canonical: CanonicalHookEvent,\n providerId: string,\n): HookSupportResult {\n const profile = getProviderHookProfile(providerId);\n if (!profile) {\n return { canonical, supported: false, native: null };\n }\n const mapping = profile.mappings[canonical];\n return {\n canonical,\n supported: mapping?.supported ?? false,\n native: mapping?.nativeName ?? null,\n notes: mapping?.notes,\n };\n}\n\n/**\n * Get all supported canonical events for a provider.\n *\n * @remarks\n * Filters the full canonical event list to only those that the provider\n * supports. Returns an empty array if the provider is unknown.\n *\n * @param providerId - The provider identifier to query.\n * @returns Array of canonical event names the provider supports.\n *\n * @example\n * ```typescript\n * import { getSupportedEvents } from \"./normalizer.js\";\n *\n * const events = getSupportedEvents(\"claude-code\");\n * // [\"SessionStart\", \"SessionEnd\", \"PreToolUse\", ...]\n * ```\n *\n * @public\n */\nexport function getSupportedEvents(providerId: string): CanonicalHookEvent[] {\n const profile = getProviderHookProfile(providerId);\n if (!profile) return [];\n return CANONICAL_HOOK_EVENTS.filter(\n (event) => profile.mappings[event]?.supported,\n );\n}\n\n/**\n * Get all unsupported canonical events for a provider.\n *\n * @remarks\n * Returns the complement of `getSupportedEvents()`. If the provider is\n * unknown, all canonical events are returned since none are supported.\n *\n * @param providerId - The provider identifier to query.\n * @returns Array of canonical event names the provider does not support.\n *\n * @example\n * ```typescript\n * import { getUnsupportedEvents } from \"./normalizer.js\";\n *\n * const missing = getUnsupportedEvents(\"kimi\");\n * // Returns all canonical events (kimi has no hook support)\n * ```\n *\n * @public\n */\nexport function getUnsupportedEvents(providerId: string): CanonicalHookEvent[] {\n const profile = getProviderHookProfile(providerId);\n if (!profile) return [...CANONICAL_HOOK_EVENTS];\n return CANONICAL_HOOK_EVENTS.filter(\n (event) => !profile.mappings[event]?.supported,\n );\n}\n\n/**\n * Get providers that support a specific canonical event.\n *\n * @remarks\n * Scans all provider mappings and returns the IDs of those that support\n * the given canonical event. Useful for determining which providers can\n * be targeted when configuring a specific hook.\n *\n * @param canonical - The canonical event name to search for.\n * @returns Array of provider IDs that support this event.\n *\n * @example\n * ```typescript\n * import { getProvidersForEvent } from \"./normalizer.js\";\n *\n * const providers = getProvidersForEvent(\"PreToolUse\");\n * // [\"claude-code\", \"gemini-cli\", \"cursor\"]\n * ```\n *\n * @public\n */\nexport function getProvidersForEvent(canonical: CanonicalHookEvent): string[] {\n const data = loadMappings();\n return Object.entries(data.providerMappings)\n .filter(([, profile]) => profile.mappings[canonical]?.supported)\n .map(([id]) => id);\n}\n\n/**\n * Get canonical events common to all specified providers.\n *\n * @remarks\n * Computes the intersection of supported events across multiple providers.\n * Returns only events that every listed provider supports. Useful for\n * determining which hooks can be configured uniformly across a set of\n * target providers.\n *\n * @param providerIds - Array of provider IDs to intersect.\n * @returns Array of canonical events supported by all specified providers.\n *\n * @example\n * ```typescript\n * import { getCommonEvents } from \"./normalizer.js\";\n *\n * const common = getCommonEvents([\"claude-code\", \"gemini-cli\"]);\n * // Returns only events both providers support\n * ```\n *\n * @public\n */\nexport function getCommonEvents(providerIds: string[]): CanonicalHookEvent[] {\n if (providerIds.length === 0) return [];\n return CANONICAL_HOOK_EVENTS.filter(\n (event) => providerIds.every((id) => supportsHook(event, id)),\n );\n}\n\n// ── Summary & Matrix Functions ──────────────────────────────────────\n\n/**\n * Get a summary of hook support for a provider.\n *\n * @remarks\n * Builds an aggregated view of a provider's hook capabilities including\n * counts, coverage percentage, and categorized event lists. Returns\n * `undefined` if the provider has no hook mappings. Used by the CLI\n * `hooks show` command for provider overviews.\n *\n * @param providerId - The provider identifier to summarize.\n * @returns The hook support summary, or `undefined` if the provider is not found.\n *\n * @example\n * ```typescript\n * import { getProviderSummary } from \"./normalizer.js\";\n *\n * const summary = getProviderSummary(\"claude-code\");\n * if (summary) {\n * console.log(`${summary.coverage}% coverage`);\n * console.log(`${summary.supportedCount}/${summary.totalCanonical} events`);\n * }\n * ```\n *\n * @public\n */\nexport function getProviderSummary(providerId: string): ProviderHookSummary | undefined {\n const profile = getProviderHookProfile(providerId);\n if (!profile) return undefined;\n\n const supported = getSupportedEvents(providerId);\n const unsupported = getUnsupportedEvents(providerId);\n\n return {\n providerId,\n hookSystem: profile.hookSystem,\n experimental: profile.experimental,\n supportedCount: supported.length,\n totalCanonical: CANONICAL_HOOK_EVENTS.length,\n supported,\n unsupported,\n providerOnly: profile.providerOnlyEvents,\n coverage: Math.round((supported.length / CANONICAL_HOOK_EVENTS.length) * 100),\n };\n}\n\n/**\n * Build a cross-provider hook support matrix.\n *\n * @remarks\n * Constructs a two-dimensional matrix showing which canonical events are\n * supported by which providers, with native name translations. When no\n * provider IDs are specified, all mapped providers are included. Used by\n * the CLI `hooks matrix` command for comparison tables.\n *\n * @param providerIds - Optional array of provider IDs to include. Defaults to all mapped providers.\n * @returns The cross-provider matrix with events, providers, and mapping data.\n *\n * @example\n * ```typescript\n * import { buildHookMatrix } from \"./normalizer.js\";\n *\n * const matrix = buildHookMatrix([\"claude-code\", \"gemini-cli\"]);\n * for (const event of matrix.events) {\n * for (const provider of matrix.providers) {\n * console.log(`${event} @ ${provider}: ${matrix.matrix[event][provider].supported}`);\n * }\n * }\n * ```\n *\n * @public\n */\nexport function buildHookMatrix(providerIds?: string[]): CrossProviderMatrix {\n const data = loadMappings();\n const ids = providerIds ?? Object.keys(data.providerMappings);\n\n const matrix: Record<string, Record<string, HookMapping>> = {};\n for (const event of CANONICAL_HOOK_EVENTS) {\n matrix[event] = {};\n for (const id of ids) {\n const profile = data.providerMappings[id];\n matrix[event][id] = profile?.mappings[event] ?? {\n nativeName: null,\n supported: false,\n };\n }\n }\n\n return {\n events: [...CANONICAL_HOOK_EVENTS],\n providers: ids,\n matrix: matrix as CrossProviderMatrix[\"matrix\"],\n };\n}\n\n/**\n * Get the hook system type for a provider.\n *\n * @remarks\n * Returns the provider's hook system type: `\"config\"` for file-based hooks,\n * `\"plugin\"` for extension-based hooks, or `\"none\"` if the provider does\n * not support hooks. Returns `\"none\"` for unknown providers.\n *\n * @param providerId - The provider identifier to query.\n * @returns The hook system type (`\"config\"`, `\"plugin\"`, or `\"none\"`).\n *\n * @example\n * ```typescript\n * import { getHookSystemType } from \"./normalizer.js\";\n *\n * getHookSystemType(\"claude-code\"); // \"config\"\n * getHookSystemType(\"unknown\"); // \"none\"\n * ```\n *\n * @public\n */\nexport function getHookSystemType(providerId: string): HookSystemType {\n const profile = getProviderHookProfile(providerId);\n return profile?.hookSystem ?? \"none\";\n}\n\n/**\n * Get the resolved hook config path for a provider.\n *\n * @remarks\n * Resolves the provider's hook configuration file path by expanding\n * registry template variables (e.g. `~` to the home directory). Returns\n * `null` if the provider has no hook config path defined or is unknown.\n *\n * @param providerId - The provider identifier to query.\n * @returns The resolved filesystem path, or `null` if not available.\n *\n * @example\n * ```typescript\n * import { getHookConfigPath } from \"./normalizer.js\";\n *\n * const path = getHookConfigPath(\"claude-code\");\n * // \"/home/user/.claude/settings.json\" (resolved from template)\n * ```\n *\n * @public\n */\nexport function getHookConfigPath(providerId: string): string | null {\n const profile = getProviderHookProfile(providerId);\n if (!profile?.hookConfigPath) return null;\n return resolveRegistryTemplatePath(profile.hookConfigPath);\n}\n\n/**\n * Get provider-only events (native events with no canonical mapping).\n *\n * @remarks\n * Some providers define hook events that have no equivalent in the CAAMP\n * canonical taxonomy. This function returns those provider-specific event\n * names. Returns an empty array for unknown providers.\n *\n * @param providerId - The provider identifier to query.\n * @returns Array of native event names unique to this provider.\n *\n * @example\n * ```typescript\n * import { getProviderOnlyEvents } from \"./normalizer.js\";\n *\n * const extras = getProviderOnlyEvents(\"claude-code\");\n * // Returns any events specific to Claude Code with no canonical equivalent\n * ```\n *\n * @public\n */\nexport function getProviderOnlyEvents(providerId: string): string[] {\n const profile = getProviderHookProfile(providerId);\n return profile?.providerOnlyEvents ?? [];\n}\n\n// ── Multi-Provider Translation ──────────────────────────────────────\n\n/**\n * Translate a canonical event to native names across multiple providers.\n *\n * @remarks\n * Performs a fan-out translation of a single canonical event to all\n * specified providers simultaneously. The result record only includes\n * providers that actually support the event — unsupported providers\n * are silently excluded.\n *\n * @param canonical - The canonical event name to translate.\n * @param providerIds - Array of provider IDs to translate for.\n * @returns Record mapping provider IDs to their native event names (supported only).\n *\n * @example\n * ```typescript\n * import { translateToAll } from \"./normalizer.js\";\n *\n * const result = translateToAll(\"PreToolUse\", [\"claude-code\", \"gemini-cli\", \"kimi\"]);\n * // { \"claude-code\": \"PreToolUse\", \"gemini-cli\": \"BeforeTool\" }\n * // (kimi excluded — unsupported)\n * ```\n *\n * @public\n */\nexport function translateToAll(\n canonical: CanonicalHookEvent,\n providerIds: string[],\n): Record<string, string> {\n const result: Record<string, string> = {};\n for (const id of providerIds) {\n const native = toNative(canonical, id);\n if (native) {\n result[id] = native;\n }\n }\n return result;\n}\n\n/**\n * Find the best canonical match for a native event name across all providers.\n *\n * @remarks\n * Scans every provider's mappings to find canonical events that match the\n * given native name. Useful when you have a native name but do not know\n * which provider it belongs to. Multiple results are possible if different\n * providers use the same native name for different canonical events.\n *\n * @param nativeName - The provider-native event name to resolve.\n * @returns Array of matches, each containing the provider ID and canonical event name.\n *\n * @example\n * ```typescript\n * import { resolveNativeEvent } from \"./normalizer.js\";\n *\n * const matches = resolveNativeEvent(\"BeforeTool\");\n * // [{ providerId: \"gemini-cli\", canonical: \"PreToolUse\" }]\n * ```\n *\n * @public\n */\nexport function resolveNativeEvent(nativeName: string): Array<{\n providerId: string;\n canonical: CanonicalHookEvent;\n}> {\n const data = loadMappings();\n const results: Array<{ providerId: string; canonical: CanonicalHookEvent }> = [];\n\n for (const [providerId, profile] of Object.entries(data.providerMappings)) {\n for (const [canonical, mapping] of Object.entries(profile.mappings)) {\n if (mapping.supported && mapping.nativeName === nativeName) {\n results.push({ providerId, canonical: canonical as CanonicalHookEvent });\n }\n }\n }\n\n return results;\n}\n\n/**\n * Get the version of the hook mappings data.\n *\n * @remarks\n * Returns the semver version string from the hook mappings JSON file.\n * This can be used to check compatibility or display the data version\n * in diagnostic output.\n *\n * @returns The semver version string of the loaded hook mappings data.\n *\n * @example\n * ```typescript\n * import { getHookMappingsVersion } from \"./normalizer.js\";\n *\n * const version = getHookMappingsVersion();\n * console.log(`Hook mappings v${version}`);\n * ```\n *\n * @public\n */\nexport function getHookMappingsVersion(): string {\n return loadMappings().version;\n}\n"],"mappings":"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","names":[]}
|
|
@@ -2,7 +2,7 @@ import {
|
|
|
2
2
|
resolveProviderSkillsDir,
|
|
3
3
|
resolveProvidersRegistryPath,
|
|
4
4
|
resolveRegistryTemplatePath
|
|
5
|
-
} from "./chunk-
|
|
5
|
+
} from "./chunk-TI6WOJDG.js";
|
|
6
6
|
|
|
7
7
|
// src/core/instructions/injector.ts
|
|
8
8
|
import { existsSync } from "fs";
|
|
@@ -493,4 +493,4 @@ export {
|
|
|
493
493
|
ensureProviderInstructionFile,
|
|
494
494
|
ensureAllProviderInstructionFiles
|
|
495
495
|
};
|
|
496
|
-
//# sourceMappingURL=chunk-
|
|
496
|
+
//# sourceMappingURL=chunk-O7IVK5JY.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/core/instructions/injector.ts","../src/core/registry/providers.ts","../src/core/instructions/templates.ts"],"sourcesContent":["/**\n * Marker-based instruction file injection\n *\n * Injects content blocks between CAAMP markers in instruction files\n * (CLAUDE.md, AGENTS.md, GEMINI.md).\n */\n\nimport { existsSync } from \"node:fs\";\nimport { mkdir, readFile, writeFile } from \"node:fs/promises\";\nimport { dirname, join } from \"node:path\";\nimport type { InjectionCheckResult, InjectionStatus, Provider } from \"../../types.js\";\nimport { getProvider } from \"../registry/providers.js\";\nimport { buildInjectionContent, type InjectionTemplate } from \"./templates.js\";\n\nconst MARKER_START = \"<!-- CAAMP:START -->\";\nconst MARKER_END = \"<!-- CAAMP:END -->\";\nconst MARKER_PATTERN = /<!-- CAAMP:START -->[\\s\\S]*?<!-- CAAMP:END -->/g;\nconst MARKER_PATTERN_SINGLE = /<!-- CAAMP:START -->[\\s\\S]*?<!-- CAAMP:END -->/;\n\n/**\n * Check the status of a CAAMP injection block in an instruction file.\n *\n * Returns the injection status:\n * - `\"missing\"` - File does not exist\n * - `\"none\"` - File exists but has no CAAMP markers\n * - `\"current\"` - CAAMP block exists and matches expected content (or no expected content given)\n * - `\"outdated\"` - CAAMP block exists but differs from expected content\n *\n * @param filePath - Absolute path to the instruction file\n * @param expectedContent - Optional expected content to compare against\n * @returns The injection status\n *\n * @remarks\n * Does not modify the file. Safe to call repeatedly for status checks.\n *\n * @example\n * ```typescript\n * const status = await checkInjection(\"/project/CLAUDE.md\", expectedContent);\n * if (status === \"outdated\") {\n * console.log(\"CAAMP injection needs updating\");\n * }\n * ```\n *\n * @public\n */\nexport async function checkInjection(\n filePath: string,\n expectedContent?: string,\n): Promise<InjectionStatus> {\n if (!existsSync(filePath)) return \"missing\";\n\n const content = await readFile(filePath, \"utf-8\");\n\n if (!MARKER_PATTERN_SINGLE.test(content)) return \"none\";\n\n if (expectedContent) {\n const blockContent = extractBlock(content);\n if (blockContent && blockContent.trim() === expectedContent.trim()) {\n return \"current\";\n }\n return \"outdated\";\n }\n\n return \"current\";\n}\n\n/** Extract the content between CAAMP markers */\nfunction extractBlock(content: string): string | null {\n const match = content.match(MARKER_PATTERN_SINGLE);\n if (!match) return null;\n\n return match[0]\n .replace(MARKER_START, \"\")\n .replace(MARKER_END, \"\")\n .trim();\n}\n\n/** Build the injection block */\nfunction buildBlock(content: string): string {\n return `${MARKER_START}\\n${content}\\n${MARKER_END}`;\n}\n\n/**\n * Inject content into an instruction file between CAAMP markers.\n *\n * Behavior depends on the file state:\n * - File does not exist: creates the file with the injection block → `\"created\"`\n * - File exists without markers: prepends the injection block → `\"added\"`\n * - File exists with multiple markers (duplicates): consolidates into single block → `\"consolidated\"`\n * - File exists with markers, content differs: replaces the block → `\"updated\"`\n * - File exists with markers, content matches: no-op → `\"intact\"`\n *\n * This function is **idempotent** — calling it multiple times with the same\n * content will not modify the file after the first write.\n *\n * @param filePath - Absolute path to the instruction file\n * @param content - Content to inject between CAAMP markers\n * @returns Action taken: `\"created\"`, `\"added\"`, `\"consolidated\"`, `\"updated\"`, or `\"intact\"`\n *\n * @remarks\n * Handles duplicate marker consolidation automatically. When multiple CAAMP\n * blocks are detected (from manual edits or bugs), they are merged into one.\n *\n * @example\n * ```typescript\n * const action = await inject(\"/project/CLAUDE.md\", \"## My Config\\nSome content\");\n * console.log(`File ${action}`); // \"created\" on first call, \"intact\" on subsequent\n * ```\n *\n * @public\n */\nexport async function inject(\n filePath: string,\n content: string,\n): Promise<\"created\" | \"added\" | \"consolidated\" | \"updated\" | \"intact\"> {\n const block = buildBlock(content);\n\n // Ensure parent directory exists\n await mkdir(dirname(filePath), { recursive: true });\n\n if (!existsSync(filePath)) {\n // Create new file with injection block\n await writeFile(filePath, `${block}\\n`, \"utf-8\");\n return \"created\";\n }\n\n const existing = await readFile(filePath, \"utf-8\");\n\n // Find all CAAMP blocks in the file\n const matches = existing.match(MARKER_PATTERN);\n\n if (matches && matches.length > 0) {\n // Check if there are multiple duplicate blocks\n if (matches.length > 1) {\n // Consolidate all blocks into a single clean block\n const updated = existing\n .replace(MARKER_PATTERN, \"\")\n .replace(/^\\n{2,}/, \"\\n\")\n .trim();\n \n // Write the clean content with a single block\n const finalContent = updated \n ? `${block}\\n\\n${updated}`\n : `${block}\\n`;\n await writeFile(filePath, finalContent, \"utf-8\");\n return \"consolidated\";\n }\n\n // Check if existing content already matches (idempotency)\n const existingBlock = extractBlock(existing);\n if (existingBlock !== null && existingBlock.trim() === content.trim()) {\n return \"intact\";\n }\n\n // Replace existing block with new content\n const updated = existing.replace(MARKER_PATTERN_SINGLE, block);\n await writeFile(filePath, updated, \"utf-8\");\n return \"updated\";\n }\n\n // Prepend block to existing content\n const updated = `${block}\\n\\n${existing}`;\n await writeFile(filePath, updated, \"utf-8\");\n return \"added\";\n}\n\n/**\n * Remove the CAAMP injection block from an instruction file.\n *\n * If removing the block would leave the file empty, the file is deleted entirely.\n *\n * @param filePath - Absolute path to the instruction file\n * @returns `true` if a CAAMP block was found and removed, `false` otherwise\n *\n * @remarks\n * Cleans up any leftover blank lines after removing the block. If the file\n * would be entirely empty after removal, the file itself is deleted.\n *\n * @example\n * ```typescript\n * const removed = await removeInjection(\"/project/CLAUDE.md\");\n * ```\n *\n * @public\n */\nexport async function removeInjection(filePath: string): Promise<boolean> {\n if (!existsSync(filePath)) return false;\n\n const content = await readFile(filePath, \"utf-8\");\n if (!MARKER_PATTERN.test(content)) return false;\n\n const cleaned = content\n .replace(MARKER_PATTERN, \"\")\n .replace(/^\\n{2,}/, \"\\n\")\n .trim();\n\n if (!cleaned) {\n // File would be empty - remove it entirely\n const { rm } = await import(\"node:fs/promises\");\n await rm(filePath);\n } else {\n await writeFile(filePath, `${cleaned}\\n`, \"utf-8\");\n }\n\n return true;\n}\n\n/**\n * Check injection status across all providers' instruction files.\n *\n * Deduplicates by file path since multiple providers may share the same\n * instruction file (e.g. many providers use `AGENTS.md`).\n *\n * @param providers - Array of providers to check\n * @param projectDir - Absolute path to the project directory\n * @param scope - Whether to check project or global instruction files\n * @param expectedContent - Optional expected content to compare against\n * @returns Array of injection check results, one per unique instruction file\n *\n * @remarks\n * Multiple providers may share the same instruction file (e.g. many use\n * `AGENTS.md`). This function deduplicates to avoid redundant file reads.\n *\n * @example\n * ```typescript\n * const results = await checkAllInjections(providers, \"/project\", \"project\", expected);\n * const outdated = results.filter(r => r.status === \"outdated\");\n * ```\n *\n * @public\n */\nexport async function checkAllInjections(\n providers: Provider[],\n projectDir: string,\n scope: \"project\" | \"global\",\n expectedContent?: string,\n): Promise<InjectionCheckResult[]> {\n const results: InjectionCheckResult[] = [];\n const checked = new Set<string>();\n\n for (const provider of providers) {\n const filePath = scope === \"global\"\n ? join(provider.pathGlobal, provider.instructFile)\n : join(projectDir, provider.instructFile);\n\n // Skip duplicates (multiple providers share same instruction file)\n if (checked.has(filePath)) continue;\n checked.add(filePath);\n\n const status = await checkInjection(filePath, expectedContent);\n\n results.push({\n file: filePath,\n provider: provider.id,\n status,\n fileExists: existsSync(filePath),\n });\n }\n\n return results;\n}\n\n/**\n * Inject content into all providers' instruction files.\n *\n * Deduplicates by file path to avoid injecting the same file multiple times.\n *\n * @param providers - Array of providers to inject into\n * @param projectDir - Absolute path to the project directory\n * @param scope - Whether to target project or global instruction files\n * @param content - Content to inject between CAAMP markers\n * @returns Map of file path to action taken (`\"created\"`, `\"added\"`, `\"consolidated\"`, `\"updated\"`, or `\"intact\"`)\n *\n * @remarks\n * Providers sharing the same instruction file are only written once to avoid\n * conflicting concurrent writes.\n *\n * @example\n * ```typescript\n * const results = await injectAll(providers, \"/project\", \"project\", content);\n * for (const [file, action] of results) {\n * console.log(`${file}: ${action}`);\n * }\n * ```\n *\n * @public\n */\nexport async function injectAll(\n providers: Provider[],\n projectDir: string,\n scope: \"project\" | \"global\",\n content: string,\n): Promise<Map<string, \"created\" | \"added\" | \"consolidated\" | \"updated\" | \"intact\">> {\n const results = new Map<string, \"created\" | \"added\" | \"consolidated\" | \"updated\" | \"intact\">();\n const injected = new Set<string>();\n\n for (const provider of providers) {\n const filePath = scope === \"global\"\n ? join(provider.pathGlobal, provider.instructFile)\n : join(projectDir, provider.instructFile);\n\n // Skip duplicates\n if (injected.has(filePath)) continue;\n injected.add(filePath);\n\n const action = await inject(filePath, content);\n results.set(filePath, action);\n }\n\n return results;\n}\n\n// ── Provider Instruction File API ─────────────────────────────────\n\n/**\n * Options for ensuring a provider instruction file.\n *\n * @public\n */\nexport interface EnsureProviderInstructionFileOptions {\n /** `\\@` references to inject (e.g. `[\"\\@AGENTS.md\"]`). */\n references: string[];\n /** Optional inline content blocks. @defaultValue `undefined` */\n content?: string[];\n /** Whether this is a global or project-level file. @defaultValue `\"project\"` */\n scope?: \"project\" | \"global\";\n}\n\n/**\n * Result of ensuring a provider instruction file.\n *\n * @public\n */\nexport interface EnsureProviderInstructionFileResult {\n /** Absolute path to the instruction file. */\n filePath: string;\n /** Instruction file name from the provider registry. */\n instructFile: string;\n /** Action taken. */\n action: \"created\" | \"added\" | \"consolidated\" | \"updated\" | \"intact\";\n /** Provider ID. */\n providerId: string;\n}\n\n/**\n * Ensure a provider's instruction file exists with the correct CAAMP block.\n *\n * This is the canonical API for adapters and external packages to manage\n * provider instruction files. Instead of directly creating/modifying\n * CLAUDE.md, GEMINI.md, etc., callers should use this function to\n * delegate instruction file management to CAAMP.\n *\n * The instruction file name is resolved from CAAMP's provider registry\n * (single source of truth), not hardcoded by the caller.\n *\n * @remarks\n * The instruction file name is resolved from CAAMP's provider registry\n * (single source of truth), not hardcoded by the caller.\n *\n * @param providerId - Provider ID from the registry (e.g. `\"claude-code\"`, `\"gemini-cli\"`)\n * @param projectDir - Absolute path to the project directory\n * @param options - References, content, and scope configuration\n * @returns Result with file path, action taken, and provider metadata\n * @throws Error if the provider ID is not found in the registry\n *\n * @example\n * ```typescript\n * const result = await ensureProviderInstructionFile(\"claude-code\", \"/project\", {\n * references: [\"\\@AGENTS.md\"],\n * });\n * ```\n *\n * @public\n */\nexport async function ensureProviderInstructionFile(\n providerId: string,\n projectDir: string,\n options: EnsureProviderInstructionFileOptions,\n): Promise<EnsureProviderInstructionFileResult> {\n const provider = getProvider(providerId);\n if (!provider) {\n throw new Error(`Unknown provider: \"${providerId}\". Check CAAMP provider registry.`);\n }\n\n const scope = options.scope ?? \"project\";\n const filePath = scope === \"global\"\n ? join(provider.pathGlobal, provider.instructFile)\n : join(projectDir, provider.instructFile);\n\n const template: InjectionTemplate = {\n references: options.references,\n content: options.content,\n };\n\n const injectionContent = buildInjectionContent(template);\n const action = await inject(filePath, injectionContent);\n\n return {\n filePath,\n instructFile: provider.instructFile,\n action,\n providerId: provider.id,\n };\n}\n\n/**\n * Ensure instruction files for multiple providers at once.\n *\n * Deduplicates by file path — providers sharing the same instruction file\n * (e.g. many providers use AGENTS.md) are only written once.\n *\n * @remarks\n * Providers sharing the same instruction file (e.g. many use `AGENTS.md`)\n * are only written once, avoiding duplicate blocks.\n *\n * @param providerIds - Array of provider IDs from the registry\n * @param projectDir - Absolute path to the project directory\n * @param options - References, content, and scope configuration\n * @returns Array of results, one per unique instruction file\n * @throws Error if any provider ID is not found in the registry\n *\n * @example\n * ```typescript\n * const results = await ensureAllProviderInstructionFiles(\n * [\"claude-code\", \"cursor\", \"gemini-cli\"],\n * \"/project\",\n * { references: [\"\\@AGENTS.md\"] },\n * );\n * ```\n *\n * @public\n */\nexport async function ensureAllProviderInstructionFiles(\n providerIds: string[],\n projectDir: string,\n options: EnsureProviderInstructionFileOptions,\n): Promise<EnsureProviderInstructionFileResult[]> {\n const results: EnsureProviderInstructionFileResult[] = [];\n const processed = new Set<string>();\n\n for (const providerId of providerIds) {\n const provider = getProvider(providerId);\n if (!provider) {\n throw new Error(`Unknown provider: \"${providerId}\". Check CAAMP provider registry.`);\n }\n\n const scope = options.scope ?? \"project\";\n const filePath = scope === \"global\"\n ? join(provider.pathGlobal, provider.instructFile)\n : join(projectDir, provider.instructFile);\n\n // Skip duplicates (multiple providers may share the same instruction file)\n if (processed.has(filePath)) continue;\n processed.add(filePath);\n\n const template: InjectionTemplate = {\n references: options.references,\n content: options.content,\n };\n\n const injectionContent = buildInjectionContent(template);\n const action = await inject(filePath, injectionContent);\n\n results.push({\n filePath,\n instructFile: provider.instructFile,\n action,\n providerId: provider.id,\n });\n }\n\n return results;\n}\n","/**\n * Provider registry loader\n *\n * Loads providers from providers/registry.json and resolves\n * platform-specific paths at runtime.\n */\n\nimport { readFileSync } from \"node:fs\";\nimport { dirname, join } from \"node:path\";\nimport { fileURLToPath } from \"node:url\";\nimport type {\n ConfigFormat,\n DetectionMethod,\n Provider,\n ProviderCapabilities,\n ProviderHooksCapability,\n ProviderPriority,\n ProviderSkillsCapability,\n ProviderSpawnCapability,\n ProviderStatus,\n TransportType,\n} from \"../../types.js\";\nimport { type PathScope, resolveProvidersRegistryPath, resolveProviderSkillsDir, resolveRegistryTemplatePath } from \"../paths/standard.js\";\nimport type { HookEvent, ProviderRegistry, RegistryCapabilities, RegistryProvider, SkillsPrecedence, SpawnMechanism } from \"./types.js\";\n\n// ── Capability Defaults ──────────────────────────────────────────────\n\nconst DEFAULT_SKILLS_CAPABILITY: ProviderSkillsCapability = {\n agentsGlobalPath: null,\n agentsProjectPath: null,\n precedence: \"vendor-only\",\n};\n\nconst DEFAULT_HOOKS_CAPABILITY: ProviderHooksCapability = {\n supported: [],\n hookConfigPath: null,\n hookFormat: null,\n};\n\nconst DEFAULT_SPAWN_CAPABILITY: ProviderSpawnCapability = {\n supportsSubagents: false,\n supportsProgrammaticSpawn: false,\n supportsInterAgentComms: false,\n supportsParallelSpawn: false,\n spawnMechanism: null,\n};\n\nfunction resolveCapabilities(raw?: RegistryCapabilities): ProviderCapabilities {\n const skills: ProviderSkillsCapability = raw?.skills\n ? {\n agentsGlobalPath: raw.skills.agentsGlobalPath\n ? resolveRegistryTemplatePath(raw.skills.agentsGlobalPath)\n : null,\n agentsProjectPath: raw.skills.agentsProjectPath,\n precedence: raw.skills.precedence,\n }\n : { ...DEFAULT_SKILLS_CAPABILITY };\n\n const hooks: ProviderHooksCapability = raw?.hooks\n ? {\n supported: raw.hooks.supported as HookEvent[],\n hookConfigPath: raw.hooks.hookConfigPath\n ? resolveRegistryTemplatePath(raw.hooks.hookConfigPath)\n : null,\n hookFormat: raw.hooks.hookFormat as ProviderHooksCapability[\"hookFormat\"],\n }\n : { ...DEFAULT_HOOKS_CAPABILITY, supported: [] };\n\n const spawn: ProviderSpawnCapability = raw?.spawn\n ? {\n supportsSubagents: raw.spawn.supportsSubagents,\n supportsProgrammaticSpawn: raw.spawn.supportsProgrammaticSpawn,\n supportsInterAgentComms: raw.spawn.supportsInterAgentComms,\n supportsParallelSpawn: raw.spawn.supportsParallelSpawn,\n spawnMechanism: raw.spawn.spawnMechanism as SpawnMechanism | null,\n }\n : { ...DEFAULT_SPAWN_CAPABILITY };\n\n return { skills, hooks, spawn };\n}\n\nfunction findRegistryPath(): string {\n const thisDir = dirname(fileURLToPath(import.meta.url));\n return resolveProvidersRegistryPath(thisDir);\n}\n\nlet _registry: ProviderRegistry | null = null;\nlet _providers: Map<string, Provider> | null = null;\nlet _aliasMap: Map<string, string> | null = null;\n\nfunction resolveProvider(raw: RegistryProvider): Provider {\n return {\n id: raw.id,\n toolName: raw.toolName,\n vendor: raw.vendor,\n agentFlag: raw.agentFlag,\n aliases: raw.aliases,\n pathGlobal: resolveRegistryTemplatePath(raw.pathGlobal),\n pathProject: raw.pathProject,\n instructFile: raw.instructFile,\n configKey: raw.configKey,\n configFormat: raw.configFormat as ConfigFormat,\n configPathGlobal: resolveRegistryTemplatePath(raw.configPathGlobal),\n configPathProject: raw.configPathProject,\n pathSkills: resolveRegistryTemplatePath(raw.pathSkills),\n pathProjectSkills: raw.pathProjectSkills,\n detection: {\n methods: raw.detection.methods as DetectionMethod[],\n binary: raw.detection.binary,\n directories: raw.detection.directories?.map(resolveRegistryTemplatePath),\n appBundle: raw.detection.appBundle,\n flatpakId: raw.detection.flatpakId,\n },\n supportedTransports: raw.supportedTransports as TransportType[],\n supportsHeaders: raw.supportsHeaders,\n priority: raw.priority as ProviderPriority,\n status: raw.status as ProviderStatus,\n agentSkillsCompatible: raw.agentSkillsCompatible,\n capabilities: resolveCapabilities(raw.capabilities),\n };\n}\n\nfunction loadRegistry(): ProviderRegistry {\n if (_registry) return _registry;\n\n const registryPath = findRegistryPath();\n const raw = readFileSync(registryPath, \"utf-8\");\n _registry = JSON.parse(raw) as ProviderRegistry;\n return _registry;\n}\n\nfunction ensureProviders(): void {\n if (_providers) return;\n\n const registry = loadRegistry();\n _providers = new Map<string, Provider>();\n _aliasMap = new Map<string, string>();\n\n for (const [id, raw] of Object.entries(registry.providers)) {\n const provider = resolveProvider(raw);\n _providers.set(id, provider);\n\n // Build alias map\n for (const alias of provider.aliases) {\n _aliasMap.set(alias, id);\n }\n }\n}\n\n/**\n * Retrieve all registered providers with resolved platform paths.\n *\n * Providers are lazily loaded from `providers/registry.json` on first call\n * and cached for subsequent calls.\n *\n * @remarks\n * The registry is parsed once and cached in-module state. Platform-specific\n * template paths (e.g. `~/.config/...`) are resolved at load time via\n * {@link resolveRegistryTemplatePath}. Call {@link resetRegistry} to force\n * a reload.\n *\n * @returns Array of all provider definitions\n *\n * @example\n * ```typescript\n * const providers = getAllProviders();\n * console.log(`${providers.length} providers registered`);\n * ```\n *\n * @public\n */\nexport function getAllProviders(): Provider[] {\n ensureProviders();\n if (!_providers) return [];\n return Array.from(_providers.values());\n}\n\n/**\n * Look up a provider by its ID or any of its aliases.\n *\n * @remarks\n * Alias resolution is performed via an internal map built during registry loading.\n * If the input matches an alias, it is resolved to the canonical provider ID before\n * lookup. If it matches neither an alias nor a canonical ID, `undefined` is returned.\n *\n * @param idOrAlias - Provider ID (e.g. `\"claude-code\"`) or alias (e.g. `\"claude\"`)\n * @returns The matching provider, or `undefined` if not found\n *\n * @example\n * ```typescript\n * const provider = getProvider(\"claude\");\n * // Returns the claude-code provider via alias resolution\n * ```\n *\n * @public\n */\nexport function getProvider(idOrAlias: string): Provider | undefined {\n ensureProviders();\n const resolved = _aliasMap?.get(idOrAlias) ?? idOrAlias;\n return _providers?.get(resolved);\n}\n\n/**\n * Resolve an alias to its canonical provider ID.\n *\n * If the input is already a canonical ID (or unrecognized), it is returned as-is.\n *\n * @remarks\n * Alias mappings are built from the `aliases` array in each provider's registry\n * entry. This function is safe to call with canonical IDs -- they pass through unchanged.\n *\n * @param idOrAlias - Provider ID or alias to resolve\n * @returns The canonical provider ID\n *\n * @example\n * ```typescript\n * resolveAlias(\"claude\"); // \"claude-code\"\n * resolveAlias(\"claude-code\"); // \"claude-code\"\n * resolveAlias(\"unknown\"); // \"unknown\"\n * ```\n *\n * @public\n */\nexport function resolveAlias(idOrAlias: string): string {\n ensureProviders();\n return _aliasMap?.get(idOrAlias) ?? idOrAlias;\n}\n\n/**\n * Filter providers by their priority tier.\n *\n * @remarks\n * Provider priority is assigned in `providers/registry.json` and indicates the\n * relative importance of a provider for detection ordering and display.\n *\n * @param priority - Priority level to filter by (`\"high\"`, `\"medium\"`, or `\"low\"`)\n * @returns Array of providers matching the given priority\n *\n * @example\n * ```typescript\n * const highPriority = getProvidersByPriority(\"high\");\n * console.log(highPriority.map(p => p.toolName));\n * ```\n *\n * @public\n */\nexport function getProvidersByPriority(priority: ProviderPriority): Provider[] {\n return getAllProviders().filter((p) => p.priority === priority);\n}\n\n/**\n * Filter providers by their lifecycle status.\n *\n * @remarks\n * Lifecycle status is maintained per-provider in the registry and reflects\n * the provider's stability and support level within CAAMP.\n *\n * @param status - Status to filter by (`\"active\"`, `\"beta\"`, `\"deprecated\"`, or `\"planned\"`)\n * @returns Array of providers matching the given status\n *\n * @example\n * ```typescript\n * const active = getProvidersByStatus(\"active\");\n * console.log(`${active.length} active providers`);\n * ```\n *\n * @public\n */\nexport function getProvidersByStatus(status: ProviderStatus): Provider[] {\n return getAllProviders().filter((p) => p.status === status);\n}\n\n/**\n * Filter providers that use a specific instruction file.\n *\n * Multiple providers often share the same instruction file (e.g. many use `\"AGENTS.md\"`).\n *\n * @remarks\n * CAAMP supports three instruction file types: `CLAUDE.md`, `AGENTS.md`, and `GEMINI.md`.\n * Most providers read from `AGENTS.md` as the universal standard, while a few\n * have vendor-specific files.\n *\n * @param file - Instruction file name (e.g. `\"CLAUDE.md\"`, `\"AGENTS.md\"`)\n * @returns Array of providers that use the given instruction file\n *\n * @example\n * ```typescript\n * const claudeProviders = getProvidersByInstructFile(\"CLAUDE.md\");\n * console.log(claudeProviders.map(p => p.id));\n * ```\n *\n * @public\n */\nexport function getProvidersByInstructFile(file: string): Provider[] {\n return getAllProviders().filter((p) => p.instructFile === file);\n}\n\n/**\n * Get the set of all unique instruction file names across all providers.\n *\n * @remarks\n * Iterates over all registered providers and collects the distinct\n * `instructFile` values. The result is deduplicated via a `Set`.\n *\n * @returns Array of unique instruction file names (e.g. `[\"CLAUDE.md\", \"AGENTS.md\", \"GEMINI.md\"]`)\n *\n * @example\n * ```typescript\n * const files = getInstructionFiles();\n * // [\"CLAUDE.md\", \"AGENTS.md\", \"GEMINI.md\"]\n * ```\n *\n * @public\n */\nexport function getInstructionFiles(): string[] {\n const files = new Set<string>();\n for (const p of getAllProviders()) {\n files.add(p.instructFile);\n }\n return Array.from(files);\n}\n\n/**\n * Get the total number of registered providers.\n *\n * @remarks\n * Triggers lazy loading of the registry if not already loaded.\n * The count reflects the number of entries in `providers/registry.json`.\n *\n * @returns Count of providers in the registry\n *\n * @example\n * ```typescript\n * console.log(`Registry has ${getProviderCount()} providers`);\n * ```\n *\n * @public\n */\nexport function getProviderCount(): number {\n ensureProviders();\n return _providers?.size ?? 0;\n}\n\n/**\n * Get the semantic version string of the provider registry.\n *\n * @remarks\n * The version is read from the top-level `version` field in `providers/registry.json`\n * and follows semver conventions. It is bumped when provider definitions change.\n *\n * @returns Version string from `providers/registry.json` (e.g. `\"1.0.0\"`)\n *\n * @example\n * ```typescript\n * console.log(`Registry version: ${getRegistryVersion()}`);\n * ```\n *\n * @public\n */\nexport function getRegistryVersion(): string {\n return loadRegistry().version;\n}\n\n/**\n * Filter providers that support a specific hook event.\n *\n * @remarks\n * Hook events are declared per-provider in the `capabilities.hooks.supported`\n * array within the registry. Only providers that explicitly list the event\n * are returned.\n *\n * @param event - Hook event to filter by (e.g. `\"onToolComplete\"`)\n * @returns Array of providers whose hooks capability includes the given event\n *\n * @example\n * ```typescript\n * const providers = getProvidersByHookEvent(\"onToolComplete\");\n * console.log(providers.map(p => p.id));\n * ```\n *\n * @public\n */\nexport function getProvidersByHookEvent(event: HookEvent): Provider[] {\n return getAllProviders().filter((p) => p.capabilities.hooks.supported.includes(event));\n}\n\n/**\n * Get hook events common to all specified providers.\n *\n * If providerIds is provided, returns the intersection of their supported events.\n * If providerIds is undefined or empty, uses all providers.\n *\n * @remarks\n * Computes the set intersection of `capabilities.hooks.supported` across the\n * target providers. Useful for determining which hook events can be reliably\n * used across a multi-agent installation.\n *\n * @param providerIds - Optional array of provider IDs to intersect\n * @returns Array of hook events supported by ALL specified providers\n *\n * @example\n * ```typescript\n * const common = getCommonHookEvents([\"claude-code\", \"gemini-cli\"]);\n * console.log(`${common.length} common hook events`);\n * ```\n *\n * @public\n */\nexport function getCommonHookEvents(providerIds?: string[]): HookEvent[] {\n const providers = providerIds && providerIds.length > 0\n ? providerIds.map((id) => getProvider(id)).filter((p): p is Provider => p !== undefined)\n : getAllProviders();\n\n if (providers.length === 0) return [];\n\n const first = providers[0]!.capabilities.hooks.supported as HookEvent[];\n return first.filter((event) =>\n providers.every((p) => p.capabilities.hooks.supported.includes(event)),\n );\n}\n\n/**\n * Check whether a provider supports a specific capability via dot-path query.\n *\n * The dot-path addresses a value inside `provider.capabilities`. For boolean\n * fields the provider \"supports\" the capability when the value is `true`.\n * For non-boolean fields the provider \"supports\" it when the value is neither\n * `null` nor `undefined` (and, for arrays, non-empty).\n *\n * @remarks\n * This function traverses the capabilities object using dot-delimited path\n * segments. It handles three value types: booleans (must be `true`), arrays\n * (must be non-empty), and all other values (must be non-null/undefined).\n * Invalid paths return `false`.\n *\n * @param provider - Provider to inspect\n * @param dotPath - Dot-delimited capability path (e.g. `\"spawn.supportsSubagents\"`, `\"hooks.supported\"`)\n * @returns `true` when the provider has the specified capability\n *\n * @example\n * ```typescript\n * const claude = getProvider(\"claude-code\");\n * providerSupports(claude!, \"spawn.supportsSubagents\"); // true\n * providerSupports(claude!, \"hooks.supported\"); // true (non-empty array)\n * ```\n *\n * @public\n */\nexport function providerSupports(provider: Provider, dotPath: string): boolean {\n const parts = dotPath.split(\".\");\n // eslint-disable-next-line @typescript-eslint/no-explicit-any\n let current: any = provider.capabilities;\n for (const part of parts) {\n if (current == null || typeof current !== \"object\") return false;\n current = current[part];\n }\n if (typeof current === \"boolean\") return current;\n if (Array.isArray(current)) return current.length > 0;\n return current != null;\n}\n\n/**\n * Filter providers that support spawning subagents.\n *\n * @remarks\n * This is a convenience wrapper that checks the `capabilities.spawn.supportsSubagents`\n * boolean flag. For more granular spawn capability filtering, use\n * {@link getProvidersBySpawnCapability}.\n *\n * @returns Array of providers where `capabilities.spawn.supportsSubagents === true`\n *\n * @example\n * ```typescript\n * const spawnCapable = getSpawnCapableProviders();\n * console.log(spawnCapable.map(p => p.id));\n * ```\n *\n * @public\n */\nexport function getSpawnCapableProviders(): Provider[] {\n return getAllProviders().filter((p) => p.capabilities.spawn.supportsSubagents);\n}\n\n/**\n * Filter providers by a specific boolean spawn capability flag.\n *\n * @remarks\n * The spawn capability has four boolean flags that can be queried independently.\n * The `spawnMechanism` string field is excluded from the flag type since it is\n * not a boolean check.\n *\n * @param flag - One of the four boolean flags on {@link ProviderSpawnCapability}\n * (`\"supportsSubagents\"`, `\"supportsProgrammaticSpawn\"`,\n * `\"supportsInterAgentComms\"`, `\"supportsParallelSpawn\"`)\n * @returns Array of providers where the specified flag is `true`\n *\n * @example\n * ```typescript\n * const parallel = getProvidersBySpawnCapability(\"supportsParallelSpawn\");\n * console.log(parallel.map(p => p.id));\n * ```\n *\n * @see {@link getSpawnCapableProviders}\n *\n * @public\n */\nexport function getProvidersBySpawnCapability(\n flag: keyof Omit<ProviderSpawnCapability, \"spawnMechanism\">,\n): Provider[] {\n return getAllProviders().filter((p) => p.capabilities.spawn[flag] === true);\n}\n\n/**\n * Reset cached registry data, forcing a reload on next access.\n *\n * @remarks\n * Clears the in-memory provider map, alias map, and raw registry cache.\n * Primarily used in test suites to ensure a clean state between test cases.\n *\n * @example\n * ```typescript\n * resetRegistry();\n * // Next call to getAllProviders() will re-read registry.json\n * ```\n *\n * @public\n */\nexport function resetRegistry(): void {\n _registry = null;\n _providers = null;\n _aliasMap = null;\n}\n\n// ── Skills Query Functions ──────────────────────────────────────────\n\n/**\n * Filter providers by their skills precedence value.\n *\n * @remarks\n * Skills precedence controls how a provider resolves skill files when both\n * vendor-specific and `.agents/` standard paths exist. Values include\n * `\"vendor-only\"`, `\"agents-canonical\"`, `\"agents-first\"`, `\"agents-supported\"`,\n * and `\"vendor-global-agents-project\"`.\n *\n * @param precedence - Skills precedence to filter by\n * @returns Array of providers matching the given precedence\n *\n * @example\n * ```typescript\n * const vendorOnly = getProvidersBySkillsPrecedence(\"vendor-only\");\n * console.log(vendorOnly.map(p => p.id));\n * ```\n *\n * @public\n */\nexport function getProvidersBySkillsPrecedence(precedence: SkillsPrecedence): Provider[] {\n return getAllProviders().filter((p) => p.capabilities.skills.precedence === precedence);\n}\n\n/**\n * Get the effective skills paths for a provider, ordered by precedence.\n *\n * @remarks\n * The returned array is ordered by precedence priority. For example, with\n * `\"agents-first\"` precedence the `.agents/` path appears before the vendor\n * path. The `source` field indicates whether the path comes from the vendor\n * directory or the `.agents/` standard directory.\n *\n * @param provider - Provider to resolve paths for\n * @param scope - Whether to resolve global or project paths\n * @param projectDir - Project directory for project-scope resolution\n * @returns Ordered array of paths with source and scope metadata\n *\n * @example\n * ```typescript\n * const provider = getProvider(\"claude-code\")!;\n * const paths = getEffectiveSkillsPaths(provider, \"global\");\n * for (const p of paths) {\n * console.log(`${p.source} (${p.scope}): ${p.path}`);\n * }\n * ```\n *\n * @public\n */\nexport function getEffectiveSkillsPaths(\n provider: Provider,\n scope: PathScope,\n projectDir?: string,\n): Array<{ path: string; source: string; scope: string }> {\n const vendorPath = resolveProviderSkillsDir(provider, scope, projectDir);\n const { precedence, agentsGlobalPath, agentsProjectPath } = provider.capabilities.skills;\n\n const resolveAgentsPath = (): string | null => {\n if (scope === \"global\" && agentsGlobalPath) return agentsGlobalPath;\n if (scope === \"project\" && agentsProjectPath && projectDir) {\n return join(projectDir, agentsProjectPath);\n }\n return null;\n };\n\n const agentsPath = resolveAgentsPath();\n const scopeLabel = scope === \"global\" ? \"global\" : \"project\";\n\n switch (precedence) {\n case \"vendor-only\":\n return [{ path: vendorPath, source: \"vendor\", scope: scopeLabel }];\n case \"agents-canonical\":\n return agentsPath ? [{ path: agentsPath, source: \"agents\", scope: scopeLabel }] : [];\n case \"agents-first\":\n return [\n ...(agentsPath ? [{ path: agentsPath, source: \"agents\", scope: scopeLabel }] : []),\n { path: vendorPath, source: \"vendor\", scope: scopeLabel },\n ];\n case \"agents-supported\":\n return [\n { path: vendorPath, source: \"vendor\", scope: scopeLabel },\n ...(agentsPath ? [{ path: agentsPath, source: \"agents\", scope: scopeLabel }] : []),\n ];\n case \"vendor-global-agents-project\":\n if (scope === \"global\") {\n return [{ path: vendorPath, source: \"vendor\", scope: \"global\" }];\n }\n return [\n ...(agentsPath ? [{ path: agentsPath, source: \"agents\", scope: \"project\" }] : []),\n { path: vendorPath, source: \"vendor\", scope: \"project\" },\n ];\n default:\n return [{ path: vendorPath, source: \"vendor\", scope: scopeLabel }];\n }\n}\n\n/**\n * Build a full skills map for all providers.\n *\n * @remarks\n * Produces a summary of each provider's skills configuration including\n * the precedence mode and resolved global/project paths. For `\"vendor-only\"`\n * providers the paths point to the vendor skills directory; for others they\n * point to the `.agents/` standard paths.\n *\n * @returns Array of skills map entries with provider ID, tool name, precedence, and paths\n *\n * @example\n * ```typescript\n * const skillsMap = buildSkillsMap();\n * for (const entry of skillsMap) {\n * console.log(`${entry.providerId}: ${entry.precedence}`);\n * }\n * ```\n *\n * @public\n */\nexport function buildSkillsMap(): Array<{\n providerId: string;\n toolName: string;\n precedence: SkillsPrecedence;\n paths: { global: string | null; project: string | null };\n}> {\n return getAllProviders().map((p) => {\n const { precedence, agentsGlobalPath, agentsProjectPath } = p.capabilities.skills;\n const isVendorOnly = precedence === \"vendor-only\";\n return {\n providerId: p.id,\n toolName: p.toolName,\n precedence,\n paths: {\n global: isVendorOnly ? p.pathSkills : (agentsGlobalPath ?? null),\n project: isVendorOnly ? p.pathProjectSkills : (agentsProjectPath ?? null),\n },\n };\n });\n}\n\n/**\n * Get capabilities for a provider by ID or alias.\n *\n * @remarks\n * Shorthand for `getProvider(idOrAlias)?.capabilities`. Returns the full\n * capabilities object containing skills, hooks, and spawn sub-objects.\n *\n * @param idOrAlias - Provider ID or alias\n * @returns The provider's capabilities, or undefined if not found\n *\n * @example\n * ```typescript\n * const caps = getProviderCapabilities(\"claude-code\");\n * if (caps?.spawn.supportsSubagents) {\n * console.log(\"Supports subagent spawning\");\n * }\n * ```\n *\n * @public\n */\nexport function getProviderCapabilities(idOrAlias: string): ProviderCapabilities | undefined {\n return getProvider(idOrAlias)?.capabilities;\n}\n\n/**\n * Check if a provider supports a capability using ID/alias lookup.\n *\n * Convenience wrapper that resolves the provider first, then delegates\n * to the provider-level {@link providerSupports}.\n *\n * @remarks\n * Returns `false` both when the provider is not found and when the capability\n * is not supported. Use {@link getProvider} first if you need to distinguish\n * between these cases.\n *\n * @param idOrAlias - Provider ID or alias\n * @param capabilityPath - Dot-path into capabilities (e.g. \"spawn.supportsSubagents\")\n * @returns true if the provider supports the capability, false otherwise\n *\n * @example\n * ```typescript\n * if (providerSupportsById(\"claude-code\", \"spawn.supportsSubagents\")) {\n * console.log(\"Claude Code supports subagent spawning\");\n * }\n * ```\n *\n * @see {@link providerSupports}\n *\n * @public\n */\nexport function providerSupportsById(idOrAlias: string, capabilityPath: string): boolean {\n const provider = getProvider(idOrAlias);\n if (!provider) return false;\n return providerSupports(provider, capabilityPath);\n}\n","/**\n * Instruction template management\n *\n * Generates injection content based on provider capabilities.\n * Includes structured InjectionTemplate API for project-level customization.\n */\n\nimport type { Provider } from \"../../types.js\";\n\n// ── InjectionTemplate API ───────────────────────────────────────────\n\n/**\n * Structured template for injection content.\n *\n * @remarks\n * Projects use this to define what goes between CAAMP markers in\n * instruction files, rather than passing ad-hoc strings.\n *\n * @public\n */\nexport interface InjectionTemplate {\n /** References to include (e.g. `\"\\@AGENTS.md\"`, `\"\\@.cleo/project-context.json\"`). */\n references: string[];\n /** Inline content blocks (raw markdown/text). @defaultValue `undefined` */\n content?: string[];\n}\n\n/**\n * Build injection content from a structured template.\n *\n * Produces a string suitable for injection between CAAMP markers.\n * References are output as `@` lines, content blocks are appended as-is.\n *\n * @param template - Template defining references and content\n * @returns Formatted injection content string\n *\n * @remarks\n * References are output one per line. Content blocks are appended after a\n * blank separator line when references are present.\n *\n * @example\n * ```typescript\n * const content = buildInjectionContent({\n * references: [\"\\@AGENTS.md\"],\n * });\n * ```\n *\n * @public\n */\nexport function buildInjectionContent(template: InjectionTemplate): string {\n const lines: string[] = [];\n\n for (const ref of template.references) {\n lines.push(ref);\n }\n\n if (template.content && template.content.length > 0) {\n if (lines.length > 0) {\n lines.push(\"\");\n }\n lines.push(...template.content);\n }\n\n return lines.join(\"\\n\");\n}\n\n/**\n * Parse injection content back into template form.\n *\n * Lines starting with `@` are treated as references.\n * All other non-empty lines are treated as content blocks.\n *\n * @param content - Raw injection content string\n * @returns Parsed InjectionTemplate\n *\n * @remarks\n * Inverse of {@link buildInjectionContent}. Empty lines are ignored.\n *\n * @example\n * ```typescript\n * const template = parseInjectionContent(\"\\@AGENTS.md\\n\\@.cleo/config.json\");\n * ```\n *\n * @public\n */\nexport function parseInjectionContent(content: string): InjectionTemplate {\n const references: string[] = [];\n const contentLines: string[] = [];\n\n for (const line of content.split(\"\\n\")) {\n const trimmed = line.trim();\n if (!trimmed) continue;\n\n if (trimmed.startsWith(\"@\")) {\n references.push(trimmed);\n } else {\n contentLines.push(line);\n }\n }\n\n return {\n references,\n content: contentLines.length > 0 ? contentLines : undefined,\n };\n}\n\n// ── Legacy API (preserved) ──────────────────────────────────────────\n\n/**\n * Generate a standard CAAMP injection block for instruction files.\n *\n * Produces markdown content suitable for injection between CAAMP markers.\n * Optionally includes MCP server and custom content sections.\n *\n * @remarks\n * This is the legacy API preserved for backward compatibility. New code\n * should prefer {@link buildInjectionContent} with an `InjectionTemplate`.\n *\n * @param options - Optional configuration for the generated content\n * @returns Generated markdown string\n *\n * @example\n * ```typescript\n * const content = generateInjectionContent({ mcpServerName: \"filesystem\" });\n * ```\n *\n * @public\n */\nexport function generateInjectionContent(options?: {\n mcpServerName?: string;\n customContent?: string;\n}): string {\n const lines: string[] = [];\n\n lines.push(\"## CAAMP Managed Configuration\");\n lines.push(\"\");\n lines.push(\"This section is managed by [CAAMP](https://github.com/caamp/caamp).\");\n lines.push(\"Do not edit between the CAAMP markers manually.\");\n\n if (options?.mcpServerName) {\n lines.push(\"\");\n lines.push(`### MCP Server: ${options.mcpServerName}`);\n lines.push(`Configured via \\`caamp mcp install\\`.`);\n }\n\n if (options?.customContent) {\n lines.push(\"\");\n lines.push(options.customContent);\n }\n\n return lines.join(\"\\n\");\n}\n\n/**\n * Generate a skills discovery section for instruction files.\n *\n * @remarks\n * Produces a markdown list of installed skill names. Returns an empty string\n * when no skills are provided.\n *\n * @param skillNames - Array of skill names to list\n * @returns Markdown string listing installed skills\n *\n * @example\n * ```typescript\n * const section = generateSkillsSection([\"code-review\", \"testing\"]);\n * ```\n *\n * @public\n */\nexport function generateSkillsSection(skillNames: string[]): string {\n if (skillNames.length === 0) return \"\";\n\n const lines: string[] = [];\n lines.push(\"### Installed Skills\");\n lines.push(\"\");\n\n for (const name of skillNames) {\n lines.push(`- \\`${name}\\` - Available via SKILL.md`);\n }\n\n return lines.join(\"\\n\");\n}\n\n/**\n * Get the correct instruction file name for a provider.\n *\n * @remarks\n * Simple accessor that returns the `instructFile` property from the provider\n * registry entry (e.g. `\"CLAUDE.md\"`, `\"AGENTS.md\"`, `\"GEMINI.md\"`).\n *\n * @param provider - Provider registry entry\n * @returns Instruction file name\n *\n * @example\n * ```typescript\n * const fileName = getInstructFile(provider);\n * // \"CLAUDE.md\"\n * ```\n *\n * @public\n */\nexport function getInstructFile(provider: Provider): string {\n return provider.instructFile;\n}\n\n/**\n * Group providers by their instruction file name.\n *\n * Useful for determining which providers share the same instruction file\n * (e.g. multiple providers using `AGENTS.md`).\n *\n * @param providers - Array of providers to group\n * @returns Map from instruction file name to array of providers using that file\n *\n * @remarks\n * Useful for determining which providers share the same instruction file\n * to avoid duplicate file operations.\n *\n * @example\n * ```typescript\n * const groups = groupByInstructFile(getAllProviders());\n * for (const [file, providers] of groups) {\n * console.log(`${file}: ${providers.map(p => p.id).join(\", \")}`);\n * }\n * ```\n *\n * @public\n */\nexport function groupByInstructFile(providers: Provider[]): Map<string, Provider[]> {\n const groups = new Map<string, Provider[]>();\n\n for (const provider of providers) {\n const existing = groups.get(provider.instructFile) ?? [];\n existing.push(provider);\n groups.set(provider.instructFile, existing);\n }\n\n return groups;\n}\n"],"mappings":"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","names":["dirname","join","dirname","updated","join"]}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/core/platform-paths.ts","../src/core/paths/standard.ts"],"sourcesContent":["/**\n * Central OS platform path resolution using env-paths.\n *\n * Provides OS-appropriate paths for CAAMP's global directories using\n * XDG conventions on Linux, standard conventions on macOS/Windows.\n * Results are cached for the process lifetime. Env vars take precedence.\n *\n * Platform path defaults:\n * data: ~/.local/share/agents | ~/Library/Application Support/agents | %LOCALAPPDATA%\\agents\\Data\n * config: ~/.config/agents | ~/Library/Preferences/agents | %APPDATA%\\agents\\Config\n * cache: ~/.cache/agents | ~/Library/Caches/agents | %LOCALAPPDATA%\\agents\\Cache\n * log: ~/.local/state/agents | ~/Library/Logs/agents | %LOCALAPPDATA%\\agents\\Log\n * temp: /tmp/<user>/agents | /var/folders/.../agents | %TEMP%\\agents\n *\n * AGENTS_HOME env var overrides the data path for backward compatibility\n * with existing ~/.agents installations.\n */\n\nimport envPaths from 'env-paths';\nimport { arch, homedir, hostname, platform, release } from 'node:os';\nimport { isAbsolute, join, resolve } from 'node:path';\n\nconst APP_NAME = 'agents';\n\n/**\n * Normalize an AGENTS_HOME env var value to an absolute path.\n * Returns undefined when the value is absent, empty, or whitespace-only\n * (callers should fall back to the OS default in that case).\n */\nfunction resolveAgentsHomeOverride(value: string | undefined): string | undefined {\n if (value === undefined) return undefined;\n const trimmed = value.trim();\n if (trimmed.length === 0) return undefined;\n if (trimmed === '~') return homedir();\n if (trimmed.startsWith('~/')) return join(homedir(), trimmed.slice(2));\n if (isAbsolute(trimmed)) return resolve(trimmed);\n return resolve(homedir(), trimmed);\n}\n\n/**\n * OS-appropriate directory paths for CAAMP's global storage.\n *\n * @public\n */\nexport interface PlatformPaths {\n /** User data dir. Override with AGENTS_HOME env var. */\n data: string;\n /** OS config dir (XDG_CONFIG_HOME / Library/Preferences / %APPDATA%). */\n config: string;\n /** OS cache dir. */\n cache: string;\n /** OS log dir. */\n log: string;\n /** OS temp dir. */\n temp: string;\n}\n\n/**\n * Snapshot of the current system environment and resolved platform paths.\n *\n * @public\n */\nexport interface SystemInfo {\n /** Operating system platform identifier. */\n platform: NodeJS.Platform;\n /** CPU architecture (e.g. `\"x64\"`, `\"arm64\"`). */\n arch: string;\n /** OS kernel release version string. */\n release: string;\n /** Machine hostname. */\n hostname: string;\n /** Node.js version string (e.g. `\"v20.11.0\"`). */\n nodeVersion: string;\n /** Resolved platform directory paths. */\n paths: PlatformPaths;\n}\n\nlet _paths: PlatformPaths | null = null;\nlet _sysInfo: SystemInfo | null = null;\nlet _lastAgentsHome: string | undefined = undefined;\n\n/**\n * Get OS-appropriate paths for CAAMP's global directories.\n *\n * @remarks\n * Cached after first call. The `AGENTS_HOME` env var overrides the data path\n * for backward compatibility with existing `~/.agents` installations. The\n * cache auto-invalidates when `AGENTS_HOME` changes (supports test isolation).\n *\n * @returns Resolved platform paths\n *\n * @example\n * ```typescript\n * const paths = getPlatformPaths();\n * console.log(paths.data); // e.g. \"/home/user/.local/share/agents\"\n * ```\n *\n * @public\n */\nexport function getPlatformPaths(): PlatformPaths {\n const currentAgentsHome = process.env['AGENTS_HOME'];\n\n // Invalidate if AGENTS_HOME changed since last cache build\n if (_paths && currentAgentsHome !== _lastAgentsHome) {\n _paths = null;\n _sysInfo = null;\n }\n\n if (_paths) return _paths;\n\n const ep = envPaths(APP_NAME, { suffix: '' });\n _lastAgentsHome = currentAgentsHome;\n\n _paths = {\n data: resolveAgentsHomeOverride(currentAgentsHome) ?? ep.data,\n config: ep.config,\n cache: ep.cache,\n log: ep.log,\n temp: ep.temp,\n };\n\n return _paths;\n}\n\n/**\n * Get a cached system information snapshot.\n *\n * @remarks\n * Captured once and reused for the process lifetime. Includes platform,\n * architecture, hostname, Node version, and resolved paths.\n *\n * @returns Cached system info object\n *\n * @example\n * ```typescript\n * const info = getSystemInfo();\n * console.log(`Running on ${info.platform}/${info.arch}`);\n * ```\n *\n * @public\n */\nexport function getSystemInfo(): SystemInfo {\n if (_sysInfo) return _sysInfo;\n\n const paths = getPlatformPaths();\n\n _sysInfo = {\n platform: platform(),\n arch: arch(),\n release: release(),\n hostname: hostname(),\n nodeVersion: process.version,\n paths,\n };\n\n return _sysInfo;\n}\n\n/**\n * Invalidate the path and system info caches.\n * Use in tests after mutating AGENTS_HOME env var.\n * @internal\n */\nexport function _resetPlatformPathsCache(): void {\n _paths = null;\n _sysInfo = null;\n _lastAgentsHome = undefined;\n}\n","import { existsSync } from \"node:fs\";\nimport { homedir } from \"node:os\";\nimport { dirname, join } from \"node:path\";\nimport type { Provider } from \"../../types.js\";\nimport { getPlatformPaths } from \"../platform-paths.js\";\n\n/**\n * Scope for path resolution, either global (user home) or project-local.\n *\n * @remarks\n * Global scope resolves paths under the user's home directory (e.g., `~/.agents/`).\n * Project scope resolves paths relative to the project root (e.g., `<project>/.agents/`).\n *\n * @public\n */\nexport type PathScope = \"project\" | \"global\";\n\n/**\n * Platform-specific directory locations for agent configuration.\n *\n * @remarks\n * Provides resolved paths for the current operating system, accounting for\n * platform differences in config directory locations (XDG on Linux, Library on macOS,\n * AppData on Windows).\n *\n * @public\n */\nexport interface PlatformLocations {\n /** The user's home directory path. */\n home: string;\n /** The platform-specific configuration directory. */\n config: string;\n /** The VS Code user settings directory. */\n vscodeConfig: string;\n /** The Zed editor configuration directory. */\n zedConfig: string;\n /** The Claude Desktop application configuration directory. */\n claudeDesktopConfig: string;\n /** List of application directories (macOS only). */\n applications: string[];\n}\n\n/**\n * Resolves platform-specific directory locations for the current OS.\n *\n * @remarks\n * Detects the current platform and returns appropriate paths for configuration,\n * editor settings, and application directories. Uses `XDG_CONFIG_HOME` on Linux\n * and macOS when available, falls back to conventional defaults. On Windows,\n * uses `APPDATA` or defaults to `~/AppData/Roaming`.\n *\n * @returns Platform-specific directory locations\n *\n * @example\n * ```typescript\n * const locations = getPlatformLocations();\n * console.log(locations.config); // e.g., \"/home/user/.config\"\n * ```\n *\n * @public\n */\nexport function getPlatformLocations(): PlatformLocations {\n const home = homedir();\n const platform = process.platform;\n\n if (platform === \"win32\") {\n const appData = process.env[\"APPDATA\"] ?? join(home, \"AppData\", \"Roaming\");\n return {\n home,\n config: appData,\n vscodeConfig: join(appData, \"Code\", \"User\"),\n zedConfig: join(appData, \"Zed\"),\n claudeDesktopConfig: join(appData, \"Claude\"),\n applications: [],\n };\n }\n\n if (platform === \"darwin\") {\n const config = process.env[\"XDG_CONFIG_HOME\"] ?? join(home, \".config\");\n return {\n home,\n config,\n vscodeConfig: join(home, \"Library\", \"Application Support\", \"Code\", \"User\"),\n zedConfig: join(home, \"Library\", \"Application Support\", \"Zed\"),\n claudeDesktopConfig: join(home, \"Library\", \"Application Support\", \"Claude\"),\n applications: [\"/Applications\", join(home, \"Applications\")],\n };\n }\n\n const config = process.env[\"XDG_CONFIG_HOME\"] ?? join(home, \".config\");\n return {\n home,\n config,\n vscodeConfig: join(config, \"Code\", \"User\"),\n zedConfig: join(config, \"zed\"),\n claudeDesktopConfig: join(config, \"Claude\"),\n applications: [],\n };\n}\n\n/**\n * Returns the global agents home directory path.\n *\n * @remarks\n * Delegates to the platform paths module to resolve the data directory\n * for the current operating system. This is the root for global agent\n * configuration such as canonical skills and lock files.\n *\n * @returns The absolute path to the global agents home directory\n *\n * @example\n * ```typescript\n * const home = getAgentsHome();\n * // e.g., \"/home/user/.local/share/caamp\"\n * ```\n *\n * @public\n */\nexport function getAgentsHome(): string {\n return getPlatformPaths().data;\n}\n\n/**\n * Returns the project-local `.agents` directory path.\n *\n * @remarks\n * Joins the project root with `.agents` to produce the conventional\n * project-scoped agent configuration directory.\n *\n * @param projectRoot - The project root directory, defaults to `process.cwd()`\n * @returns The absolute path to the project's `.agents` directory\n *\n * @example\n * ```typescript\n * const dir = getProjectAgentsDir(\"/home/user/my-project\");\n * // returns \"/home/user/my-project/.agents\"\n * ```\n *\n * @public\n */\nexport function getProjectAgentsDir(projectRoot = process.cwd()): string {\n return join(projectRoot, \".agents\");\n}\n\n/**\n * Resolves a relative path against a project directory.\n *\n * @remarks\n * A simple path join utility that combines the project directory with\n * the given relative path to produce an absolute path.\n *\n * @param relativePath - The relative path to resolve\n * @param projectDir - The project root directory, defaults to `process.cwd()`\n * @returns The resolved absolute path\n *\n * @example\n * ```typescript\n * const path = resolveProjectPath(\".agents/config.toml\", \"/home/user/project\");\n * // returns \"/home/user/project/.agents/config.toml\"\n * ```\n *\n * @public\n */\nexport function resolveProjectPath(relativePath: string, projectDir = process.cwd()): string {\n return join(projectDir, relativePath);\n}\n\n/**\n * Returns the canonical skills storage directory path.\n *\n * @remarks\n * Skills are stored once in this canonical directory and symlinked into\n * provider-specific locations. This is the single source of truth for\n * installed skill files.\n *\n * @returns The absolute path to the canonical skills directory\n *\n * @example\n * ```typescript\n * const dir = getCanonicalSkillsDir();\n * // e.g., \"/home/user/.local/share/caamp/skills\"\n * ```\n *\n * @public\n */\nexport function getCanonicalSkillsDir(): string {\n return join(getAgentsHome(), \"skills\");\n}\n\n/**\n * Returns the path to the CAAMP lock file.\n *\n * @remarks\n * The lock file tracks installed skills and their versions to enable\n * deterministic reinstallation and conflict detection.\n *\n * @returns The absolute path to the `.caamp-lock.json` file\n *\n * @example\n * ```typescript\n * const lockPath = getLockFilePath();\n * // e.g., \"/home/user/.local/share/caamp/.caamp-lock.json\"\n * ```\n *\n * @public\n */\nexport function getLockFilePath(): string {\n return join(getAgentsHome(), \".caamp-lock.json\");\n}\n\n// ── .agents/ Standard Directory Structure ────────────────────────────\n\n/**\n * Gets the MCP directory within the `.agents/` standard structure.\n *\n * @remarks\n * Resolves the MCP configuration directory based on scope. Global scope\n * points to `~/.agents/mcp/`, while project scope points to\n * `<project>/.agents/mcp/`.\n *\n * @param scope - `\"global\"` for `~/.agents/mcp/`, `\"project\"` for `<project>/.agents/mcp/`\n * @param projectDir - Project root (defaults to `process.cwd()`)\n * @returns The absolute path to the MCP directory\n *\n * @example\n * ```typescript\n * const globalMcp = getAgentsMcpDir(\"global\");\n * const projectMcp = getAgentsMcpDir(\"project\", \"/home/user/project\");\n * ```\n *\n * @public\n */\nexport function getAgentsMcpDir(scope: PathScope = \"global\", projectDir?: string): string {\n if (scope === \"global\") return join(getAgentsHome(), \"mcp\");\n return join(projectDir ?? process.cwd(), \".agents\", \"mcp\");\n}\n\n/**\n * Gets the MCP servers.json path within the `.agents/` standard structure.\n *\n * @remarks\n * Per the `.agents/` standard (Section 9), this is the canonical MCP\n * server registry that should be checked before legacy per-provider configs.\n *\n * @param scope - `\"global\"` for `~/.agents/mcp/servers.json`, `\"project\"` for `<project>/.agents/mcp/servers.json`\n * @param projectDir - Project root (defaults to `process.cwd()`)\n * @returns The absolute path to the `servers.json` file\n *\n * @example\n * ```typescript\n * const serversPath = getAgentsMcpServersPath(\"global\");\n * // e.g., \"/home/user/.agents/mcp/servers.json\"\n * ```\n *\n * @public\n */\nexport function getAgentsMcpServersPath(scope: PathScope = \"global\", projectDir?: string): string {\n return join(getAgentsMcpDir(scope, projectDir), \"servers.json\");\n}\n\n/**\n * Gets the primary AGENTS.md instruction file path within `.agents/`.\n *\n * @remarks\n * Returns the path to the AGENTS.md file for the given scope. This is the\n * standard instruction file that agents read for project or global guidance.\n *\n * @param scope - `\"global\"` for `~/.agents/AGENTS.md`, `\"project\"` for `<project>/.agents/AGENTS.md`\n * @param projectDir - Project root (defaults to `process.cwd()`)\n * @returns The absolute path to the AGENTS.md file\n *\n * @example\n * ```typescript\n * const agentsFile = getAgentsInstructFile(\"project\", \"/home/user/project\");\n * // returns \"/home/user/project/.agents/AGENTS.md\"\n * ```\n *\n * @public\n */\nexport function getAgentsInstructFile(scope: PathScope = \"global\", projectDir?: string): string {\n if (scope === \"global\") return join(getAgentsHome(), \"AGENTS.md\");\n return join(projectDir ?? process.cwd(), \".agents\", \"AGENTS.md\");\n}\n\n/**\n * Gets the config.toml path within the `.agents/` standard structure.\n *\n * @remarks\n * Returns the path to the TOML configuration file for agent settings.\n * Global scope points to `~/.agents/config.toml`, project scope points\n * to `<project>/.agents/config.toml`.\n *\n * @param scope - `\"global\"` for `~/.agents/config.toml`, `\"project\"` for `<project>/.agents/config.toml`\n * @param projectDir - Project root (defaults to `process.cwd()`)\n * @returns The absolute path to the config.toml file\n *\n * @example\n * ```typescript\n * const configPath = getAgentsConfigPath(\"global\");\n * // e.g., \"/home/user/.agents/config.toml\"\n * ```\n *\n * @public\n */\nexport function getAgentsConfigPath(scope: PathScope = \"global\", projectDir?: string): string {\n if (scope === \"global\") return join(getAgentsHome(), \"config.toml\");\n return join(projectDir ?? process.cwd(), \".agents\", \"config.toml\");\n}\n\n/**\n * Gets the wiki directory within the `.agents/` standard structure.\n *\n * @remarks\n * The wiki directory stores markdown documentation files that agents\n * can reference for project or global knowledge.\n *\n * @param scope - `\"global\"` or `\"project\"`\n * @param projectDir - Project root (defaults to `process.cwd()`)\n * @returns The absolute path to the wiki directory\n *\n * @example\n * ```typescript\n * const wikiDir = getAgentsWikiDir(\"project\", \"/home/user/project\");\n * // returns \"/home/user/project/.agents/wiki\"\n * ```\n *\n * @public\n */\nexport function getAgentsWikiDir(scope: PathScope = \"global\", projectDir?: string): string {\n if (scope === \"global\") return join(getAgentsHome(), \"wiki\");\n return join(projectDir ?? process.cwd(), \".agents\", \"wiki\");\n}\n\n/**\n * Gets the spec directory within the `.agents/` standard structure.\n *\n * @remarks\n * The spec directory stores specification files used by agents for\n * project architecture and design reference.\n *\n * @param scope - `\"global\"` or `\"project\"`\n * @param projectDir - Project root (defaults to `process.cwd()`)\n * @returns The absolute path to the spec directory\n *\n * @example\n * ```typescript\n * const specDir = getAgentsSpecDir(\"global\");\n * // e.g., \"/home/user/.agents/spec\"\n * ```\n *\n * @public\n */\nexport function getAgentsSpecDir(scope: PathScope = \"global\", projectDir?: string): string {\n if (scope === \"global\") return join(getAgentsHome(), \"spec\");\n return join(projectDir ?? process.cwd(), \".agents\", \"spec\");\n}\n\n/**\n * Gets the links directory within the `.agents/` standard structure.\n *\n * @remarks\n * The links directory stores symlinks or references to external resources\n * that agents can follow for additional context.\n *\n * @param scope - `\"global\"` or `\"project\"`\n * @param projectDir - Project root (defaults to `process.cwd()`)\n * @returns The absolute path to the links directory\n *\n * @example\n * ```typescript\n * const linksDir = getAgentsLinksDir(\"project\", \"/home/user/project\");\n * // returns \"/home/user/project/.agents/links\"\n * ```\n *\n * @public\n */\nexport function getAgentsLinksDir(scope: PathScope = \"global\", projectDir?: string): string {\n if (scope === \"global\") return join(getAgentsHome(), \"links\");\n return join(projectDir ?? process.cwd(), \".agents\", \"links\");\n}\n\n/**\n * Resolves a registry template path by substituting platform variables.\n *\n * @remarks\n * Replaces template variables like `$HOME`, `$CONFIG`, `$VSCODE_CONFIG`,\n * `$ZED_CONFIG`, `$CLAUDE_DESKTOP_CONFIG`, and `$AGENTS_HOME` with their\n * actual platform-specific values. Used to resolve paths from the provider\n * registry JSON.\n *\n * @param template - The template string containing `$VARIABLE` placeholders\n * @returns The resolved absolute path with all variables expanded\n *\n * @example\n * ```typescript\n * const path = resolveRegistryTemplatePath(\"$HOME/.config/claude/settings.json\");\n * // e.g., \"/home/user/.config/claude/settings.json\"\n * ```\n *\n * @public\n */\nexport function resolveRegistryTemplatePath(template: string): string {\n const locations = getPlatformLocations();\n return template\n .replace(/\\$HOME/g, locations.home)\n .replace(/\\$CONFIG/g, locations.config)\n .replace(/\\$VSCODE_CONFIG/g, locations.vscodeConfig)\n .replace(/\\$ZED_CONFIG/g, locations.zedConfig)\n .replace(/\\$CLAUDE_DESKTOP_CONFIG/g, locations.claudeDesktopConfig)\n .replace(/\\$AGENTS_HOME/g, getAgentsHome());\n}\n\n/**\n * Resolves the configuration file path for a provider at the given scope.\n *\n * @remarks\n * For global scope, returns the provider's `configPathGlobal` directly.\n * For project scope, joins the project directory with the provider's\n * `configPathProject`. Returns null if the provider has no project-scoped\n * config path defined.\n *\n * @param provider - The provider whose config path to resolve\n * @param scope - Whether to resolve global or project config path\n * @param projectDir - The project root directory, defaults to `process.cwd()`\n * @returns The resolved config file path, or null if unavailable for the given scope\n *\n * @example\n * ```typescript\n * const configPath = resolveProviderConfigPath(provider, \"project\", \"/home/user/project\");\n * if (configPath) {\n * console.log(\"Config at:\", configPath);\n * }\n * ```\n *\n * @public\n */\nexport function resolveProviderConfigPath(\n provider: Provider,\n scope: PathScope,\n projectDir = process.cwd(),\n): string | null {\n if (scope === \"global\") {\n return provider.configPathGlobal;\n }\n if (!provider.configPathProject) {\n return null;\n }\n return resolveProjectPath(provider.configPathProject, projectDir);\n}\n\n/**\n * Determines the preferred configuration scope for a provider.\n *\n * @remarks\n * If the global flag is explicitly set, always returns `\"global\"`. Otherwise,\n * returns `\"project\"` if the provider supports project-scoped configuration,\n * or `\"global\"` as a fallback.\n *\n * @param provider - The provider to determine scope for\n * @param useGlobalFlag - Optional flag to force global scope\n * @returns The preferred path scope for configuration\n *\n * @example\n * ```typescript\n * const scope = resolvePreferredConfigScope(provider, false);\n * // returns \"project\" if provider has configPathProject, otherwise \"global\"\n * ```\n *\n * @public\n */\nexport function resolvePreferredConfigScope(provider: Provider, useGlobalFlag?: boolean): PathScope {\n if (useGlobalFlag) {\n return \"global\";\n }\n return provider.configPathProject ? \"project\" : \"global\";\n}\n\n/**\n * Resolves the skills directory path for a provider at the given scope.\n *\n * @remarks\n * For global scope, returns the provider's `pathSkills` directly.\n * For project scope, joins the project directory with the provider's\n * `pathProjectSkills` relative path.\n *\n * @param provider - The provider whose skills directory to resolve\n * @param scope - Whether to resolve global or project skills path\n * @param projectDir - The project root directory, defaults to `process.cwd()`\n * @returns The resolved skills directory path\n *\n * @example\n * ```typescript\n * const skillsDir = resolveProviderSkillsDir(provider, \"global\");\n * // e.g., \"/home/user/.claude/skills\"\n * ```\n *\n * @public\n */\nexport function resolveProviderSkillsDir(\n provider: Provider,\n scope: PathScope,\n projectDir = process.cwd(),\n): string {\n if (scope === \"global\") {\n return provider.pathSkills;\n }\n return resolveProjectPath(provider.pathProjectSkills, projectDir);\n}\n\n/**\n * Gets all target directories for skill installation based on provider precedence.\n *\n * @remarks\n * Resolves one or more skill directories based on the provider's skills\n * precedence setting. The precedence determines whether vendor-only, agents-canonical,\n * agents-first, agents-supported, or vendor-global-agents-project strategies are used.\n * Falls back to the vendor path when the agents path is not configured.\n *\n * @param provider - Provider to resolve paths for\n * @param scope - Whether to resolve global or project paths\n * @param projectDir - Project directory for project-scope resolution\n * @returns Array of target directories for symlink creation\n *\n * @example\n * ```typescript\n * const dirs = resolveProviderSkillsDirs(provider, \"project\", \"/home/user/project\");\n * for (const dir of dirs) {\n * console.log(\"Install skill to:\", dir);\n * }\n * ```\n *\n * @public\n */\nexport function resolveProviderSkillsDirs(\n provider: Provider,\n scope: PathScope,\n projectDir = process.cwd(),\n): string[] {\n const vendorPath = resolveProviderSkillsDir(provider, scope, projectDir);\n const precedence = provider.capabilities?.skills?.precedence ?? \"vendor-only\";\n\n const resolveAgentsPath = (): string | null => {\n if (scope === \"global\") {\n return provider.capabilities?.skills?.agentsGlobalPath ?? null;\n }\n const projectRelative = provider.capabilities?.skills?.agentsProjectPath ?? null;\n return projectRelative ? join(projectDir, projectRelative) : null;\n };\n\n switch (precedence) {\n case \"vendor-only\":\n return [vendorPath];\n\n case \"agents-canonical\": {\n const agentsPath = resolveAgentsPath();\n return agentsPath ? [agentsPath] : [vendorPath];\n }\n\n case \"agents-first\": {\n const agentsPath = resolveAgentsPath();\n return agentsPath ? [agentsPath, vendorPath] : [vendorPath];\n }\n\n case \"agents-supported\": {\n const agentsPath = resolveAgentsPath();\n return agentsPath ? [vendorPath, agentsPath] : [vendorPath];\n }\n\n case \"vendor-global-agents-project\": {\n if (scope === \"global\") {\n return [vendorPath];\n }\n const agentsPath = resolveAgentsPath();\n return agentsPath ? [agentsPath, vendorPath] : [vendorPath];\n }\n\n default:\n return [vendorPath];\n }\n}\n\n/**\n * Resolves a provider's project-level path against a project directory.\n *\n * @remarks\n * Joins the project directory with the provider's `pathProject` relative path\n * to produce an absolute path for project-scoped provider configuration.\n *\n * @param provider - The provider whose project path to resolve\n * @param projectDir - The project root directory, defaults to `process.cwd()`\n * @returns The resolved absolute path for the provider's project directory\n *\n * @example\n * ```typescript\n * const projectPath = resolveProviderProjectPath(provider, \"/home/user/project\");\n * // e.g., \"/home/user/project/.claude\"\n * ```\n *\n * @public\n */\nexport function resolveProviderProjectPath(provider: Provider, projectDir = process.cwd()): string {\n return resolveProjectPath(provider.pathProject, projectDir);\n}\n\n/**\n * Locates the providers registry.json file by searching up from a start directory.\n *\n * @remarks\n * First checks common relative locations (3 levels up and 1 level up),\n * then walks up to 8 parent directories looking for `providers/registry.json`.\n * Throws if the registry file cannot be found.\n *\n * @param startDir - The directory to start searching from\n * @returns The absolute path to the found `providers/registry.json` file\n * @throws Error if `providers/registry.json` cannot be found within 8 parent levels\n *\n * @example\n * ```typescript\n * const registryPath = resolveProvidersRegistryPath(__dirname);\n * // e.g., \"/home/user/caamp/providers/registry.json\"\n * ```\n *\n * @public\n */\nexport function resolveProvidersRegistryPath(startDir: string): string {\n const candidates = [\n join(startDir, \"..\", \"..\", \"..\", \"providers\", \"registry.json\"),\n join(startDir, \"..\", \"providers\", \"registry.json\"),\n ];\n\n for (const candidate of candidates) {\n if (existsSync(candidate)) {\n return candidate;\n }\n }\n\n let current = startDir;\n for (let i = 0; i < 8; i += 1) {\n const candidate = join(current, \"providers\", \"registry.json\");\n if (existsSync(candidate)) {\n return candidate;\n }\n current = dirname(current);\n }\n\n throw new Error(`Cannot find providers/registry.json (searched from ${startDir})`);\n}\n\n/**\n * Normalizes a skill sub-path by cleaning separators and removing SKILL.md suffix.\n *\n * @remarks\n * Converts backslashes to forward slashes, strips leading slashes and\n * trailing `/SKILL.md`, and trims whitespace. Returns undefined for\n * empty or falsy inputs.\n *\n * @param path - The raw skill sub-path to normalize\n * @returns The normalized path, or undefined if the input is empty or falsy\n *\n * @example\n * ```typescript\n * const normalized = normalizeSkillSubPath(\"skills/my-skill/SKILL.md\");\n * // returns \"skills/my-skill\"\n * ```\n *\n * @public\n */\nexport function normalizeSkillSubPath(path: string | undefined): string | undefined {\n if (!path) return undefined;\n const normalized = path.replace(/\\\\/g, \"/\").replace(/^\\/+/, \"\").replace(/\\/SKILL\\.md$/i, \"\").trim();\n return normalized.length > 0 ? normalized : undefined;\n}\n\n/**\n * Builds a list of candidate sub-paths for skill file resolution.\n *\n * @remarks\n * Normalizes both the marketplace and parsed paths, then generates additional\n * candidates by prepending known prefixes (`.agents`, `.claude`) to paths\n * starting with `skills/`. Returns a deduplicated list of candidates, with\n * an undefined entry as fallback if no candidates are found.\n *\n * @param marketplacePath - The sub-path from the marketplace listing\n * @param parsedPath - The sub-path parsed from the source URL\n * @returns A deduplicated array of candidate sub-paths\n *\n * @example\n * ```typescript\n * const candidates = buildSkillSubPathCandidates(\"skills/my-skill\", undefined);\n * // returns [\"skills/my-skill\", \".agents/skills/my-skill\", \".claude/skills/my-skill\"]\n * ```\n *\n * @public\n */\nexport function buildSkillSubPathCandidates(\n marketplacePath: string | undefined,\n parsedPath: string | undefined,\n): (string | undefined)[] {\n const candidates: (string | undefined)[] = [];\n const base = normalizeSkillSubPath(marketplacePath);\n const parsed = normalizeSkillSubPath(parsedPath);\n\n if (base) candidates.push(base);\n if (parsed) candidates.push(parsed);\n\n const knownPrefixes = [\".agents\", \".claude\"];\n for (const value of [base, parsed]) {\n if (!value || !value.startsWith(\"skills/\")) continue;\n for (const prefix of knownPrefixes) {\n candidates.push(`${prefix}/${value}`);\n }\n }\n\n if (candidates.length === 0) {\n candidates.push(undefined);\n }\n\n return Array.from(new Set(candidates));\n}\n"],"mappings":"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","names":["homedir","join","homedir","platform","join","config"]}
|
|
@@ -2,7 +2,7 @@ import {
|
|
|
2
2
|
getAllProviders,
|
|
3
3
|
groupByInstructFile,
|
|
4
4
|
injectAll
|
|
5
|
-
} from "./chunk-
|
|
5
|
+
} from "./chunk-O7IVK5JY.js";
|
|
6
6
|
import {
|
|
7
7
|
__export,
|
|
8
8
|
getAgentsConfigPath,
|
|
@@ -15,7 +15,7 @@ import {
|
|
|
15
15
|
resolveProviderConfigPath,
|
|
16
16
|
resolveProviderProjectPath,
|
|
17
17
|
resolveProviderSkillsDirs
|
|
18
|
-
} from "./chunk-
|
|
18
|
+
} from "./chunk-TI6WOJDG.js";
|
|
19
19
|
|
|
20
20
|
// src/core/formats/utils.ts
|
|
21
21
|
function deepMerge(target, source) {
|
|
@@ -1754,8 +1754,11 @@ function isMarketplaceScoped(input) {
|
|
|
1754
1754
|
// src/core/network/fetch.ts
|
|
1755
1755
|
var DEFAULT_FETCH_TIMEOUT_MS = 1e4;
|
|
1756
1756
|
var NetworkError = class extends Error {
|
|
1757
|
+
/** Classification of the failure. */
|
|
1757
1758
|
kind;
|
|
1759
|
+
/** URL that was being fetched. */
|
|
1758
1760
|
url;
|
|
1761
|
+
/** HTTP status code (only present for `"http"` kind). */
|
|
1759
1762
|
status;
|
|
1760
1763
|
constructor(message, kind, url, status) {
|
|
1761
1764
|
super(message);
|
|
@@ -1825,7 +1828,15 @@ function toResult(skill) {
|
|
|
1825
1828
|
};
|
|
1826
1829
|
}
|
|
1827
1830
|
var SkillsMPAdapter = class {
|
|
1831
|
+
/** The marketplace identifier used in search results. */
|
|
1828
1832
|
name = "agentskills.in";
|
|
1833
|
+
/**
|
|
1834
|
+
* Search for skills by query string.
|
|
1835
|
+
*
|
|
1836
|
+
* @param query - Search query to match against skill names and descriptions.
|
|
1837
|
+
* @param limit - Maximum number of results to return.
|
|
1838
|
+
* @returns Array of marketplace results sorted by stars.
|
|
1839
|
+
*/
|
|
1829
1840
|
async search(query, limit = 20) {
|
|
1830
1841
|
const params = new URLSearchParams({
|
|
1831
1842
|
search: query,
|
|
@@ -1837,6 +1848,12 @@ var SkillsMPAdapter = class {
|
|
|
1837
1848
|
const data = await response.json();
|
|
1838
1849
|
return data.skills.map(toResult);
|
|
1839
1850
|
}
|
|
1851
|
+
/**
|
|
1852
|
+
* Look up a specific skill by its scoped name.
|
|
1853
|
+
*
|
|
1854
|
+
* @param scopedName - The scoped skill name (e.g. `"@author/skill-name"`).
|
|
1855
|
+
* @returns The matching marketplace result, or `null` if not found.
|
|
1856
|
+
*/
|
|
1840
1857
|
async getSkill(scopedName) {
|
|
1841
1858
|
const parts = parseScopedName(scopedName);
|
|
1842
1859
|
const searchTerms = parts ? [parts.name, `${parts.author} ${parts.name}`, scopedName] : [scopedName];
|
|
@@ -1879,7 +1896,15 @@ function toResult2(skill) {
|
|
|
1879
1896
|
};
|
|
1880
1897
|
}
|
|
1881
1898
|
var SkillsShAdapter = class {
|
|
1899
|
+
/** The marketplace identifier used in search results. */
|
|
1882
1900
|
name = "skills.sh";
|
|
1901
|
+
/**
|
|
1902
|
+
* Search for skills by query string.
|
|
1903
|
+
*
|
|
1904
|
+
* @param query - Search query to match against skill names.
|
|
1905
|
+
* @param limit - Maximum number of results to return.
|
|
1906
|
+
* @returns Array of marketplace results.
|
|
1907
|
+
*/
|
|
1883
1908
|
async search(query, limit = 20) {
|
|
1884
1909
|
const params = new URLSearchParams({
|
|
1885
1910
|
q: query,
|
|
@@ -1890,6 +1915,12 @@ var SkillsShAdapter = class {
|
|
|
1890
1915
|
const data = await response.json();
|
|
1891
1916
|
return data.results.map(toResult2);
|
|
1892
1917
|
}
|
|
1918
|
+
/**
|
|
1919
|
+
* Look up a specific skill by its scoped name.
|
|
1920
|
+
*
|
|
1921
|
+
* @param scopedName - The scoped skill name (e.g. `"@author/skill-name"`).
|
|
1922
|
+
* @returns The matching marketplace result, or `null` if not found.
|
|
1923
|
+
*/
|
|
1893
1924
|
async getSkill(scopedName) {
|
|
1894
1925
|
const results = await this.search(scopedName, 5);
|
|
1895
1926
|
return results.find((r) => r.scopedName === scopedName) ?? null;
|
|
@@ -1898,6 +1929,7 @@ var SkillsShAdapter = class {
|
|
|
1898
1929
|
|
|
1899
1930
|
// src/core/marketplace/client.ts
|
|
1900
1931
|
var MarketplaceUnavailableError = class extends Error {
|
|
1932
|
+
/** Per-adapter failure messages. */
|
|
1901
1933
|
details;
|
|
1902
1934
|
constructor(message, details) {
|
|
1903
1935
|
super(message);
|
|
@@ -1906,6 +1938,7 @@ var MarketplaceUnavailableError = class extends Error {
|
|
|
1906
1938
|
}
|
|
1907
1939
|
};
|
|
1908
1940
|
var MarketplaceClient = class {
|
|
1941
|
+
/** Configured marketplace adapters. */
|
|
1909
1942
|
adapters;
|
|
1910
1943
|
/**
|
|
1911
1944
|
* Create a new marketplace client.
|
|
@@ -3572,4 +3605,4 @@ export {
|
|
|
3572
3605
|
toSarif,
|
|
3573
3606
|
validateSkill
|
|
3574
3607
|
};
|
|
3575
|
-
//# sourceMappingURL=chunk-
|
|
3608
|
+
//# sourceMappingURL=chunk-ZF4W3K5H.js.map
|