@canonical/pragma-cli 0.18.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +377 -0
- package/package.json +78 -0
- package/src/bin.test.ts +52 -0
- package/src/bin.ts +19 -0
- package/src/compile-validation.runner.ts +137 -0
- package/src/compile-validation.ts +192 -0
- package/src/completions/computeSocketPath.test.ts +26 -0
- package/src/completions/computeSocketPath.ts +16 -0
- package/src/completions/constants.ts +18 -0
- package/src/completions/handleQuery.test.ts +93 -0
- package/src/completions/handleQuery.ts +30 -0
- package/src/completions/index.ts +14 -0
- package/src/completions/queryCompletions.test.ts +100 -0
- package/src/completions/queryCompletions.ts +61 -0
- package/src/completions/querySocket.test.ts +70 -0
- package/src/completions/querySocket.ts +44 -0
- package/src/completions/startCompletionsServer.test.ts +74 -0
- package/src/completions/startCompletionsServer.ts +75 -0
- package/src/completions/startCompletionsServer.unit.test.ts +131 -0
- package/src/completions/waitForSocket.test.ts +27 -0
- package/src/completions/waitForSocket.ts +27 -0
- package/src/config/configExists.ts +14 -0
- package/src/config/index.ts +6 -0
- package/src/config/readConfig.test.ts +152 -0
- package/src/config/readConfig.ts +64 -0
- package/src/config/resolveConfigPath.test.ts +20 -0
- package/src/config/resolveConfigPath.ts +11 -0
- package/src/config/types.ts +26 -0
- package/src/config/writeConfig.ts +49 -0
- package/src/constants.ts +78 -0
- package/src/domains/block/blockConfig.ts +53 -0
- package/src/domains/block/commands/index.ts +4 -0
- package/src/domains/block/commands/list.test.ts +111 -0
- package/src/domains/block/commands/list.ts +69 -0
- package/src/domains/block/commands/lookup.test.ts +187 -0
- package/src/domains/block/commands/lookup.ts +199 -0
- package/src/domains/block/formatters/index.ts +4 -0
- package/src/domains/block/formatters/list.test.ts +107 -0
- package/src/domains/block/formatters/list.ts +64 -0
- package/src/domains/block/formatters/lookup.test.ts +317 -0
- package/src/domains/block/formatters/lookup.ts +354 -0
- package/src/domains/block/formatters/types.ts +16 -0
- package/src/domains/block/helpers/index.ts +4 -0
- package/src/domains/block/helpers/resolveAspects.test.ts +64 -0
- package/src/domains/block/helpers/resolveAspects.ts +35 -0
- package/src/domains/block/index.ts +25 -0
- package/src/domains/block/mcp/index.ts +2 -0
- package/src/domains/block/mcp/specs.ts +199 -0
- package/src/domains/block/operations/index.ts +4 -0
- package/src/domains/block/operations/list.test.ts +80 -0
- package/src/domains/block/operations/list.ts +72 -0
- package/src/domains/block/operations/lookup.test.ts +108 -0
- package/src/domains/block/operations/lookup.ts +359 -0
- package/src/domains/block/orchestration/blockEmptyError.ts +19 -0
- package/src/domains/block/orchestration/buildBlockFilters.ts +12 -0
- package/src/domains/block/orchestration/enrichBlocks.ts +37 -0
- package/src/domains/block/orchestration/index.ts +5 -0
- package/src/domains/block/orchestration/resolveBlockList.ts +39 -0
- package/src/domains/block/orchestration/resolveBlockLookup.ts +21 -0
- package/src/domains/block/types.ts +30 -0
- package/src/domains/config/commands/channel.test.ts +101 -0
- package/src/domains/config/commands/channel.ts +80 -0
- package/src/domains/config/commands/index.ts +4 -0
- package/src/domains/config/commands/show.test.ts +61 -0
- package/src/domains/config/commands/show.ts +51 -0
- package/src/domains/config/commands/tier.test.ts +84 -0
- package/src/domains/config/commands/tier.ts +78 -0
- package/src/domains/config/formatters/channel.test.ts +56 -0
- package/src/domains/config/formatters/channel.ts +34 -0
- package/src/domains/config/formatters/index.ts +4 -0
- package/src/domains/config/formatters/show.test.ts +126 -0
- package/src/domains/config/formatters/show.ts +66 -0
- package/src/domains/config/formatters/tier.test.ts +70 -0
- package/src/domains/config/formatters/tier.ts +40 -0
- package/src/domains/config/index.ts +23 -0
- package/src/domains/config/mcp/index.ts +2 -0
- package/src/domains/config/mcp/specs.ts +108 -0
- package/src/domains/config/operations/index.ts +5 -0
- package/src/domains/config/operations/show.test.ts +60 -0
- package/src/domains/config/operations/show.ts +42 -0
- package/src/domains/config/operations/types.ts +13 -0
- package/src/domains/config/operations/validateChannel.test.ts +36 -0
- package/src/domains/config/operations/validateChannel.ts +24 -0
- package/src/domains/config/operations/validateTier.test.ts +56 -0
- package/src/domains/config/operations/validateTier.ts +35 -0
- package/src/domains/create/commands/component.test.ts +166 -0
- package/src/domains/create/commands/component.ts +129 -0
- package/src/domains/create/commands/index.ts +3 -0
- package/src/domains/create/commands/package.test.ts +35 -0
- package/src/domains/create/commands/package.ts +21 -0
- package/src/domains/create/index.ts +15 -0
- package/src/domains/create/mcp/index.ts +2 -0
- package/src/domains/create/mcp/specs.ts +174 -0
- package/src/domains/doctor/commands/doctor.test.ts +75 -0
- package/src/domains/doctor/commands/doctor.ts +43 -0
- package/src/domains/doctor/commands/index.ts +2 -0
- package/src/domains/doctor/formatters/doctor.test.ts +117 -0
- package/src/domains/doctor/formatters/doctor.ts +84 -0
- package/src/domains/doctor/formatters/index.ts +2 -0
- package/src/domains/doctor/index.ts +9 -0
- package/src/domains/doctor/mcp/index.ts +2 -0
- package/src/domains/doctor/mcp/specs.ts +88 -0
- package/src/domains/doctor/operations/checks/checkConfigFile.ts +28 -0
- package/src/domains/doctor/operations/checks/checkKeStore.ts +38 -0
- package/src/domains/doctor/operations/checks/checkMcpConfigured.ts +65 -0
- package/src/domains/doctor/operations/checks/checkNodeVersion.ts +25 -0
- package/src/domains/doctor/operations/checks/checkPragmaVersion.ts +20 -0
- package/src/domains/doctor/operations/checks/checkShellCompletions.ts +42 -0
- package/src/domains/doctor/operations/checks/checkSkillsSymlinked.ts +64 -0
- package/src/domains/doctor/operations/checks/index.ts +8 -0
- package/src/domains/doctor/operations/checks.test.ts +151 -0
- package/src/domains/doctor/operations/index.ts +3 -0
- package/src/domains/doctor/operations/runChecks.test.ts +89 -0
- package/src/domains/doctor/operations/runChecks.ts +38 -0
- package/src/domains/doctor/operations/types.ts +20 -0
- package/src/domains/graph/commands/index.ts +4 -0
- package/src/domains/graph/commands/inspect.test.ts +94 -0
- package/src/domains/graph/commands/inspect.ts +63 -0
- package/src/domains/graph/commands/query.test.ts +92 -0
- package/src/domains/graph/commands/query.ts +60 -0
- package/src/domains/graph/formatters/index.ts +4 -0
- package/src/domains/graph/formatters/inspect.ts +45 -0
- package/src/domains/graph/formatters/query.ts +37 -0
- package/src/domains/graph/helpers/index.ts +3 -0
- package/src/domains/graph/helpers/resolveUri.ts +70 -0
- package/src/domains/graph/index.ts +22 -0
- package/src/domains/graph/mcp/index.ts +2 -0
- package/src/domains/graph/mcp/specs.ts +82 -0
- package/src/domains/graph/operations/executeQuery.test.ts +68 -0
- package/src/domains/graph/operations/executeQuery.ts +32 -0
- package/src/domains/graph/operations/index.ts +4 -0
- package/src/domains/graph/operations/inspectUri.test.ts +101 -0
- package/src/domains/graph/operations/inspectUri.ts +61 -0
- package/src/domains/info/commands/index.ts +4 -0
- package/src/domains/info/commands/info.test.ts +88 -0
- package/src/domains/info/commands/info.ts +47 -0
- package/src/domains/info/commands/upgrade.test.ts +183 -0
- package/src/domains/info/commands/upgrade.ts +150 -0
- package/src/domains/info/constants.test.ts +22 -0
- package/src/domains/info/constants.ts +15 -0
- package/src/domains/info/formatters/index.ts +8 -0
- package/src/domains/info/formatters/info.test.ts +173 -0
- package/src/domains/info/formatters/info.ts +116 -0
- package/src/domains/info/formatters/upgrade.test.ts +133 -0
- package/src/domains/info/formatters/upgrade.ts +68 -0
- package/src/domains/info/index.ts +7 -0
- package/src/domains/info/operations/checkRegistryVersion.test.ts +124 -0
- package/src/domains/info/operations/checkRegistryVersion.ts +45 -0
- package/src/domains/info/operations/collectInfo.test.ts +111 -0
- package/src/domains/info/operations/collectInfo.ts +68 -0
- package/src/domains/info/operations/collectStoreSummary.test.ts +61 -0
- package/src/domains/info/operations/collectStoreSummary.ts +42 -0
- package/src/domains/info/operations/index.ts +5 -0
- package/src/domains/info/types.test-d.ts +35 -0
- package/src/domains/info/types.ts +46 -0
- package/src/domains/llm/commands/index.ts +3 -0
- package/src/domains/llm/commands/llm.ts +37 -0
- package/src/domains/llm/data/commandReference.ts +25 -0
- package/src/domains/llm/data/decisionTrees.ts +46 -0
- package/src/domains/llm/data/index.ts +4 -0
- package/src/domains/llm/formatters/index.ts +3 -0
- package/src/domains/llm/formatters/orientation.ts +53 -0
- package/src/domains/llm/index.ts +8 -0
- package/src/domains/llm/llmCommand.test.ts +93 -0
- package/src/domains/llm/mcp/index.ts +2 -0
- package/src/domains/llm/mcp/specs.ts +87 -0
- package/src/domains/llm/operations/collectContext.ts +47 -0
- package/src/domains/llm/operations/index.ts +3 -0
- package/src/domains/llm/types.ts +32 -0
- package/src/domains/modifier/commands/index.ts +4 -0
- package/src/domains/modifier/commands/list.test.ts +57 -0
- package/src/domains/modifier/commands/list.ts +33 -0
- package/src/domains/modifier/commands/lookup.test.ts +91 -0
- package/src/domains/modifier/commands/lookup.ts +105 -0
- package/src/domains/modifier/formatters/index.ts +4 -0
- package/src/domains/modifier/formatters/list.test.ts +38 -0
- package/src/domains/modifier/formatters/list.ts +35 -0
- package/src/domains/modifier/formatters/lookup.test.ts +33 -0
- package/src/domains/modifier/formatters/lookup.ts +38 -0
- package/src/domains/modifier/index.ts +22 -0
- package/src/domains/modifier/mcp/index.ts +2 -0
- package/src/domains/modifier/mcp/specs.ts +97 -0
- package/src/domains/modifier/operations/index.ts +4 -0
- package/src/domains/modifier/operations/list.test.ts +39 -0
- package/src/domains/modifier/operations/list.ts +41 -0
- package/src/domains/modifier/operations/lookup.test.ts +37 -0
- package/src/domains/modifier/operations/lookup.ts +57 -0
- package/src/domains/modifier/orchestration/index.ts +3 -0
- package/src/domains/modifier/orchestration/modifierEmptyError.ts +10 -0
- package/src/domains/modifier/orchestration/resolveModifierList.ts +16 -0
- package/src/domains/modifier/orchestration/resolveModifierLookup.ts +20 -0
- package/src/domains/modifier/types.ts +5 -0
- package/src/domains/ontology/commands/index.ts +4 -0
- package/src/domains/ontology/commands/list.test.ts +82 -0
- package/src/domains/ontology/commands/list.ts +46 -0
- package/src/domains/ontology/commands/show.test.ts +103 -0
- package/src/domains/ontology/commands/show.ts +66 -0
- package/src/domains/ontology/formatters/index.ts +4 -0
- package/src/domains/ontology/formatters/list.ts +37 -0
- package/src/domains/ontology/formatters/show.ts +74 -0
- package/src/domains/ontology/helpers/findNamespace.ts +23 -0
- package/src/domains/ontology/helpers/index.ts +7 -0
- package/src/domains/ontology/helpers/queryClasses.ts +50 -0
- package/src/domains/ontology/helpers/queryProperties.ts +57 -0
- package/src/domains/ontology/helpers/resolvePrefix.ts +45 -0
- package/src/domains/ontology/index.ts +26 -0
- package/src/domains/ontology/mcp/index.ts +2 -0
- package/src/domains/ontology/mcp/specs.ts +93 -0
- package/src/domains/ontology/operations/index.ts +5 -0
- package/src/domains/ontology/operations/listOntologies.test.ts +45 -0
- package/src/domains/ontology/operations/listOntologies.ts +101 -0
- package/src/domains/ontology/operations/showOntology.test.ts +65 -0
- package/src/domains/ontology/operations/showOntology.ts +41 -0
- package/src/domains/ontology/operations/showOntologyRaw.test.ts +39 -0
- package/src/domains/ontology/operations/showOntologyRaw.ts +50 -0
- package/src/domains/setup/commands/all.ts +58 -0
- package/src/domains/setup/commands/completions.ts +85 -0
- package/src/domains/setup/commands/entrypoints.test.ts +170 -0
- package/src/domains/setup/commands/index.ts +6 -0
- package/src/domains/setup/commands/lsp.ts +49 -0
- package/src/domains/setup/commands/mcp.ts +119 -0
- package/src/domains/setup/commands/skills.test.ts +64 -0
- package/src/domains/setup/commands/skills.ts +106 -0
- package/src/domains/setup/formatters/index.ts +2 -0
- package/src/domains/setup/formatters/skills.test.ts +103 -0
- package/src/domains/setup/formatters/skills.ts +112 -0
- package/src/domains/setup/formatters/types.ts +7 -0
- package/src/domains/setup/helpers/completionScripts.test.ts +58 -0
- package/src/domains/setup/helpers/completionScripts.ts +92 -0
- package/src/domains/setup/helpers/constants.ts +2 -0
- package/src/domains/setup/helpers/detectShell.test.ts +48 -0
- package/src/domains/setup/helpers/detectShell.ts +20 -0
- package/src/domains/setup/helpers/index.ts +12 -0
- package/src/domains/setup/helpers/runSetupTask.test.ts +279 -0
- package/src/domains/setup/helpers/runSetupTask.ts +229 -0
- package/src/domains/setup/index.ts +33 -0
- package/src/domains/setup/operations/index.ts +6 -0
- package/src/domains/setup/operations/setupAll.test.ts +48 -0
- package/src/domains/setup/operations/setupAll.ts +41 -0
- package/src/domains/setup/operations/setupCompletions.test.ts +94 -0
- package/src/domains/setup/operations/setupCompletions.ts +54 -0
- package/src/domains/setup/operations/setupLsp.test.ts +29 -0
- package/src/domains/setup/operations/setupLsp.ts +17 -0
- package/src/domains/setup/operations/setupMcp.test.ts +115 -0
- package/src/domains/setup/operations/setupMcp.ts +108 -0
- package/src/domains/setup/operations/setupSkills.test.ts +191 -0
- package/src/domains/setup/operations/setupSkills.ts +160 -0
- package/src/domains/setup/types.ts +18 -0
- package/src/domains/shared/ToolSpec.ts +49 -0
- package/src/domains/shared/bootStore.ts +77 -0
- package/src/domains/shared/buildQuery.ts +18 -0
- package/src/domains/shared/compactUri.test.ts +21 -0
- package/src/domains/shared/compactUri.ts +16 -0
- package/src/domains/shared/context.ts +15 -0
- package/src/domains/shared/contracts.ts +113 -0
- package/src/domains/shared/extractLocalName.test.ts +21 -0
- package/src/domains/shared/extractLocalName.ts +14 -0
- package/src/domains/shared/filters/buildChannelFilter.test.ts +52 -0
- package/src/domains/shared/filters/buildChannelFilter.ts +41 -0
- package/src/domains/shared/filters/buildFilterConfig.ts +16 -0
- package/src/domains/shared/filters/buildFilters.test.ts +33 -0
- package/src/domains/shared/filters/buildFilters.ts +30 -0
- package/src/domains/shared/filters/buildTierFilter.test.ts +78 -0
- package/src/domains/shared/filters/buildTierFilter.ts +69 -0
- package/src/domains/shared/filters/describeFilters.ts +14 -0
- package/src/domains/shared/filters/index.ts +10 -0
- package/src/domains/shared/formatters.ts +35 -0
- package/src/domains/shared/lookupMany.ts +45 -0
- package/src/domains/shared/namespaces.ts +18 -0
- package/src/domains/shared/packages.ts +76 -0
- package/src/domains/shared/prefixes.ts +37 -0
- package/src/domains/shared/runtime.ts +62 -0
- package/src/domains/shared/types.test-d.ts +127 -0
- package/src/domains/shared/types.ts +348 -0
- package/src/domains/skill/commands/index.ts +2 -0
- package/src/domains/skill/commands/list.test.ts +111 -0
- package/src/domains/skill/commands/list.ts +69 -0
- package/src/domains/skill/formatters/index.ts +2 -0
- package/src/domains/skill/formatters/list.test.ts +140 -0
- package/src/domains/skill/formatters/list.ts +119 -0
- package/src/domains/skill/formatters/types.ts +8 -0
- package/src/domains/skill/helpers/index.ts +6 -0
- package/src/domains/skill/helpers/parseFrontmatter.test.ts +122 -0
- package/src/domains/skill/helpers/parseFrontmatter.ts +112 -0
- package/src/domains/skill/helpers/resolveSkillSources.ts +34 -0
- package/src/domains/skill/index.ts +19 -0
- package/src/domains/skill/mcp/index.ts +2 -0
- package/src/domains/skill/mcp/specs.ts +38 -0
- package/src/domains/skill/operations/discover.test.ts +145 -0
- package/src/domains/skill/operations/discover.ts +68 -0
- package/src/domains/skill/operations/index.ts +4 -0
- package/src/domains/skill/operations/list.test.ts +68 -0
- package/src/domains/skill/operations/list.ts +47 -0
- package/src/domains/skill/types.ts +25 -0
- package/src/domains/standard/commands/categories.test.ts +70 -0
- package/src/domains/standard/commands/categories.ts +48 -0
- package/src/domains/standard/commands/index.ts +5 -0
- package/src/domains/standard/commands/list.test.ts +105 -0
- package/src/domains/standard/commands/list.ts +81 -0
- package/src/domains/standard/commands/lookup.test.ts +135 -0
- package/src/domains/standard/commands/lookup.ts +127 -0
- package/src/domains/standard/formatters/categories.test.ts +44 -0
- package/src/domains/standard/formatters/categories.ts +38 -0
- package/src/domains/standard/formatters/index.ts +6 -0
- package/src/domains/standard/formatters/list.test.ts +166 -0
- package/src/domains/standard/formatters/list.ts +156 -0
- package/src/domains/standard/formatters/lookup.test.ts +95 -0
- package/src/domains/standard/formatters/lookup.ts +86 -0
- package/src/domains/standard/formatters/types.ts +31 -0
- package/src/domains/standard/index.ts +32 -0
- package/src/domains/standard/mcp/index.ts +2 -0
- package/src/domains/standard/mcp/specs.ts +197 -0
- package/src/domains/standard/operations/categories.test.ts +61 -0
- package/src/domains/standard/operations/categories.ts +38 -0
- package/src/domains/standard/operations/index.ts +5 -0
- package/src/domains/standard/operations/list.test.ts +75 -0
- package/src/domains/standard/operations/list.ts +64 -0
- package/src/domains/standard/operations/lookup.test.ts +58 -0
- package/src/domains/standard/operations/lookup.ts +142 -0
- package/src/domains/standard/orchestration/buildStandardFilters.ts +11 -0
- package/src/domains/standard/orchestration/index.ts +4 -0
- package/src/domains/standard/orchestration/resolveStandardList.ts +42 -0
- package/src/domains/standard/orchestration/resolveStandardLookup.ts +20 -0
- package/src/domains/standard/orchestration/standardEmptyError.ts +19 -0
- package/src/domains/standard/types.ts +15 -0
- package/src/domains/tier/commands/index.ts +3 -0
- package/src/domains/tier/commands/list.test.ts +67 -0
- package/src/domains/tier/commands/list.ts +43 -0
- package/src/domains/tier/formatters/index.ts +3 -0
- package/src/domains/tier/formatters/list.test.ts +33 -0
- package/src/domains/tier/formatters/list.ts +40 -0
- package/src/domains/tier/index.ts +17 -0
- package/src/domains/tier/mcp/index.ts +2 -0
- package/src/domains/tier/mcp/specs.ts +38 -0
- package/src/domains/tier/operations/index.ts +3 -0
- package/src/domains/tier/operations/list.test.ts +32 -0
- package/src/domains/tier/operations/list.ts +33 -0
- package/src/domains/token/commands/add-config.test.ts +68 -0
- package/src/domains/token/commands/add-config.ts +62 -0
- package/src/domains/token/commands/index.ts +5 -0
- package/src/domains/token/commands/list.test.ts +82 -0
- package/src/domains/token/commands/list.ts +45 -0
- package/src/domains/token/commands/lookup.test.ts +125 -0
- package/src/domains/token/commands/lookup.ts +121 -0
- package/src/domains/token/formatters/index.ts +4 -0
- package/src/domains/token/formatters/list.test.ts +37 -0
- package/src/domains/token/formatters/list.ts +37 -0
- package/src/domains/token/formatters/lookup.test.ts +61 -0
- package/src/domains/token/formatters/lookup.ts +72 -0
- package/src/domains/token/index.ts +32 -0
- package/src/domains/token/mcp/index.ts +2 -0
- package/src/domains/token/mcp/specs.ts +145 -0
- package/src/domains/token/operations/add-config.test.ts +68 -0
- package/src/domains/token/operations/add-config.ts +61 -0
- package/src/domains/token/operations/index.ts +7 -0
- package/src/domains/token/operations/list.test.ts +50 -0
- package/src/domains/token/operations/list.ts +52 -0
- package/src/domains/token/operations/lookup.test.ts +42 -0
- package/src/domains/token/operations/lookup.ts +66 -0
- package/src/domains/token/orchestration/index.ts +3 -0
- package/src/domains/token/orchestration/resolveTokenList.ts +17 -0
- package/src/domains/token/orchestration/resolveTokenLookup.ts +20 -0
- package/src/domains/token/orchestration/tokenEmptyError.ts +17 -0
- package/src/domains/token/types.ts +6 -0
- package/src/error/PragmaError.test.ts +121 -0
- package/src/error/PragmaError.ts +164 -0
- package/src/error/constants.ts +12 -0
- package/src/error/index.ts +4 -0
- package/src/error/types.ts +41 -0
- package/src/index.ts +141 -0
- package/src/mcp/createMcpServer.test.ts +41 -0
- package/src/mcp/createMcpServer.ts +53 -0
- package/src/mcp/error/buildRecovery.test.ts +37 -0
- package/src/mcp/error/buildRecovery.ts +17 -0
- package/src/mcp/error/index.ts +12 -0
- package/src/mcp/error/serializeError.test.ts +121 -0
- package/src/mcp/error/serializeError.ts +31 -0
- package/src/mcp/error/serializeErrorPayload.test.ts +38 -0
- package/src/mcp/error/serializeErrorPayload.ts +27 -0
- package/src/mcp/error/types.ts +51 -0
- package/src/mcp/index.ts +15 -0
- package/src/mcp/registerResources.test.ts +236 -0
- package/src/mcp/registerResources.ts +303 -0
- package/src/mcp/runMcpServer.test.ts +66 -0
- package/src/mcp/runMcpServer.ts +24 -0
- package/src/mcp/tools/index.ts +55 -0
- package/src/mcp/tools/registerFromSpec.ts +97 -0
- package/src/mcp/tools/registerTools.test.ts +1124 -0
- package/src/mcp/types.ts +62 -0
- package/src/mcp/utils/estimateTokens.test.ts +14 -0
- package/src/mcp/utils/estimateTokens.ts +9 -0
- package/src/mcp/utils/index.ts +11 -0
- package/src/mcp/utils/wrapTool.test.ts +108 -0
- package/src/mcp/utils/wrapTool.ts +81 -0
- package/src/package-manager/constants.ts +29 -0
- package/src/package-manager/detectInstallSource.test.ts +23 -0
- package/src/package-manager/detectInstallSource.ts +111 -0
- package/src/package-manager/detectLocalInstall.test.ts +16 -0
- package/src/package-manager/detectLocalInstall.ts +43 -0
- package/src/package-manager/detectPackageManager.test.ts +81 -0
- package/src/package-manager/detectPackageManager.ts +30 -0
- package/src/package-manager/index.ts +6 -0
- package/src/package-manager/types.ts +12 -0
- package/src/pipeline/collectCommands.test.ts +37 -0
- package/src/pipeline/collectCommands.ts +47 -0
- package/src/pipeline/constants.ts +14 -0
- package/src/pipeline/createProgram.test.ts +112 -0
- package/src/pipeline/createProgram.ts +70 -0
- package/src/pipeline/formatTerminal.test.ts +43 -0
- package/src/pipeline/formatTerminal.ts +53 -0
- package/src/pipeline/index.ts +19 -0
- package/src/pipeline/mapExitCode.test.ts +41 -0
- package/src/pipeline/mapExitCode.ts +12 -0
- package/src/pipeline/parseGlobalFlags.ts +21 -0
- package/src/pipeline/renderError.test.ts +151 -0
- package/src/pipeline/renderError.ts +114 -0
- package/src/pipeline/resolveCommandKind.test.ts +68 -0
- package/src/pipeline/resolveCommandKind.ts +71 -0
- package/src/pipeline/runCli.orchestration.test.ts +418 -0
- package/src/pipeline/runCli.test.ts +70 -0
- package/src/pipeline/runCli.ts +215 -0
- package/src/pipeline/types.ts +17 -0
- package/src/testing/cli.ts +43 -0
- package/src/testing/createTestMcpClient.ts +64 -0
- package/src/testing/dsFixtures.ts +506 -0
- package/src/testing/fixtures/canonical-config.json +4 -0
- package/src/testing/fixtures/canonical.ttl +437 -0
- package/src/testing/fixtures/filtered-config.json +4 -0
- package/src/testing/fixtures.ts +34 -0
- package/src/testing/helpers/assertParity.ts +28 -0
- package/src/testing/helpers/createTestMcpClient.ts +40 -0
- package/src/testing/helpers/createTestRuntime.ts +67 -0
- package/src/testing/index.ts +8 -0
- package/src/testing/integration/agent-session.test.ts +148 -0
- package/src/testing/integration/boot.test.ts +67 -0
- package/src/testing/integration/mcp-surface.test.ts +574 -0
- package/src/testing/integration/parity.test.ts +192 -0
- package/src/testing/store.ts +39 -0
- package/src/testing/types.ts +20 -0
|
@@ -0,0 +1,69 @@
|
|
|
1
|
+
import {
|
|
2
|
+
type CommandDefinition,
|
|
3
|
+
type CommandResult,
|
|
4
|
+
createOutputResult,
|
|
5
|
+
} from "@canonical/cli-core";
|
|
6
|
+
import { PragmaError } from "#error";
|
|
7
|
+
import type { PragmaContext } from "../../shared/context.js";
|
|
8
|
+
import { selectFormatter } from "../../shared/formatters.js";
|
|
9
|
+
import { listFormatters } from "../formatters/index.js";
|
|
10
|
+
import type { SkillListInput } from "../formatters/types.js";
|
|
11
|
+
import { listSkills } from "../operations/index.js";
|
|
12
|
+
|
|
13
|
+
/**
|
|
14
|
+
* Build the `pragma skill list` command definition.
|
|
15
|
+
*
|
|
16
|
+
* Discovers skills from installed packages and formats the result.
|
|
17
|
+
* Throws when no skills are found, with a recovery hint.
|
|
18
|
+
*
|
|
19
|
+
* @param ctx - Pragma context providing cwd and formatter selection.
|
|
20
|
+
* @returns The command definition for `pragma skill list`.
|
|
21
|
+
* @note Impure
|
|
22
|
+
*/
|
|
23
|
+
export default function buildListCommand(
|
|
24
|
+
ctx: PragmaContext,
|
|
25
|
+
): CommandDefinition {
|
|
26
|
+
return {
|
|
27
|
+
path: ["skill", "list"],
|
|
28
|
+
description: "List available agent skills from design system packages",
|
|
29
|
+
parameters: [
|
|
30
|
+
{
|
|
31
|
+
name: "detailed",
|
|
32
|
+
description: "Show full metadata for each skill",
|
|
33
|
+
type: "boolean",
|
|
34
|
+
default: false,
|
|
35
|
+
},
|
|
36
|
+
],
|
|
37
|
+
meta: {
|
|
38
|
+
examples: [
|
|
39
|
+
"pragma skill list",
|
|
40
|
+
"pragma skill list --detailed",
|
|
41
|
+
"pragma skill list --llm",
|
|
42
|
+
"pragma skill list --format json",
|
|
43
|
+
],
|
|
44
|
+
},
|
|
45
|
+
execute: async (
|
|
46
|
+
params: Record<string, unknown>,
|
|
47
|
+
): Promise<CommandResult> => {
|
|
48
|
+
const detailed = params.detailed === true;
|
|
49
|
+
const { skills, sources } = await listSkills(ctx.cwd);
|
|
50
|
+
|
|
51
|
+
if (skills.length === 0) {
|
|
52
|
+
const allUnavailable = sources.every((s) => !s.available);
|
|
53
|
+
throw PragmaError.emptyResults("skill", {
|
|
54
|
+
recovery: {
|
|
55
|
+
message: allUnavailable
|
|
56
|
+
? "Install @canonical packages first"
|
|
57
|
+
: "No SKILL.md files found in source packages",
|
|
58
|
+
},
|
|
59
|
+
});
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
const input: SkillListInput = { skills, sources, detailed };
|
|
63
|
+
|
|
64
|
+
return createOutputResult(input, {
|
|
65
|
+
plain: selectFormatter(ctx, listFormatters),
|
|
66
|
+
});
|
|
67
|
+
},
|
|
68
|
+
};
|
|
69
|
+
}
|
|
@@ -0,0 +1,140 @@
|
|
|
1
|
+
import { describe, expect, it } from "vitest";
|
|
2
|
+
import type { DiscoveredSkill, SkillSource } from "../types.js";
|
|
3
|
+
import formatters from "./list.js";
|
|
4
|
+
|
|
5
|
+
const MOCK_SKILLS: readonly DiscoveredSkill[] = [
|
|
6
|
+
{
|
|
7
|
+
name: "design-audit",
|
|
8
|
+
description: "Audit a component against DS specs",
|
|
9
|
+
sourcePath: "node_modules/@canonical/ds-global/skills/design-audit",
|
|
10
|
+
sourcePackage: "@canonical/ds-global",
|
|
11
|
+
folderName: "design-audit",
|
|
12
|
+
frontmatter: {
|
|
13
|
+
name: "design-audit",
|
|
14
|
+
description: "Audit a component against DS specs",
|
|
15
|
+
metadata: { author: "canonical" },
|
|
16
|
+
},
|
|
17
|
+
},
|
|
18
|
+
{
|
|
19
|
+
name: "scaffold-story",
|
|
20
|
+
description: "Generate a Storybook story from anatomy",
|
|
21
|
+
sourcePath: "/tmp/node_modules/@canonical/pragma-cli/skills/scaffold-story",
|
|
22
|
+
sourcePackage: "@canonical/pragma-cli",
|
|
23
|
+
folderName: "scaffold-story",
|
|
24
|
+
frontmatter: {
|
|
25
|
+
name: "scaffold-story",
|
|
26
|
+
description: "Generate a Storybook story from anatomy",
|
|
27
|
+
},
|
|
28
|
+
},
|
|
29
|
+
];
|
|
30
|
+
|
|
31
|
+
const MOCK_SOURCES: readonly SkillSource[] = [
|
|
32
|
+
{
|
|
33
|
+
path: "node_modules/@canonical/ds-global/skills",
|
|
34
|
+
packageName: "@canonical/ds-global",
|
|
35
|
+
available: true,
|
|
36
|
+
},
|
|
37
|
+
{
|
|
38
|
+
path: "node_modules/@canonical/anatomy-dsl/skills",
|
|
39
|
+
packageName: "@canonical/anatomy-dsl",
|
|
40
|
+
available: false,
|
|
41
|
+
},
|
|
42
|
+
{
|
|
43
|
+
path: "/tmp/node_modules/@canonical/pragma-cli/skills",
|
|
44
|
+
packageName: "@canonical/pragma-cli",
|
|
45
|
+
available: true,
|
|
46
|
+
},
|
|
47
|
+
];
|
|
48
|
+
|
|
49
|
+
describe("skill list formatters", () => {
|
|
50
|
+
describe("plain", () => {
|
|
51
|
+
it("groups skills by source package", () => {
|
|
52
|
+
const output = formatters.plain({
|
|
53
|
+
skills: MOCK_SKILLS,
|
|
54
|
+
sources: MOCK_SOURCES,
|
|
55
|
+
detailed: false,
|
|
56
|
+
});
|
|
57
|
+
expect(output).toContain("@canonical/ds-global");
|
|
58
|
+
expect(output).toContain("@canonical/pragma-cli");
|
|
59
|
+
expect(output).toContain("design-audit");
|
|
60
|
+
expect(output).toContain("scaffold-story");
|
|
61
|
+
});
|
|
62
|
+
|
|
63
|
+
it("shows unavailable sources", () => {
|
|
64
|
+
const output = formatters.plain({
|
|
65
|
+
skills: MOCK_SKILLS,
|
|
66
|
+
sources: MOCK_SOURCES,
|
|
67
|
+
detailed: false,
|
|
68
|
+
});
|
|
69
|
+
expect(output).toContain("@canonical/anatomy-dsl not installed");
|
|
70
|
+
});
|
|
71
|
+
|
|
72
|
+
it("shows total count", () => {
|
|
73
|
+
const output = formatters.plain({
|
|
74
|
+
skills: MOCK_SKILLS,
|
|
75
|
+
sources: MOCK_SOURCES,
|
|
76
|
+
detailed: false,
|
|
77
|
+
});
|
|
78
|
+
expect(output).toContain("2 skills from 2 packages");
|
|
79
|
+
});
|
|
80
|
+
|
|
81
|
+
it("shows metadata when detailed", () => {
|
|
82
|
+
const output = formatters.plain({
|
|
83
|
+
skills: MOCK_SKILLS,
|
|
84
|
+
sources: MOCK_SOURCES,
|
|
85
|
+
detailed: true,
|
|
86
|
+
});
|
|
87
|
+
expect(output).toContain("Author");
|
|
88
|
+
expect(output).toContain("canonical");
|
|
89
|
+
expect(output).toContain("Source:");
|
|
90
|
+
});
|
|
91
|
+
});
|
|
92
|
+
|
|
93
|
+
describe("llm", () => {
|
|
94
|
+
it("renders markdown with heading", () => {
|
|
95
|
+
const output = formatters.llm({
|
|
96
|
+
skills: MOCK_SKILLS,
|
|
97
|
+
sources: MOCK_SOURCES,
|
|
98
|
+
detailed: false,
|
|
99
|
+
});
|
|
100
|
+
expect(output).toContain("## Skills");
|
|
101
|
+
expect(output).toContain("### @canonical/ds-global");
|
|
102
|
+
expect(output).toContain("**design-audit**");
|
|
103
|
+
});
|
|
104
|
+
|
|
105
|
+
it("notes unavailable sources", () => {
|
|
106
|
+
const output = formatters.llm({
|
|
107
|
+
skills: MOCK_SKILLS,
|
|
108
|
+
sources: MOCK_SOURCES,
|
|
109
|
+
detailed: false,
|
|
110
|
+
});
|
|
111
|
+
expect(output).toContain("*Not installed: @canonical/anatomy-dsl*");
|
|
112
|
+
});
|
|
113
|
+
});
|
|
114
|
+
|
|
115
|
+
describe("json", () => {
|
|
116
|
+
it("produces valid JSON array", () => {
|
|
117
|
+
const output = formatters.json({
|
|
118
|
+
skills: MOCK_SKILLS,
|
|
119
|
+
sources: MOCK_SOURCES,
|
|
120
|
+
detailed: false,
|
|
121
|
+
});
|
|
122
|
+
const parsed = JSON.parse(output);
|
|
123
|
+
expect(Array.isArray(parsed)).toBe(true);
|
|
124
|
+
expect(parsed).toHaveLength(2);
|
|
125
|
+
expect(parsed[0].name).toBe("design-audit");
|
|
126
|
+
expect(parsed[0].source).toBe("@canonical/ds-global");
|
|
127
|
+
});
|
|
128
|
+
});
|
|
129
|
+
|
|
130
|
+
describe("empty skills", () => {
|
|
131
|
+
it("handles no skills gracefully", () => {
|
|
132
|
+
const output = formatters.plain({
|
|
133
|
+
skills: [],
|
|
134
|
+
sources: MOCK_SOURCES,
|
|
135
|
+
detailed: false,
|
|
136
|
+
});
|
|
137
|
+
expect(output).toContain("0 skills from 0 packages");
|
|
138
|
+
});
|
|
139
|
+
});
|
|
140
|
+
});
|
|
@@ -0,0 +1,119 @@
|
|
|
1
|
+
import chalk from "chalk";
|
|
2
|
+
import type { Formatters } from "../../shared/formatters.js";
|
|
3
|
+
import type { DiscoveredSkill } from "../types.js";
|
|
4
|
+
import type { SkillListInput } from "./types.js";
|
|
5
|
+
|
|
6
|
+
/**
|
|
7
|
+
* Group discovered skills by their source package name.
|
|
8
|
+
*
|
|
9
|
+
* @param skills - Skills to group.
|
|
10
|
+
* @returns A Map keyed by package name.
|
|
11
|
+
*/
|
|
12
|
+
function groupBySource(
|
|
13
|
+
skills: readonly DiscoveredSkill[],
|
|
14
|
+
): Map<string, DiscoveredSkill[]> {
|
|
15
|
+
const groups = new Map<string, DiscoveredSkill[]>();
|
|
16
|
+
for (const skill of skills) {
|
|
17
|
+
const group = groups.get(skill.sourcePackage) ?? [];
|
|
18
|
+
group.push(skill);
|
|
19
|
+
groups.set(skill.sourcePackage, group);
|
|
20
|
+
}
|
|
21
|
+
return groups;
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
/**
|
|
25
|
+
* Formatters for `pragma skill list` output.
|
|
26
|
+
*
|
|
27
|
+
* - **plain** renders skills grouped by source package with chalk styling.
|
|
28
|
+
* - **llm** renders a markdown document with package headings.
|
|
29
|
+
* - **json** serializes a flat skill array with metadata.
|
|
30
|
+
*/
|
|
31
|
+
const formatters: Formatters<SkillListInput> = {
|
|
32
|
+
plain({ skills, sources, detailed }) {
|
|
33
|
+
const lines: string[] = [];
|
|
34
|
+
const grouped = groupBySource(skills);
|
|
35
|
+
|
|
36
|
+
for (const [pkg, pkgSkills] of grouped) {
|
|
37
|
+
lines.push(` ${chalk.dim(pkg)}`);
|
|
38
|
+
for (const skill of pkgSkills) {
|
|
39
|
+
const desc = chalk.dim(skill.description);
|
|
40
|
+
lines.push(` ${chalk.bold(skill.name.padEnd(22))}${desc}`);
|
|
41
|
+
|
|
42
|
+
if (detailed) {
|
|
43
|
+
const fm = skill.frontmatter;
|
|
44
|
+
if (fm.license) {
|
|
45
|
+
lines.push(` License: ${fm.license}`);
|
|
46
|
+
}
|
|
47
|
+
if (fm.compatibility?.length) {
|
|
48
|
+
lines.push(` Compatibility: ${fm.compatibility.join(", ")}`);
|
|
49
|
+
}
|
|
50
|
+
if (fm.metadata) {
|
|
51
|
+
for (const [key, value] of Object.entries(fm.metadata)) {
|
|
52
|
+
const label = key.charAt(0).toUpperCase() + key.slice(1);
|
|
53
|
+
lines.push(` ${label.padEnd(16)}${String(value)}`);
|
|
54
|
+
}
|
|
55
|
+
}
|
|
56
|
+
lines.push(` Source: ${skill.sourcePath}`);
|
|
57
|
+
}
|
|
58
|
+
}
|
|
59
|
+
lines.push("");
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
const unavailable = sources.filter((s) => !s.available);
|
|
63
|
+
for (const source of unavailable) {
|
|
64
|
+
lines.push(
|
|
65
|
+
chalk.dim(
|
|
66
|
+
` ${source.packageName} not installed (no skills from this source)`,
|
|
67
|
+
),
|
|
68
|
+
);
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
const total = `${skills.length} skill${skills.length === 1 ? "" : "s"}`;
|
|
72
|
+
const pkgCount = grouped.size;
|
|
73
|
+
const pkgs = `${pkgCount} package${pkgCount === 1 ? "" : "s"}`;
|
|
74
|
+
lines.push(`${total} from ${pkgs}`);
|
|
75
|
+
|
|
76
|
+
return lines.join("\n");
|
|
77
|
+
},
|
|
78
|
+
|
|
79
|
+
llm({ skills, sources }) {
|
|
80
|
+
const lines: string[] = [];
|
|
81
|
+
const grouped = groupBySource(skills);
|
|
82
|
+
|
|
83
|
+
lines.push("## Skills");
|
|
84
|
+
lines.push("");
|
|
85
|
+
|
|
86
|
+
for (const [pkg, pkgSkills] of grouped) {
|
|
87
|
+
lines.push(`### ${pkg}`);
|
|
88
|
+
for (const skill of pkgSkills) {
|
|
89
|
+
lines.push(`- **${skill.name}** — ${skill.description}`);
|
|
90
|
+
}
|
|
91
|
+
lines.push("");
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
const unavailable = sources.filter((s) => !s.available);
|
|
95
|
+
if (unavailable.length > 0) {
|
|
96
|
+
lines.push(
|
|
97
|
+
`*Not installed: ${unavailable.map((s) => s.packageName).join(", ")}*`,
|
|
98
|
+
);
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
return lines.join("\n");
|
|
102
|
+
},
|
|
103
|
+
|
|
104
|
+
json({ skills }) {
|
|
105
|
+
return JSON.stringify(
|
|
106
|
+
skills.map((s) => ({
|
|
107
|
+
name: s.name,
|
|
108
|
+
description: s.description,
|
|
109
|
+
source: s.sourcePackage,
|
|
110
|
+
path: s.sourcePath,
|
|
111
|
+
metadata: s.frontmatter.metadata ?? {},
|
|
112
|
+
})),
|
|
113
|
+
null,
|
|
114
|
+
2,
|
|
115
|
+
);
|
|
116
|
+
},
|
|
117
|
+
};
|
|
118
|
+
|
|
119
|
+
export default formatters;
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
import type { DiscoveredSkill, SkillSource } from "../types.js";
|
|
2
|
+
|
|
3
|
+
/** Formatter input for `pragma skill list`, pairing skills with source metadata and detail flag. */
|
|
4
|
+
export interface SkillListInput {
|
|
5
|
+
readonly skills: readonly DiscoveredSkill[];
|
|
6
|
+
readonly sources: readonly SkillSource[];
|
|
7
|
+
readonly detailed: boolean;
|
|
8
|
+
}
|
|
@@ -0,0 +1,122 @@
|
|
|
1
|
+
import { describe, expect, it } from "vitest";
|
|
2
|
+
import parseFrontmatter from "./parseFrontmatter.js";
|
|
3
|
+
|
|
4
|
+
describe("parseFrontmatter", () => {
|
|
5
|
+
it("parses valid frontmatter with required fields", () => {
|
|
6
|
+
const content = `---
|
|
7
|
+
name: design-audit
|
|
8
|
+
description: Audit a component implementation against DS specs
|
|
9
|
+
---
|
|
10
|
+
|
|
11
|
+
# Design Audit
|
|
12
|
+
|
|
13
|
+
Instructions here.
|
|
14
|
+
`;
|
|
15
|
+
const result = parseFrontmatter(content);
|
|
16
|
+
expect(result).toEqual({
|
|
17
|
+
name: "design-audit",
|
|
18
|
+
description: "Audit a component implementation against DS specs",
|
|
19
|
+
});
|
|
20
|
+
});
|
|
21
|
+
|
|
22
|
+
it("parses frontmatter with all optional fields", () => {
|
|
23
|
+
const content = `---
|
|
24
|
+
name: design-audit
|
|
25
|
+
description: Audit a component implementation against DS specs
|
|
26
|
+
license: MIT
|
|
27
|
+
compatibility: [claude, cursor, windsurf]
|
|
28
|
+
metadata:
|
|
29
|
+
author: canonical
|
|
30
|
+
version: 1.0
|
|
31
|
+
---
|
|
32
|
+
|
|
33
|
+
Body content.
|
|
34
|
+
`;
|
|
35
|
+
const result = parseFrontmatter(content);
|
|
36
|
+
expect(result).toEqual({
|
|
37
|
+
name: "design-audit",
|
|
38
|
+
description: "Audit a component implementation against DS specs",
|
|
39
|
+
license: "MIT",
|
|
40
|
+
compatibility: ["claude", "cursor", "windsurf"],
|
|
41
|
+
metadata: { author: "canonical", version: 1.0 },
|
|
42
|
+
});
|
|
43
|
+
});
|
|
44
|
+
|
|
45
|
+
it("returns null for missing frontmatter delimiters", () => {
|
|
46
|
+
expect(parseFrontmatter("# No frontmatter here")).toBeNull();
|
|
47
|
+
});
|
|
48
|
+
|
|
49
|
+
it("returns null for empty content", () => {
|
|
50
|
+
expect(parseFrontmatter("")).toBeNull();
|
|
51
|
+
});
|
|
52
|
+
|
|
53
|
+
it("returns null when name is missing", () => {
|
|
54
|
+
const content = `---
|
|
55
|
+
description: Some description
|
|
56
|
+
---`;
|
|
57
|
+
expect(parseFrontmatter(content)).toBeNull();
|
|
58
|
+
});
|
|
59
|
+
|
|
60
|
+
it("returns null when description is missing", () => {
|
|
61
|
+
const content = `---
|
|
62
|
+
name: some-skill
|
|
63
|
+
---`;
|
|
64
|
+
expect(parseFrontmatter(content)).toBeNull();
|
|
65
|
+
});
|
|
66
|
+
|
|
67
|
+
it("returns null when name is empty string", () => {
|
|
68
|
+
const content = `---
|
|
69
|
+
name:
|
|
70
|
+
description: Some description
|
|
71
|
+
---`;
|
|
72
|
+
expect(parseFrontmatter(content)).toBeNull();
|
|
73
|
+
});
|
|
74
|
+
|
|
75
|
+
it("handles quoted values", () => {
|
|
76
|
+
const content = `---
|
|
77
|
+
name: "my-skill"
|
|
78
|
+
description: 'A skill with quotes'
|
|
79
|
+
---`;
|
|
80
|
+
const result = parseFrontmatter(content);
|
|
81
|
+
expect(result?.name).toBe("my-skill");
|
|
82
|
+
expect(result?.description).toBe("A skill with quotes");
|
|
83
|
+
});
|
|
84
|
+
|
|
85
|
+
it("handles metadata with nested keys", () => {
|
|
86
|
+
const content = `---
|
|
87
|
+
name: test-skill
|
|
88
|
+
description: A test skill
|
|
89
|
+
metadata:
|
|
90
|
+
author: canonical
|
|
91
|
+
priority: high
|
|
92
|
+
---`;
|
|
93
|
+
const result = parseFrontmatter(content);
|
|
94
|
+
expect(result?.metadata).toEqual({
|
|
95
|
+
author: "canonical",
|
|
96
|
+
priority: "high",
|
|
97
|
+
});
|
|
98
|
+
});
|
|
99
|
+
|
|
100
|
+
it("ignores content after closing delimiter", () => {
|
|
101
|
+
const content = `---
|
|
102
|
+
name: test-skill
|
|
103
|
+
description: A test skill
|
|
104
|
+
---
|
|
105
|
+
|
|
106
|
+
This is the body and should not affect parsing.
|
|
107
|
+
name: wrong-name
|
|
108
|
+
`;
|
|
109
|
+
const result = parseFrontmatter(content);
|
|
110
|
+
expect(result?.name).toBe("test-skill");
|
|
111
|
+
});
|
|
112
|
+
|
|
113
|
+
it("handles single-item compatibility array", () => {
|
|
114
|
+
const content = `---
|
|
115
|
+
name: test-skill
|
|
116
|
+
description: A test skill
|
|
117
|
+
compatibility: [claude]
|
|
118
|
+
---`;
|
|
119
|
+
const result = parseFrontmatter(content);
|
|
120
|
+
expect(result?.compatibility).toEqual(["claude"]);
|
|
121
|
+
});
|
|
122
|
+
});
|
|
@@ -0,0 +1,112 @@
|
|
|
1
|
+
import type { SkillFrontmatter } from "../types.js";
|
|
2
|
+
|
|
3
|
+
/** Regex matching the `---`-delimited YAML frontmatter block. */
|
|
4
|
+
const FRONTMATTER_RE = /^---\r?\n([\s\S]*?)\r?\n---/;
|
|
5
|
+
|
|
6
|
+
/**
|
|
7
|
+
* Parse a single YAML scalar value (string, boolean, number, or inline array).
|
|
8
|
+
*
|
|
9
|
+
* @param raw - Raw string value from a YAML line.
|
|
10
|
+
* @returns The parsed JavaScript value.
|
|
11
|
+
*/
|
|
12
|
+
function parseYamlValue(raw: string): unknown {
|
|
13
|
+
const trimmed = raw.trim();
|
|
14
|
+
|
|
15
|
+
if (trimmed.startsWith("[") && trimmed.endsWith("]")) {
|
|
16
|
+
return trimmed
|
|
17
|
+
.slice(1, -1)
|
|
18
|
+
.split(",")
|
|
19
|
+
.map((s) => s.trim().replace(/^["']|["']$/g, ""))
|
|
20
|
+
.filter(Boolean);
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
if (trimmed === "true") return true;
|
|
24
|
+
if (trimmed === "false") return false;
|
|
25
|
+
|
|
26
|
+
const num = Number(trimmed);
|
|
27
|
+
if (!Number.isNaN(num) && trimmed !== "") return num;
|
|
28
|
+
|
|
29
|
+
return trimmed.replace(/^["']|["']$/g, "");
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
/**
|
|
33
|
+
* Parse a simple subset of YAML supporting top-level keys and one level of nesting.
|
|
34
|
+
*
|
|
35
|
+
* @param block - The YAML block content (without `---` delimiters).
|
|
36
|
+
* @returns A record of parsed key-value pairs.
|
|
37
|
+
*/
|
|
38
|
+
function parseSimpleYaml(block: string): Record<string, unknown> {
|
|
39
|
+
const result: Record<string, unknown> = {};
|
|
40
|
+
let currentKey: string | null = null;
|
|
41
|
+
let nestedObject: Record<string, unknown> | null = null;
|
|
42
|
+
|
|
43
|
+
for (const line of block.split("\n")) {
|
|
44
|
+
if (line.trim() === "" || line.trim().startsWith("#")) continue;
|
|
45
|
+
|
|
46
|
+
const indented = line.match(/^(\s+)(\w[\w-]*)\s*:\s*(.*)/);
|
|
47
|
+
const topLevel = line.match(/^(\w[\w-]*)\s*:\s*(.*)/);
|
|
48
|
+
|
|
49
|
+
if (indented && currentKey && (indented[1]?.length ?? 0) >= 2) {
|
|
50
|
+
if (!nestedObject) nestedObject = {};
|
|
51
|
+
const key = indented[2] as string;
|
|
52
|
+
const value = (indented[3] ?? "").trim();
|
|
53
|
+
nestedObject[key] = value === "" ? true : parseYamlValue(value);
|
|
54
|
+
} else if (topLevel) {
|
|
55
|
+
if (currentKey && nestedObject) {
|
|
56
|
+
result[currentKey] = nestedObject;
|
|
57
|
+
nestedObject = null;
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
const key = topLevel[1] as string;
|
|
61
|
+
const value = topLevel[2] ?? "";
|
|
62
|
+
currentKey = key;
|
|
63
|
+
if (value === "") {
|
|
64
|
+
nestedObject = {};
|
|
65
|
+
} else {
|
|
66
|
+
result[key] = parseYamlValue(value);
|
|
67
|
+
nestedObject = null;
|
|
68
|
+
}
|
|
69
|
+
}
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
if (currentKey && nestedObject) {
|
|
73
|
+
result[currentKey] = nestedObject;
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
return result;
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
/**
|
|
80
|
+
* Extract and validate YAML frontmatter from a SKILL.md file.
|
|
81
|
+
*
|
|
82
|
+
* Requires `name` and `description` fields. Returns null for missing
|
|
83
|
+
* or malformed frontmatter.
|
|
84
|
+
*
|
|
85
|
+
* @param content - Full SKILL.md file content.
|
|
86
|
+
* @returns Parsed frontmatter, or null if invalid.
|
|
87
|
+
*/
|
|
88
|
+
export default function parseFrontmatter(
|
|
89
|
+
content: string,
|
|
90
|
+
): SkillFrontmatter | null {
|
|
91
|
+
const match = content.match(FRONTMATTER_RE);
|
|
92
|
+
if (!match?.[1]) return null;
|
|
93
|
+
|
|
94
|
+
const parsed = parseSimpleYaml(match[1]);
|
|
95
|
+
|
|
96
|
+
if (typeof parsed.name !== "string" || parsed.name === "") return null;
|
|
97
|
+
if (typeof parsed.description !== "string" || parsed.description === "")
|
|
98
|
+
return null;
|
|
99
|
+
|
|
100
|
+
return {
|
|
101
|
+
name: parsed.name,
|
|
102
|
+
description: parsed.description,
|
|
103
|
+
...(typeof parsed.license === "string" && { license: parsed.license }),
|
|
104
|
+
...(Array.isArray(parsed.compatibility) && {
|
|
105
|
+
compatibility: parsed.compatibility,
|
|
106
|
+
}),
|
|
107
|
+
...(typeof parsed.metadata === "object" &&
|
|
108
|
+
parsed.metadata !== null && {
|
|
109
|
+
metadata: parsed.metadata as Readonly<Record<string, unknown>>,
|
|
110
|
+
}),
|
|
111
|
+
};
|
|
112
|
+
}
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
import { join } from "node:path";
|
|
2
|
+
import { PACKAGES, resolvePackages } from "../../shared/packages.js";
|
|
3
|
+
|
|
4
|
+
/** A resolved skill source directory from an installed package. */
|
|
5
|
+
export interface SkillSource {
|
|
6
|
+
/** Absolute path to the skills directory. */
|
|
7
|
+
readonly dir: string;
|
|
8
|
+
/** Package name this source belongs to. */
|
|
9
|
+
readonly packageName: string;
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
/**
|
|
13
|
+
* Resolve skill source directories from the shared package registry.
|
|
14
|
+
* Package-manager agnostic -- works with bun, npm, pnpm, yarn.
|
|
15
|
+
*
|
|
16
|
+
* @returns Array of resolved skill sources with absolute paths.
|
|
17
|
+
* @note Impure
|
|
18
|
+
*/
|
|
19
|
+
export default function resolveSkillSources(): SkillSource[] {
|
|
20
|
+
const sources: SkillSource[] = [];
|
|
21
|
+
const resolved = resolvePackages();
|
|
22
|
+
|
|
23
|
+
for (const { pkg, dir } of resolved) {
|
|
24
|
+
const def = PACKAGES.find((p) => p.pkg === pkg);
|
|
25
|
+
if (!def?.skills) continue;
|
|
26
|
+
|
|
27
|
+
sources.push({
|
|
28
|
+
dir: join(dir, def.skills),
|
|
29
|
+
packageName: pkg,
|
|
30
|
+
});
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
return sources;
|
|
34
|
+
}
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
/** @module Skill domain -- CLI commands and operations for `pragma skill`. */
|
|
2
|
+
|
|
3
|
+
import type { CommandDefinition } from "@canonical/cli-core";
|
|
4
|
+
import type { PragmaContext } from "../shared/context.js";
|
|
5
|
+
import { listCommand } from "./commands/index.js";
|
|
6
|
+
|
|
7
|
+
/**
|
|
8
|
+
* Return all skill command definitions.
|
|
9
|
+
*
|
|
10
|
+
* @param ctx - Pragma context providing cwd and formatter selection.
|
|
11
|
+
* @returns An array of CommandDefinitions for the skill subcommands.
|
|
12
|
+
*/
|
|
13
|
+
export function commands(ctx: PragmaContext): CommandDefinition[] {
|
|
14
|
+
return [listCommand(ctx)];
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
export { specs as mcpSpecs } from "./mcp/index.js";
|
|
18
|
+
export type { SkillListResult } from "./operations/index.js";
|
|
19
|
+
export { discoverSkills, listSkills } from "./operations/index.js";
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* MCP tool specs for the skill domain.
|
|
3
|
+
*/
|
|
4
|
+
|
|
5
|
+
import type { ToolSpec } from "../../shared/ToolSpec.js";
|
|
6
|
+
import { listFormatters as skillListFmt } from "../formatters/index.js";
|
|
7
|
+
import { listSkills } from "../operations/index.js";
|
|
8
|
+
|
|
9
|
+
const specs: readonly ToolSpec[] = [
|
|
10
|
+
{
|
|
11
|
+
name: "skill_list",
|
|
12
|
+
description: "List available agent skills from design system packages.",
|
|
13
|
+
params: {
|
|
14
|
+
condensed: {
|
|
15
|
+
type: "boolean",
|
|
16
|
+
description: "Token-optimized output",
|
|
17
|
+
optional: true,
|
|
18
|
+
},
|
|
19
|
+
},
|
|
20
|
+
readOnly: true,
|
|
21
|
+
async execute(rt, { condensed }) {
|
|
22
|
+
const { skills, sources } = await listSkills(rt.cwd);
|
|
23
|
+
|
|
24
|
+
if (condensed) {
|
|
25
|
+
const text = skillListFmt.llm({ skills, sources, detailed: false });
|
|
26
|
+
return {
|
|
27
|
+
condensed: true,
|
|
28
|
+
text,
|
|
29
|
+
tokens: `~${Math.ceil(text.length / 4)}`,
|
|
30
|
+
};
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
return { data: { skills, sources }, meta: { count: skills.length } };
|
|
34
|
+
},
|
|
35
|
+
},
|
|
36
|
+
];
|
|
37
|
+
|
|
38
|
+
export default specs;
|