@nospt/plugin-dev-ai-hub-common 1.0.2 → 1.1.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 CHANGED
@@ -2,7 +2,7 @@
2
2
 
3
3
  Isomorphic contracts shared by the **Dev AI Hub** frontend and backend plugins.
4
4
 
5
- > **Requires Backstage 1.51 or later.** The `AiResource` kind comes from Backstage's own alpha catalog module, which is not available before that.
5
+ > **Requires Backstage 1.54 or later.** The `AiResource` kind comes from Backstage's own alpha catalog module, and these packages depend on `@backstage/catalog-model@^1.10.0` — first shipped in 1.54.0, along with the `plugin` and `marketplace` subtypes.
6
6
 
7
7
  **You do not install this package directly.** It arrives as a dependency of [`@nospt/plugin-dev-ai-hub`](https://www.npmjs.com/package/@nospt/plugin-dev-ai-hub) and [`@nospt/plugin-dev-ai-hub-backend`](https://www.npmjs.com/package/@nospt/plugin-dev-ai-hub-backend), and the three publish in lockstep at the same version.
8
8
 
@@ -30,13 +30,15 @@ interface ResourceSummary {
30
30
  frameworks: string[]; // "claude-code", "github-copilot", …
31
31
  version?: string;
32
32
  kind: string;
33
- childCount?: number; // unpopulated until containment lands
33
+ children: string[]; // renderable container members (spec.skills/spec.plugins)
34
+ parents: string[]; // in-memory inverse of children
35
+ childCount?: number; // = children.length
34
36
  helpText?: string;
35
37
  annotations: Record<string, string>;
36
38
  }
37
39
  ```
38
40
 
39
- `toResourceSummary(entity)` is the `Entity` → `ResourceSummary` mapping, exported here so the backend router and the test fixtures cannot disagree about it.
41
+ `toResourceSummary(entity)` maps one `Entity`; `toResourceSummaries(entities)` maps a whole catalog read and resolves containment in both directions (ADR-0015). Both are exported here so the backend router and the test fixtures cannot disagree about them.
40
42
 
41
43
  ## Also in here
42
44
 
package/dist/index.cjs.js CHANGED
@@ -9,12 +9,13 @@ var toResourceSummary = require('./toResourceSummary.cjs.js');
9
9
  exports.AGENT_LINK_CAPABLE_FRAMEWORKS = resources.AGENT_LINK_CAPABLE_FRAMEWORKS;
10
10
  exports.ANNOTATION_COMPATIBLE_FRAMEWORKS = resources.ANNOTATION_COMPATIBLE_FRAMEWORKS;
11
11
  exports.ANNOTATION_HELP = resources.ANNOTATION_HELP;
12
- exports.ANNOTATION_VERSION = resources.ANNOTATION_VERSION;
13
12
  exports.DEVAIHUB_ANNOTATION_PREFIX = resources.DEVAIHUB_ANNOTATION_PREFIX;
14
13
  exports.INSTALLABLE_FRAMEWORKS = resources.INSTALLABLE_FRAMEWORKS;
15
14
  exports.KNOWN_FRAMEWORKS = resources.KNOWN_FRAMEWORKS;
16
15
  exports.MARKETPLACE_CAPABLE_FRAMEWORKS = resources.MARKETPLACE_CAPABLE_FRAMEWORKS;
16
+ exports.MARKETPLACE_MEMBER_TYPES = resources.MARKETPLACE_MEMBER_TYPES;
17
17
  exports.MCP_LINK_CAPABLE_FRAMEWORKS = resources.MCP_LINK_CAPABLE_FRAMEWORKS;
18
+ exports.PLUGIN_MEMBER_TYPES = resources.PLUGIN_MEMBER_TYPES;
18
19
  exports.PROMPT_LINK_CAPABLE_FRAMEWORKS = resources.PROMPT_LINK_CAPABLE_FRAMEWORKS;
19
20
  exports.RESOURCE_TYPES = resources.RESOURCE_TYPES;
20
21
  exports.RESOURCE_TYPE_REGISTRY = resources.RESOURCE_TYPE_REGISTRY;
@@ -32,11 +33,13 @@ exports.getPromptInstallLinks = resources.getPromptInstallLinks;
32
33
  exports.getResourceInstallTarget = resources.getResourceInstallTarget;
33
34
  exports.hasCopyableBody = resources.hasCopyableBody;
34
35
  exports.hasDownloadableArtifact = resources.hasDownloadableArtifact;
36
+ exports.isRenderableContainment = resources.isRenderableContainment;
35
37
  exports.isResourceType = resources.isResourceType;
36
38
  exports.normalizeFramework = resources.normalizeFramework;
37
39
  exports.resolveCapableFrameworks = resources.resolveCapableFrameworks;
38
40
  exports.TelemetryActionEnum = telemetry.TelemetryActionEnum;
39
41
  exports.TelemetryEventInputSchema = telemetry.TelemetryEventInputSchema;
40
42
  exports.isParseableEntityRef = toResourceSummary.isParseableEntityRef;
43
+ exports.toResourceSummaries = toResourceSummary.toResourceSummaries;
41
44
  exports.toResourceSummary = toResourceSummary.toResourceSummary;
42
45
  //# sourceMappingURL=index.cjs.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"index.cjs.js","sources":[],"sourcesContent":[],"names":[],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;"}
1
+ {"version":3,"file":"index.cjs.js","sources":[],"sourcesContent":[],"names":[],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;"}
package/dist/index.d.ts CHANGED
@@ -12,12 +12,35 @@ import { Entity } from '@backstage/catalog-model';
12
12
  declare const RESOURCE_TYPES: readonly ["skill", "agent", "hook", "mcp-config", "plugin", "marketplace"];
13
13
  type ResourceType = (typeof RESOURCE_TYPES)[number];
14
14
  declare function isResourceType(value: unknown): value is ResourceType;
15
+ /**
16
+ * The AiResource types a `plugin` may contain — restricted to the component
17
+ * kinds a real Claude Code / GitHub Copilot plugin manifest allows: `skills`,
18
+ * `agents`, `hooks` and `mcpServers` (schemastore `claude-code-plugin` /
19
+ * `claude-code-marketplace.json`). A plugin's other manifest components
20
+ * (`commands`, `lspServers`, `outputStyles`, `themes`, `channels`, `monitors`)
21
+ * have no AiResource type, so they are never members here; other plugins are
22
+ * `dependencies`, not containment.
23
+ */
24
+ declare const PLUGIN_MEMBER_TYPES: readonly ResourceType[];
25
+ /**
26
+ * The AiResource types a `marketplace` may contain: `plugin` only. A real
27
+ * `.claude-plugin/marketplace.json` `plugins` array lists plugins and nothing
28
+ * else (required by the schema Claude Code and Copilot CLI both read).
29
+ */
30
+ declare const MARKETPLACE_MEMBER_TYPES: readonly ResourceType[];
31
+ /**
32
+ * Whether a container of `parent` type renders a child of `child` type as a
33
+ * member — restricted to what a real Claude/Copilot plugin or marketplace
34
+ * manifest permits (ADR-0010/0015). A `marketplace` contains `plugin` only; a
35
+ * `plugin` contains the component types DevAI Hub models. Any other pairing is
36
+ * silently not rendered. This is a rendering convention, not catalog
37
+ * validation (upstream `allowedKinds` is the broad `["AiResource"]`).
38
+ */
39
+ declare function isRenderableContainment(parent: ResourceType, child: ResourceType): boolean;
15
40
  /** Annotation prefix owned by DevAI Hub. */
16
41
  declare const DEVAIHUB_ANNOTATION_PREFIX = "devaihub.io";
17
42
  /** Comma-separated compatible-framework tokens (non-skill read path, ADR-0003). */
18
43
  declare const ANNOTATION_COMPATIBLE_FRAMEWORKS = "devaihub.io/compatible-frameworks";
19
- /** Optional semantic version string, displayed on cards. */
20
- declare const ANNOTATION_VERSION = "devaihub.io/version";
21
44
  /** Optional free-form help text shown on the detail panel. */
22
45
  declare const ANNOTATION_HELP = "devaihub.io/help";
23
46
  /** Known framework tokens; unknown tokens pass through as-is (ADR-0003). */
@@ -99,7 +122,20 @@ interface ResourceSummary {
99
122
  frameworks: string[];
100
123
  version?: string;
101
124
  kind: string;
102
- /** `plugin` dependsOn count; unpopulated until containment lands (issue #32). */
125
+ /**
126
+ * Entity refs this container declares as its contents — a `marketplace`'s
127
+ * `spec.plugins`, a `plugin`'s `spec.skills`; empty for leaf types. Filtered
128
+ * to the caller's visible set and to the child types the container renders
129
+ * (ADR-0015). The frontend resolves each ref against the summaries it holds.
130
+ */
131
+ children: string[];
132
+ /**
133
+ * Entity refs of the containers that declare this resource — the in-memory
134
+ * inverse of `children`, built over the same caller-visible catalog read
135
+ * (ADR-0015). A resource can belong to several containers.
136
+ */
137
+ parents: string[];
138
+ /** Count of `children` (their declared, caller-visible members). */
103
139
  childCount?: number;
104
140
  helpText?: string;
105
141
  annotations: Record<string, string>;
@@ -334,6 +370,26 @@ type TelemetryCounts = Record<TelemetryAction, number>;
334
370
  */
335
371
  declare function isParseableEntityRef(ref: string): boolean;
336
372
  declare function toResourceSummary(entity: Entity): ResourceSummary | undefined;
373
+ /**
374
+ * Map a whole caller-visible catalog read to summaries, resolving containment
375
+ * in both directions (ADR-0015). This is the function the served payload uses;
376
+ * `toResourceSummary` alone cannot, because containment is only knowable across
377
+ * the whole set.
378
+ *
379
+ * Upstream imposes no type restriction on membership — `spec.skills` /
380
+ * `spec.plugins` carry `allowedKinds: ["AiResource"]`, so any AiResource type
381
+ * is a legal member. DevAI Hub narrows that to its rendering convention
382
+ * (ADR-0010/0015): a `marketplace` renders `plugin` children only, a `plugin`
383
+ * renders `skill`/`agent`/`hook`/`mcp-config`. That convention is applied here,
384
+ * once, so `children`, `childCount`, `parents`, the card chip and the detail
385
+ * sections cannot disagree:
386
+ *
387
+ * - `children` keeps only refs that resolve within this read **and** whose type
388
+ * the container legitimately contains — so a card never counts, nor a detail
389
+ * panel links, a hidden member (ADR-0006) or an off-convention one.
390
+ * - `parents` is the in-memory inverse of those filtered children.
391
+ */
392
+ declare function toResourceSummaries(entities: Entity[]): ResourceSummary[];
337
393
 
338
- export { AGENT_LINK_CAPABLE_FRAMEWORKS, ANNOTATION_COMPATIBLE_FRAMEWORKS, ANNOTATION_HELP, ANNOTATION_VERSION, DEVAIHUB_ANNOTATION_PREFIX, INSTALLABLE_FRAMEWORKS, KNOWN_FRAMEWORKS, MARKETPLACE_CAPABLE_FRAMEWORKS, MCP_LINK_CAPABLE_FRAMEWORKS, PROMPT_LINK_CAPABLE_FRAMEWORKS, RESOURCE_TYPES, RESOURCE_TYPE_REGISTRY, TelemetryActionEnum, TelemetryEventInputSchema, expandInstallFrameworks, getAgentInstallLinks, getBodyShape, getFrameworks, getInstallSteps, getMarketplaceAddCommands, getMarketplaceInstallTemplate, getMarketplaceRepoSlug, getMarketplaceTeamSnippet, getMcpInstallLinks, getPromptInstallLinks, getResourceInstallTarget, hasCopyableBody, hasDownloadableArtifact, isParseableEntityRef, isResourceType, normalizeFramework, resolveCapableFrameworks, toResourceSummary };
394
+ export { AGENT_LINK_CAPABLE_FRAMEWORKS, ANNOTATION_COMPATIBLE_FRAMEWORKS, ANNOTATION_HELP, DEVAIHUB_ANNOTATION_PREFIX, INSTALLABLE_FRAMEWORKS, KNOWN_FRAMEWORKS, MARKETPLACE_CAPABLE_FRAMEWORKS, MARKETPLACE_MEMBER_TYPES, MCP_LINK_CAPABLE_FRAMEWORKS, PLUGIN_MEMBER_TYPES, PROMPT_LINK_CAPABLE_FRAMEWORKS, RESOURCE_TYPES, RESOURCE_TYPE_REGISTRY, TelemetryActionEnum, TelemetryEventInputSchema, expandInstallFrameworks, getAgentInstallLinks, getBodyShape, getFrameworks, getInstallSteps, getMarketplaceAddCommands, getMarketplaceInstallTemplate, getMarketplaceRepoSlug, getMarketplaceTeamSnippet, getMcpInstallLinks, getPromptInstallLinks, getResourceInstallTarget, hasCopyableBody, hasDownloadableArtifact, isParseableEntityRef, isRenderableContainment, isResourceType, normalizeFramework, resolveCapableFrameworks, toResourceSummaries, toResourceSummary };
339
395
  export type { AiResourceEntityLike, BodyShape, FrameworkToken, InstallMode, MarketplaceAddCommand, ResourceDeepLink, ResourceInstallStep, ResourceInstallTarget, ResourceListResponse, ResourceSummary, ResourceType, ResourceTypeIcon, ResourceTypeInfo, TelemetryAction, TelemetryCounts, TelemetryEventInput };
package/dist/index.esm.js CHANGED
@@ -1,4 +1,4 @@
1
- export { AGENT_LINK_CAPABLE_FRAMEWORKS, ANNOTATION_COMPATIBLE_FRAMEWORKS, ANNOTATION_HELP, ANNOTATION_VERSION, DEVAIHUB_ANNOTATION_PREFIX, INSTALLABLE_FRAMEWORKS, KNOWN_FRAMEWORKS, MARKETPLACE_CAPABLE_FRAMEWORKS, MCP_LINK_CAPABLE_FRAMEWORKS, PROMPT_LINK_CAPABLE_FRAMEWORKS, RESOURCE_TYPES, RESOURCE_TYPE_REGISTRY, expandInstallFrameworks, getAgentInstallLinks, getBodyShape, getFrameworks, getInstallSteps, getMarketplaceAddCommands, getMarketplaceInstallTemplate, getMarketplaceRepoSlug, getMarketplaceTeamSnippet, getMcpInstallLinks, getPromptInstallLinks, getResourceInstallTarget, hasCopyableBody, hasDownloadableArtifact, isResourceType, normalizeFramework, resolveCapableFrameworks } from './resources.esm.js';
1
+ export { AGENT_LINK_CAPABLE_FRAMEWORKS, ANNOTATION_COMPATIBLE_FRAMEWORKS, ANNOTATION_HELP, DEVAIHUB_ANNOTATION_PREFIX, INSTALLABLE_FRAMEWORKS, KNOWN_FRAMEWORKS, MARKETPLACE_CAPABLE_FRAMEWORKS, MARKETPLACE_MEMBER_TYPES, MCP_LINK_CAPABLE_FRAMEWORKS, PLUGIN_MEMBER_TYPES, PROMPT_LINK_CAPABLE_FRAMEWORKS, RESOURCE_TYPES, RESOURCE_TYPE_REGISTRY, expandInstallFrameworks, getAgentInstallLinks, getBodyShape, getFrameworks, getInstallSteps, getMarketplaceAddCommands, getMarketplaceInstallTemplate, getMarketplaceRepoSlug, getMarketplaceTeamSnippet, getMcpInstallLinks, getPromptInstallLinks, getResourceInstallTarget, hasCopyableBody, hasDownloadableArtifact, isRenderableContainment, isResourceType, normalizeFramework, resolveCapableFrameworks } from './resources.esm.js';
2
2
  export { TelemetryActionEnum, TelemetryEventInputSchema } from './telemetry.esm.js';
3
- export { isParseableEntityRef, toResourceSummary } from './toResourceSummary.esm.js';
3
+ export { isParseableEntityRef, toResourceSummaries, toResourceSummary } from './toResourceSummary.esm.js';
4
4
  //# sourceMappingURL=index.esm.js.map
@@ -11,9 +11,20 @@ const RESOURCE_TYPES = [
11
11
  function isResourceType(value) {
12
12
  return typeof value === "string" && RESOURCE_TYPES.includes(value);
13
13
  }
14
+ const PLUGIN_MEMBER_TYPES = [
15
+ "skill",
16
+ "agent",
17
+ "hook",
18
+ "mcp-config"
19
+ ];
20
+ const MARKETPLACE_MEMBER_TYPES = ["plugin"];
21
+ function isRenderableContainment(parent, child) {
22
+ if (parent === "marketplace") return MARKETPLACE_MEMBER_TYPES.includes(child);
23
+ if (parent === "plugin") return PLUGIN_MEMBER_TYPES.includes(child);
24
+ return false;
25
+ }
14
26
  const DEVAIHUB_ANNOTATION_PREFIX = "devaihub.io";
15
27
  const ANNOTATION_COMPATIBLE_FRAMEWORKS = `${DEVAIHUB_ANNOTATION_PREFIX}/compatible-frameworks`;
16
- const ANNOTATION_VERSION = `${DEVAIHUB_ANNOTATION_PREFIX}/version`;
17
28
  const ANNOTATION_HELP = `${DEVAIHUB_ANNOTATION_PREFIX}/help`;
18
29
  const KNOWN_FRAMEWORKS = [
19
30
  "github-copilot",
@@ -455,12 +466,13 @@ function getMarketplaceTeamSnippet(marketplaceName, repoSlug) {
455
466
  exports.AGENT_LINK_CAPABLE_FRAMEWORKS = AGENT_LINK_CAPABLE_FRAMEWORKS;
456
467
  exports.ANNOTATION_COMPATIBLE_FRAMEWORKS = ANNOTATION_COMPATIBLE_FRAMEWORKS;
457
468
  exports.ANNOTATION_HELP = ANNOTATION_HELP;
458
- exports.ANNOTATION_VERSION = ANNOTATION_VERSION;
459
469
  exports.DEVAIHUB_ANNOTATION_PREFIX = DEVAIHUB_ANNOTATION_PREFIX;
460
470
  exports.INSTALLABLE_FRAMEWORKS = INSTALLABLE_FRAMEWORKS;
461
471
  exports.KNOWN_FRAMEWORKS = KNOWN_FRAMEWORKS;
462
472
  exports.MARKETPLACE_CAPABLE_FRAMEWORKS = MARKETPLACE_CAPABLE_FRAMEWORKS;
473
+ exports.MARKETPLACE_MEMBER_TYPES = MARKETPLACE_MEMBER_TYPES;
463
474
  exports.MCP_LINK_CAPABLE_FRAMEWORKS = MCP_LINK_CAPABLE_FRAMEWORKS;
475
+ exports.PLUGIN_MEMBER_TYPES = PLUGIN_MEMBER_TYPES;
464
476
  exports.PROMPT_LINK_CAPABLE_FRAMEWORKS = PROMPT_LINK_CAPABLE_FRAMEWORKS;
465
477
  exports.RESOURCE_TYPES = RESOURCE_TYPES;
466
478
  exports.RESOURCE_TYPE_REGISTRY = RESOURCE_TYPE_REGISTRY;
@@ -478,6 +490,7 @@ exports.getPromptInstallLinks = getPromptInstallLinks;
478
490
  exports.getResourceInstallTarget = getResourceInstallTarget;
479
491
  exports.hasCopyableBody = hasCopyableBody;
480
492
  exports.hasDownloadableArtifact = hasDownloadableArtifact;
493
+ exports.isRenderableContainment = isRenderableContainment;
481
494
  exports.isResourceType = isResourceType;
482
495
  exports.normalizeFramework = normalizeFramework;
483
496
  exports.resolveCapableFrameworks = resolveCapableFrameworks;
@@ -1 +1 @@
1
- {"version":3,"file":"resources.cjs.js","sources":["../src/resources.ts"],"sourcesContent":["/**\n * The v2 read contract: AiResource vocabulary, the flat ResourceSummary\n * returned by the backend, and the shared framework resolver (ADR-0003).\n *\n * The legacy asset-model types in `types.ts` coexist with this module until\n * the legacy silo is deleted (slice 8, issue #34).\n */\n\n/** The six canonical `spec.type` values of an AiResource (ADR-0003, ADR-0010). */\nexport const RESOURCE_TYPES = [\n 'skill',\n 'agent',\n 'hook',\n 'mcp-config',\n 'plugin',\n 'marketplace',\n] as const;\n\nexport type ResourceType = (typeof RESOURCE_TYPES)[number];\n\nexport function isResourceType(value: unknown): value is ResourceType {\n return (\n typeof value === 'string' &&\n (RESOURCE_TYPES as readonly string[]).includes(value)\n );\n}\n\n/** Annotation prefix owned by DevAI Hub. */\nexport const DEVAIHUB_ANNOTATION_PREFIX = 'devaihub.io';\n\n/** Comma-separated compatible-framework tokens (non-skill read path, ADR-0003). */\nexport const ANNOTATION_COMPATIBLE_FRAMEWORKS = `${DEVAIHUB_ANNOTATION_PREFIX}/compatible-frameworks`;\n\n/** Optional semantic version string, displayed on cards. */\nexport const ANNOTATION_VERSION = `${DEVAIHUB_ANNOTATION_PREFIX}/version`;\n\n/** Optional free-form help text shown on the detail panel. */\nexport const ANNOTATION_HELP = `${DEVAIHUB_ANNOTATION_PREFIX}/help`;\n\n/** Known framework tokens; unknown tokens pass through as-is (ADR-0003). */\nexport const KNOWN_FRAMEWORKS = [\n 'github-copilot',\n 'claude-code',\n 'cursor',\n 'google-gemini',\n 'opencode',\n 'all',\n] as const;\n\nexport type FrameworkToken = (typeof KNOWN_FRAMEWORKS)[number];\n\nconst FRAMEWORK_ALIASES: Record<string, FrameworkToken> = {\n copilot: 'github-copilot',\n claude: 'claude-code',\n gemini: 'google-gemini',\n};\n\n/**\n * Normalise a framework token: trim, lowercase, resolve aliases\n * (`claude` → `claude-code`). Unknown tokens pass through literally.\n */\nexport function normalizeFramework(token: string): string {\n const t = token.trim().toLowerCase();\n return FRAMEWORK_ALIASES[t] ?? t;\n}\n\n/**\n * The minimal structural shape of an AiResource entity that the read helpers\n * need. A real `@backstage/catalog-model` `Entity` satisfies it, but keeping\n * the shape local means `-common` (and therefore the frontend) never depends\n * on catalog packages (architecture.md).\n */\nexport interface AiResourceEntityLike {\n metadata: {\n annotations?: Record<string, string>;\n };\n spec?: {\n type?: unknown;\n /** Native compatible-frameworks field, honoured regardless of `spec.type`. */\n agents?: unknown;\n [key: string]: unknown;\n };\n}\n\n/**\n * Resolve the compatible frameworks of an AiResource (ADR-0003):\n * 1. a non-empty `spec.agents` → the native field, for any `spec.type`;\n * 2. otherwise → the `devaihub.io/compatible-frameworks` annotation\n * (also the fallback when `spec.agents` is empty/absent);\n * 3. otherwise → `[]`.\n * Tokens are normalised; unknown tokens pass through. `spec.agents` is\n * preferred over the annotation because it lives on the entity itself\n * rather than in an annotation namespace we may retire.\n */\nexport function getFrameworks(entity: AiResourceEntityLike): string[] {\n if (Array.isArray(entity.spec?.agents)) {\n const agents = entity.spec.agents\n .filter((a): a is string => typeof a === 'string')\n .map(normalizeFramework)\n .filter(Boolean);\n if (agents.length > 0) {\n return dedupe(agents);\n }\n }\n\n const raw = entity.metadata.annotations?.[ANNOTATION_COMPATIBLE_FRAMEWORKS];\n if (!raw) {\n return [];\n }\n return dedupe(raw.split(',').map(normalizeFramework).filter(Boolean));\n}\n\nfunction dedupe(tokens: string[]): string[] {\n return Array.from(new Set(tokens));\n}\n\n/**\n * Icon identifiers for the type→card registry. `-common` is imported by the\n * backend, so it stays React-free: the frontend maps these identifiers to\n * actual icon components.\n */\nexport type ResourceTypeIcon =\n | 'tools'\n | 'robot'\n | 'flash'\n | 'plug'\n | 'puzzle'\n | 'store';\n\nexport interface ResourceTypeInfo {\n type: ResourceType;\n /** Singular display label, e.g. \"Skill\" (card type caption). */\n label: string;\n /** Plural display label, e.g. \"Skills\" (stat tiles, filters). */\n pluralLabel: string;\n /** Icon identifier resolved to a component by the frontend. */\n icon: ResourceTypeIcon;\n /**\n * Colour role name; the frontend resolves it to the plugin-owned\n * `--devaihub-type-<role>` custom properties (ADR-0008, NOS palette).\n */\n colorRole: ResourceType;\n}\n\n/**\n * The typed type→card registry (ADR-0003/ADR-0008): producer vocabulary and\n * consumer rendering share this single source, so they cannot drift. Adding\n * a type is one entry here plus one CSS token pair in the frontend.\n */\nexport const RESOURCE_TYPE_REGISTRY: Record<ResourceType, ResourceTypeInfo> = {\n skill: {\n type: 'skill',\n label: 'Skill',\n pluralLabel: 'Skills',\n icon: 'tools',\n colorRole: 'skill',\n },\n agent: {\n type: 'agent',\n label: 'Agent',\n pluralLabel: 'Agents',\n icon: 'robot',\n colorRole: 'agent',\n },\n hook: {\n type: 'hook',\n label: 'Hook',\n pluralLabel: 'Hooks',\n icon: 'flash',\n colorRole: 'hook',\n },\n 'mcp-config': {\n type: 'mcp-config',\n label: 'MCP Config',\n pluralLabel: 'MCP Configs',\n icon: 'plug',\n colorRole: 'mcp-config',\n },\n plugin: {\n type: 'plugin',\n label: 'Plugin',\n pluralLabel: 'Plugins',\n icon: 'puzzle',\n colorRole: 'plugin',\n },\n marketplace: {\n type: 'marketplace',\n label: 'Marketplace',\n pluralLabel: 'Marketplaces',\n icon: 'store',\n colorRole: 'marketplace',\n },\n};\n\n/**\n * The flat contract the backend returns to the frontend. The frontend never\n * sees a raw Backstage `Entity` — only this (architecture.md).\n */\nexport interface ResourceSummary {\n entityRef: string;\n name: string;\n title?: string;\n description?: string;\n tags: string[];\n type: ResourceType;\n lifecycle: string;\n owner?: string;\n sourceLocation?: string;\n frameworks: string[];\n version?: string;\n kind: string;\n /** `plugin` dependsOn count; unpopulated until containment lands (issue #32). */\n childCount?: number;\n helpText?: string;\n annotations: Record<string, string>;\n}\n\nexport interface ResourceListResponse {\n items: ResourceSummary[];\n}\n\n/**\n * The shape of a resource's body (CONTEXT.md: bodies are type-shaped, not\n * uniformly markdown). Drives detail-panel rendering: markdown is rendered,\n * json is pretty-printed and syntax-highlighted (ADR-0014). `plugin` and\n * `marketplace` bodies are the real Claude Code `plugin.json` /\n * `marketplace.json` manifest — not a hand-authored pointer doc, superseding\n * ADR-0010's markdown-only body for `marketplace`.\n */\nexport type BodyShape = 'markdown' | 'json';\n\nexport function getBodyShape(type: ResourceType): BodyShape {\n return type === 'mcp-config' || type === 'plugin' || type === 'marketplace'\n ? 'json'\n : 'markdown';\n}\n\n/**\n * Whether downloading the body delivers the artifact itself. True for every\n * type: `plugin`/`marketplace` bodies are now the real manifest JSON, so a\n * download delivers that file itself rather than a non-actionable pointer\n * doc (ADR-0014, superseding ADR-0009's amendment and ADR-0010 on this\n * point).\n */\nexport function hasDownloadableArtifact(_type: ResourceType): boolean {\n return true;\n}\n\n/**\n * Whether copying the body to the clipboard is a meaningful install action.\n * True for every type: `marketplace`'s body is now the real manifest JSON,\n * not an instructions doc, so copying it is no longer a misrepresentation\n * (ADR-0014, superseding ADR-0010 on this point).\n */\nexport function hasCopyableBody(_type: ResourceType): boolean {\n return true;\n}\n\n/**\n * How a body reaches its install path.\n *\n * `drop-in` — the body becomes the file (or directory) at `path`, which is\n * the resource's own; writing it touches nothing else.\n *\n * `merge` — `path` is a shared settings file the user already owns, and the\n * body is a fragment to merge into it. The distinction is not cosmetic:\n * following a `merge` target as if it were a `drop-in` overwrites the user's\n * existing configuration, so the UI must never present the two identically.\n */\nexport type InstallMode = 'drop-in' | 'merge';\n\nexport interface ResourceInstallTarget {\n /** Workspace-relative path. */\n path: string;\n mode: InstallMode;\n}\n\n/**\n * The concrete framework tokens a body can be installed into — `all` is a\n * wildcard over these, never a destination itself.\n */\nexport const INSTALLABLE_FRAMEWORKS = [\n 'claude-code',\n 'github-copilot',\n 'google-gemini',\n 'cursor',\n 'opencode',\n] as const;\n\n/**\n * Convention table: (type, framework) → where the body goes and how.\n * A missing entry means the pair has no filesystem convention — `plugin` and\n * `marketplace` bodies are pointers that install through their framework\n * (ADR-0009, ADR-0010), so they carry install commands rather than paths.\n */\nconst INSTALL_PATHS: Record<\n ResourceType,\n Record<string, { mode: InstallMode; path: (name: string) => string }>\n> = {\n // Every host reads a skill as a self-contained directory. Copilot also\n // picks up `.claude/skills`, but each tool's own path is what we recommend;\n // `.agents/skills` is the cross-tool alias Copilot and Gemini both honour,\n // which makes it the right answer for an unrecognised framework.\n skill: {\n 'claude-code': { mode: 'drop-in', path: name => `.claude/skills/${name}/` },\n 'github-copilot': {\n mode: 'drop-in',\n path: name => `.github/skills/${name}/`,\n },\n 'google-gemini': {\n mode: 'drop-in',\n path: name => `.gemini/skills/${name}/`,\n },\n cursor: { mode: 'drop-in', path: name => `.cursor/skills/${name}/` },\n opencode: { mode: 'drop-in', path: name => `.opencode/skills/${name}/` },\n default: { mode: 'drop-in', path: name => `.agents/skills/${name}/` },\n },\n agent: {\n 'claude-code': {\n mode: 'drop-in',\n path: name => `.claude/agents/${name}.md`,\n },\n 'github-copilot': {\n mode: 'drop-in',\n path: name => `.github/agents/${name}.agent.md`,\n },\n 'google-gemini': {\n mode: 'drop-in',\n path: name => `.gemini/agents/${name}.md`,\n },\n cursor: { mode: 'drop-in', path: name => `.cursor/rules/${name}.mdc` },\n opencode: { mode: 'drop-in', path: name => `.opencode/agents/${name}.md` },\n default: { mode: 'drop-in', path: name => `.ai/agents/${name}.md` },\n },\n // All four hosts have a hook system, but only Copilot gives each hook its\n // own file; the rest register hooks inside a shared settings document.\n // OpenCode has no declarative hook config at all — hooks are exclusively\n // JS/TS plugin modules — so it deliberately carries no entry here, the same\n // way it carries no `default`.\n hook: {\n 'claude-code': { mode: 'merge', path: () => `.claude/settings.json` },\n 'github-copilot': {\n mode: 'drop-in',\n path: name => `.github/hooks/${name}.json`,\n },\n 'google-gemini': { mode: 'merge', path: () => `.gemini/settings.json` },\n cursor: { mode: 'merge', path: () => `.cursor/hooks.json` },\n },\n 'mcp-config': {\n 'claude-code': { mode: 'merge', path: () => `.mcp.json` },\n 'github-copilot': { mode: 'merge', path: () => `.vscode/mcp.json` },\n 'google-gemini': { mode: 'merge', path: () => `.gemini/settings.json` },\n cursor: { mode: 'merge', path: () => `.cursor/mcp.json` },\n opencode: { mode: 'merge', path: () => `opencode.json` },\n },\n plugin: {},\n marketplace: {},\n};\n\n/**\n * Where a resource's body installs for one framework, and whether that path\n * is the body's own file or a settings file to merge into. Returns\n * `undefined` when the (type, framework) pair has no filesystem convention.\n */\nexport function getResourceInstallTarget(\n type: ResourceType,\n framework: string,\n name: string,\n): ResourceInstallTarget | undefined {\n const conventions = INSTALL_PATHS[type];\n const entry =\n conventions[normalizeFramework(framework)] ?? conventions.default;\n return entry ? { path: entry.path(name), mode: entry.mode } : undefined;\n}\n\n/**\n * The frameworks to show install paths for. An empty list means the resource\n * declared no compatibility and gets the neutral `default` convention; `all`\n * is a wildcard and expands to every installable host, so it never collapses\n * to a single row — or, for types with no `default`, to no rows at all.\n */\nexport function expandInstallFrameworks(frameworks: string[]): string[] {\n const normalized = dedupe(frameworks.map(normalizeFramework));\n if (normalized.length === 0) {\n return ['default'];\n }\n if (normalized.includes('all')) {\n return [...INSTALLABLE_FRAMEWORKS];\n }\n return normalized;\n}\n\n/*\n * Marketplace journey helpers (ADR-0010). A marketplace installs in two\n * steps — register the catalog with the AI tool, then install plugins from\n * it. Both Claude Code and Copilot CLI read the same\n * `.claude-plugin/marketplace.json` format and address the repo as\n * `owner/repo`; the plugin derives that slug from the mandatory\n * `source-location` (the producer contract requires the body doc to live\n * inside the marketplace repo). No derivation → the UI falls back to the\n * rendered body, which stays canonical.\n */\n\n/** Frameworks with a marketplace-add concept (Cursor/Gemini have none). */\nexport const MARKETPLACE_CAPABLE_FRAMEWORKS = [\n 'claude-code',\n 'github-copilot',\n] as const;\n\n/**\n * Narrow a resource's declared frameworks to those a given journey can\n * actually launch. A deep link is a claim about compatibility: offering\n * \"Install in Claude\" for a resource that never declared `claude-code` tells\n * the user something untrue about the resource.\n *\n * `all` and an empty list both mean \"unrestricted\" and expand to every\n * capable host. Anything else is intersected, so a declared framework with no\n * handler (Gemini) — or an unknown token — yields no links rather than\n * silently widening to every host.\n */\nexport function resolveCapableFrameworks(\n declared: string[],\n capable: readonly string[],\n): string[] {\n const normalized = dedupe(declared.map(normalizeFramework));\n if (normalized.length === 0 || normalized.includes('all')) {\n return [...capable];\n }\n return normalized.filter(f => capable.includes(f));\n}\n\n/**\n * Derive the `owner/repo` slug from a marketplace's `source-location`.\n * GitHub URLs only — other hosts return `undefined` (body-only fallback).\n */\nexport function getMarketplaceRepoSlug(\n sourceLocation: string | undefined,\n): string | undefined {\n if (!sourceLocation) {\n return undefined;\n }\n let url: URL;\n try {\n url = new URL(sourceLocation.replace(/^url:/, ''));\n } catch {\n return undefined;\n }\n if (url.hostname !== 'github.com') {\n return undefined;\n }\n const [owner, repo] = url.pathname.split('/').filter(Boolean);\n if (!owner || !repo) {\n return undefined;\n }\n return `${owner}/${repo.replace(/\\.git$/, '')}`;\n}\n\n/**\n * A one-click launcher for an add command: a custom-scheme URL that opens\n * the host app with the command pre-filled in the prompt box. The host\n * never auto-executes it — the user reviews and presses Enter — and an\n * unregistered scheme is a harmless no-op, so these complement the copy\n * button rather than replace it.\n */\nexport interface ResourceDeepLink {\n /** Host application name; render as \"Add in <label>\" / \"Install in <label>\". */\n label: string;\n href: string;\n}\n\nexport interface MarketplaceAddCommand {\n framework: string;\n command: string;\n deepLinks: ResourceDeepLink[];\n}\n\n/**\n * The copyable marketplace-add command per compatible framework. `all`,\n * unknown-only, or empty framework lists expand to every capable framework;\n * frameworks without a marketplace concept produce no row.\n *\n * Claude Code carries a single deep link on the CLI's documented\n * `claude-cli://open?q=` handler, which opens a Claude Code terminal\n * session with the command pre-filled regardless of the user's editor.\n *\n * Copilot's CLI still has no URI scheme, but Copilot's *other* surface does:\n * VS Code 1.113 ships `vscode://chat-plugin/add-marketplace?ref=`, which\n * accepts a plain or base64 `owner/repo` and always shows a confirmation\n * dialog before registering the marketplace (it also dedupes against the\n * user's existing entries). So the Copilot row keeps its terminal command for\n * CLI users *and* gains editor launchers, mirroring how `mcp-config` already\n * maps `github-copilot` onto the VS Code stable/Insiders pair.\n */\nexport function getMarketplaceAddCommands(\n frameworks: string[],\n repoSlug: string,\n): MarketplaceAddCommand[] {\n const capable = resolveCapableFrameworks(\n frameworks,\n MARKETPLACE_CAPABLE_FRAMEWORKS,\n );\n const commands: MarketplaceAddCommand[] = [];\n for (const framework of capable) {\n if (framework === 'claude-code') {\n const command = `/plugin marketplace add ${repoSlug}`;\n commands.push({\n framework,\n command,\n deepLinks: [\n {\n label: 'Claude',\n href: `claude-cli://open?q=${encodeURIComponent(command)}`,\n },\n ],\n });\n } else if (framework === 'github-copilot') {\n const ref = encodeURIComponent(repoSlug);\n commands.push({\n framework,\n command: `copilot plugin marketplace add ${repoSlug}`,\n deepLinks: [\n {\n label: 'VS Code',\n href: `vscode://chat-plugin/add-marketplace?ref=${ref}`,\n },\n {\n label: 'VS Code Insiders',\n href: `vscode-insiders://chat-plugin/add-marketplace?ref=${ref}`,\n },\n ],\n });\n }\n }\n return commands;\n}\n\n/** The raw-file URL for a GitHub blob `source-location`; else `undefined`. */\nfunction getRawGithubFileUrl(\n sourceLocation: string | undefined,\n): string | undefined {\n if (!sourceLocation) {\n return undefined;\n }\n let url: URL;\n try {\n url = new URL(sourceLocation.replace(/^url:/, ''));\n } catch {\n return undefined;\n }\n if (url.hostname !== 'github.com') {\n return undefined;\n }\n const [owner, repo, marker, ref, ...path] = url.pathname\n .split('/')\n .filter(Boolean);\n if (!owner || !repo || marker !== 'blob' || !ref || path.length === 0) {\n return undefined;\n }\n return `https://raw.githubusercontent.com/${owner}/${repo}/${ref}/${path.join(\n '/',\n )}`;\n}\n\n/**\n * A launcher that opens an agent with an install prompt pre-filled. Neither\n * host auto-executes: the user reads the prompt and presses Enter. Claude Code\n * and Cursor are the two tools exposing a generic prompt URI — Copilot's\n * editor has purpose-built routes but no prompt handler, and Gemini has\n * neither (its `gemini.google.com/app?prompt=` link drives the web app, which\n * cannot write to a local workspace).\n *\n * Cursor truncates a deeplink at the first raw `&`, so the prompt must be\n * percent-encoded — `encodeURIComponent` handles that. Cursor also caps the\n * URL at ~8000 characters; these prompts are a URL plus a path, far under it.\n */\nfunction claudePromptLink(prompt: string): ResourceDeepLink {\n return {\n label: 'Claude',\n href: `claude-cli://open?q=${encodeURIComponent(prompt)}`,\n };\n}\n\nfunction cursorPromptLink(prompt: string): ResourceDeepLink {\n return {\n label: 'Cursor',\n href: `cursor://anysphere.cursor-deeplink/prompt?text=${encodeURIComponent(\n prompt,\n )}`,\n };\n}\n\n/**\n * Frameworks with a one-click agent-install handler (Gemini has none).\n * OpenCode's Desktop app does register a real `opencode://` scheme, but every\n * route (`open-project`, `new-session`) requires an absolute local\n * `directory` param a web page cannot supply, so it's excluded here too.\n */\nexport const AGENT_LINK_CAPABLE_FRAMEWORKS = [\n 'claude-code',\n 'github-copilot',\n 'cursor',\n] as const;\n\n/** Hosts reachable by a generic prompt URI — the only route for skill/hook. */\nexport const PROMPT_LINK_CAPABLE_FRAMEWORKS = [\n 'claude-code',\n 'cursor',\n] as const;\n\n/**\n * The plain http(s) URL behind a `source-location`. Unlike\n * `getRawGithubFileUrl` this accepts **directory (`tree`) URLs**, which is what\n * a multi-file `skill` actually carries — a raw-file URL would resolve to\n * `undefined` for exactly the type that most needs a launcher.\n */\nfunction getSourceUrl(sourceLocation: string | undefined): string | undefined {\n if (!sourceLocation) {\n return undefined;\n }\n try {\n const url = new URL(sourceLocation.replace(/^url:/, ''));\n return url.protocol === 'http:' || url.protocol === 'https:'\n ? url.toString()\n : undefined;\n } catch {\n return undefined;\n }\n}\n\n/**\n * One-click launchers for the types with no purpose-built install route —\n * `skill` and `hook`. Both ride the generic prompt handlers, so the prompt\n * names the *host's own* install path and respects its install mode: a `merge`\n * target is asked to be merged into, never overwritten, so a launcher can\n * never cost the user their existing settings.\n *\n * Returns `[]` when the source URL cannot be derived, or for hosts with no\n * prompt route (Copilot, Gemini) — the path list and copy/download remain.\n */\nexport function getPromptInstallLinks(\n type: ResourceType,\n sourceLocation: string | undefined,\n name: string,\n frameworks: string[] = [],\n): ResourceDeepLink[] {\n return getInstallSteps(type, sourceLocation, name, frameworks)\n .map(step => step.link)\n .filter((link): link is ResourceDeepLink => !!link);\n}\n\n/**\n * Everything a single host needs to install a resource: where the body goes,\n * the prompt that puts it there, and a launcher when the host has a URI to\n * launch.\n *\n * `link` is absent for hosts with no prompt route — Copilot and Gemini. That\n * absence is why the step carries `prompt` as text: a host without a URI is\n * not a host without an install path, and the same instruction that a launcher\n * would pre-fill can be pasted into that agent by hand. Every declared\n * framework therefore gets something actionable, not a bare path.\n */\nexport interface ResourceInstallStep {\n framework: string;\n target: ResourceInstallTarget;\n /** The install instruction, ready to run in that host's agent. */\n prompt: string;\n /** One-click launcher, where the host exposes a generic prompt URI. */\n link?: ResourceDeepLink;\n}\n\nexport function getInstallSteps(\n type: ResourceType,\n sourceLocation: string | undefined,\n name: string,\n frameworks: string[] = [],\n): ResourceInstallStep[] {\n const sourceUrl = getSourceUrl(sourceLocation);\n if (!sourceUrl) {\n return [];\n }\n const label = RESOURCE_TYPE_REGISTRY[type].label.toLowerCase();\n const steps: ResourceInstallStep[] = [];\n for (const framework of expandInstallFrameworks(frameworks)) {\n const target = getResourceInstallTarget(type, framework, name);\n if (!target) {\n continue;\n }\n const prompt =\n target.mode === 'merge'\n ? `Install the \"${name}\" ${label}: fetch ${sourceUrl} and merge it into ${target.path}, keeping my existing settings intact.`\n : `Install the \"${name}\" ${label}: fetch ${sourceUrl} and save it to ${target.path}.`;\n let link: ResourceDeepLink | undefined;\n if (framework === 'claude-code') {\n link = claudePromptLink(prompt);\n } else if (framework === 'cursor') {\n link = cursorPromptLink(prompt);\n }\n steps.push({ framework, target, prompt, link });\n }\n return steps;\n}\n\n/** Frameworks with a one-click MCP-install handler (Gemini has none). */\nexport const MCP_LINK_CAPABLE_FRAMEWORKS = [\n 'claude-code',\n 'github-copilot',\n 'cursor',\n] as const;\n\n/**\n * One-click install links for an `agent` resource, restricted to the\n * resource's own compatible frameworks. VS Code stable and Insiders use the\n * native `chat-agent/install?url=` handler (the mechanism behind\n * awesome-copilot's Install buttons) and are Copilot's hosts, so they follow\n * `github-copilot`: VS Code downloads the file at `url` and asks the user\n * where to save it. Claude Code uses the `claude-cli://open?q=` handler with\n * an install prompt pre-filled to the convention path — reviewed and sent by\n * the user, never auto-executed. All derive from the GitHub blob URL in\n * `source-location`; anything else returns `[]` (copy/download fallback).\n */\nexport function getAgentInstallLinks(\n sourceLocation: string | undefined,\n name: string,\n frameworks: string[] = [],\n): ResourceDeepLink[] {\n const rawUrl = getRawGithubFileUrl(sourceLocation);\n if (!rawUrl) {\n return [];\n }\n const capable = resolveCapableFrameworks(\n frameworks,\n AGENT_LINK_CAPABLE_FRAMEWORKS,\n );\n const encodedRawUrl = encodeURIComponent(rawUrl);\n const installPrompt = (framework: string) => {\n const target = getResourceInstallTarget('agent', framework, name);\n return `Install this agent: fetch ${rawUrl} and save it to ${target?.path}`;\n };\n const links: ResourceDeepLink[] = [];\n for (const framework of capable) {\n if (framework === 'claude-code') {\n links.push(claudePromptLink(installPrompt('claude-code')));\n } else if (framework === 'cursor') {\n links.push(cursorPromptLink(installPrompt('cursor')));\n } else if (framework === 'github-copilot') {\n links.push({\n label: 'VS Code',\n href: `vscode:chat-agent/install?url=${encodedRawUrl}`,\n });\n links.push({\n label: 'VS Code Insiders',\n href: `vscode-insiders:chat-agent/install?url=${encodedRawUrl}`,\n });\n }\n }\n return links;\n}\n\n/**\n * One-click install links for an `mcp-config` resource, derived from its body —\n * the canonical `.mcp.json` snippet (spec §3.4). VS Code carries a native\n * `vscode:mcp/install?{json}` handler (Insiders scheme twin), Cursor a\n * documented `cursor://anysphere.cursor-deeplink/mcp/install` one, and\n * Claude Code gets a `claude-cli://open?q=` prompt around\n * `claude mcp add-json` — pre-filled, reviewed, never auto-executed.\n * Hosts follow the resource's own compatible frameworks: `all`/empty expand\n * to every capable host, anything else is intersected, so an mcp-config\n * declaring only Gemini (no handler) gets no links rather than every host's.\n * A missing, unparseable, or multi-server body returns `[]` (copy fallback) —\n * the body stays canonical, links are conveniences derived from it.\n */\nexport function getMcpInstallLinks(\n frameworks: string[],\n resourceName: string,\n bodyContent: string | undefined,\n): ResourceDeepLink[] {\n if (!bodyContent) {\n return [];\n }\n let parsed: unknown;\n try {\n parsed = JSON.parse(bodyContent);\n } catch {\n return [];\n }\n if (typeof parsed !== 'object' || parsed === null) {\n return [];\n }\n\n let serverName = resourceName;\n let config: Record<string, unknown>;\n const servers = (parsed as { mcpServers?: unknown }).mcpServers;\n if (servers !== undefined) {\n if (typeof servers !== 'object' || servers === null) {\n return [];\n }\n const entries = Object.entries(servers as Record<string, unknown>);\n if (\n entries.length !== 1 ||\n typeof entries[0][1] !== 'object' ||\n entries[0][1] === null\n ) {\n return [];\n }\n serverName = entries[0][0];\n config = entries[0][1] as Record<string, unknown>;\n } else if ('command' in parsed || 'url' in parsed) {\n config = parsed as Record<string, unknown>;\n } else {\n return [];\n }\n\n const capable = resolveCapableFrameworks(\n frameworks,\n MCP_LINK_CAPABLE_FRAMEWORKS,\n );\n\n const configJson = JSON.stringify(config);\n const links: ResourceDeepLink[] = [];\n for (const framework of capable) {\n if (framework === 'claude-code') {\n const prompt = encodeURIComponent(\n `Install this MCP config by running: claude mcp add-json ${serverName} '${configJson}'`,\n );\n links.push({ label: 'Claude', href: `claude-cli://open?q=${prompt}` });\n } else if (framework === 'github-copilot') {\n const vsConfig = encodeURIComponent(\n JSON.stringify({ name: serverName, ...config }),\n );\n links.push({ label: 'VS Code', href: `vscode:mcp/install?${vsConfig}` });\n links.push({\n label: 'VS Code Insiders',\n href: `vscode-insiders:mcp/install?${vsConfig}`,\n });\n } else if (framework === 'cursor') {\n try {\n links.push({\n label: 'Cursor',\n href: `cursor://anysphere.cursor-deeplink/mcp/install?name=${encodeURIComponent(\n serverName,\n )}&config=${btoa(configJson)}`,\n });\n } catch {\n // btoa rejects non-Latin1 config content — skip the Cursor link.\n }\n }\n }\n return links;\n}\n\n/**\n * Step two of the journey: how to install a plugin from the marketplace.\n * `marketplaceName` is the entity name, which the producer contract requires\n * to equal the `name` in `marketplace.json`.\n */\nexport function getMarketplaceInstallTemplate(marketplaceName: string): string {\n return `/plugin install <plugin>@${marketplaceName}`;\n}\n\n/**\n * The `.claude/settings.json` snippet a team commits so collaborators are\n * prompted to install the marketplace automatically (Claude Code).\n */\nexport function getMarketplaceTeamSnippet(\n marketplaceName: string,\n repoSlug: string,\n): string {\n return JSON.stringify(\n {\n extraKnownMarketplaces: {\n [marketplaceName]: { source: { source: 'github', repo: repoSlug } },\n },\n },\n null,\n 2,\n );\n}\n"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"resources.cjs.js","sources":["../src/resources.ts"],"sourcesContent":["/**\n * The v2 read contract: AiResource vocabulary, the flat ResourceSummary\n * returned by the backend, and the shared framework resolver (ADR-0003).\n *\n * The legacy asset-model types in `types.ts` coexist with this module until\n * the legacy silo is deleted (slice 8, issue #34).\n */\n\n/** The six canonical `spec.type` values of an AiResource (ADR-0003, ADR-0010). */\nexport const RESOURCE_TYPES = [\n 'skill',\n 'agent',\n 'hook',\n 'mcp-config',\n 'plugin',\n 'marketplace',\n] as const;\n\nexport type ResourceType = (typeof RESOURCE_TYPES)[number];\n\nexport function isResourceType(value: unknown): value is ResourceType {\n return (\n typeof value === 'string' &&\n (RESOURCE_TYPES as readonly string[]).includes(value)\n );\n}\n\n/**\n * The AiResource types a `plugin` may contain — restricted to the component\n * kinds a real Claude Code / GitHub Copilot plugin manifest allows: `skills`,\n * `agents`, `hooks` and `mcpServers` (schemastore `claude-code-plugin` /\n * `claude-code-marketplace.json`). A plugin's other manifest components\n * (`commands`, `lspServers`, `outputStyles`, `themes`, `channels`, `monitors`)\n * have no AiResource type, so they are never members here; other plugins are\n * `dependencies`, not containment.\n */\nexport const PLUGIN_MEMBER_TYPES: readonly ResourceType[] = [\n 'skill',\n 'agent',\n 'hook',\n 'mcp-config',\n];\n\n/**\n * The AiResource types a `marketplace` may contain: `plugin` only. A real\n * `.claude-plugin/marketplace.json` `plugins` array lists plugins and nothing\n * else (required by the schema Claude Code and Copilot CLI both read).\n */\nexport const MARKETPLACE_MEMBER_TYPES: readonly ResourceType[] = ['plugin'];\n\n/**\n * Whether a container of `parent` type renders a child of `child` type as a\n * member — restricted to what a real Claude/Copilot plugin or marketplace\n * manifest permits (ADR-0010/0015). A `marketplace` contains `plugin` only; a\n * `plugin` contains the component types DevAI Hub models. Any other pairing is\n * silently not rendered. This is a rendering convention, not catalog\n * validation (upstream `allowedKinds` is the broad `[\"AiResource\"]`).\n */\nexport function isRenderableContainment(\n parent: ResourceType,\n child: ResourceType,\n): boolean {\n if (parent === 'marketplace') return MARKETPLACE_MEMBER_TYPES.includes(child);\n if (parent === 'plugin') return PLUGIN_MEMBER_TYPES.includes(child);\n return false;\n}\n\n/** Annotation prefix owned by DevAI Hub. */\nexport const DEVAIHUB_ANNOTATION_PREFIX = 'devaihub.io';\n\n/** Comma-separated compatible-framework tokens (non-skill read path, ADR-0003). */\nexport const ANNOTATION_COMPATIBLE_FRAMEWORKS = `${DEVAIHUB_ANNOTATION_PREFIX}/compatible-frameworks`;\n\n/** Optional free-form help text shown on the detail panel. */\nexport const ANNOTATION_HELP = `${DEVAIHUB_ANNOTATION_PREFIX}/help`;\n\n/** Known framework tokens; unknown tokens pass through as-is (ADR-0003). */\nexport const KNOWN_FRAMEWORKS = [\n 'github-copilot',\n 'claude-code',\n 'cursor',\n 'google-gemini',\n 'opencode',\n 'all',\n] as const;\n\nexport type FrameworkToken = (typeof KNOWN_FRAMEWORKS)[number];\n\nconst FRAMEWORK_ALIASES: Record<string, FrameworkToken> = {\n copilot: 'github-copilot',\n claude: 'claude-code',\n gemini: 'google-gemini',\n};\n\n/**\n * Normalise a framework token: trim, lowercase, resolve aliases\n * (`claude` → `claude-code`). Unknown tokens pass through literally.\n */\nexport function normalizeFramework(token: string): string {\n const t = token.trim().toLowerCase();\n return FRAMEWORK_ALIASES[t] ?? t;\n}\n\n/**\n * The minimal structural shape of an AiResource entity that the read helpers\n * need. A real `@backstage/catalog-model` `Entity` satisfies it, but keeping\n * the shape local means `-common` (and therefore the frontend) never depends\n * on catalog packages (architecture.md).\n */\nexport interface AiResourceEntityLike {\n metadata: {\n annotations?: Record<string, string>;\n };\n spec?: {\n type?: unknown;\n /** Native compatible-frameworks field, honoured regardless of `spec.type`. */\n agents?: unknown;\n [key: string]: unknown;\n };\n}\n\n/**\n * Resolve the compatible frameworks of an AiResource (ADR-0003):\n * 1. a non-empty `spec.agents` → the native field, for any `spec.type`;\n * 2. otherwise → the `devaihub.io/compatible-frameworks` annotation\n * (also the fallback when `spec.agents` is empty/absent);\n * 3. otherwise → `[]`.\n * Tokens are normalised; unknown tokens pass through. `spec.agents` is\n * preferred over the annotation because it lives on the entity itself\n * rather than in an annotation namespace we may retire.\n */\nexport function getFrameworks(entity: AiResourceEntityLike): string[] {\n if (Array.isArray(entity.spec?.agents)) {\n const agents = entity.spec.agents\n .filter((a): a is string => typeof a === 'string')\n .map(normalizeFramework)\n .filter(Boolean);\n if (agents.length > 0) {\n return dedupe(agents);\n }\n }\n\n const raw = entity.metadata.annotations?.[ANNOTATION_COMPATIBLE_FRAMEWORKS];\n if (!raw) {\n return [];\n }\n return dedupe(raw.split(',').map(normalizeFramework).filter(Boolean));\n}\n\nfunction dedupe(tokens: string[]): string[] {\n return Array.from(new Set(tokens));\n}\n\n/**\n * Icon identifiers for the type→card registry. `-common` is imported by the\n * backend, so it stays React-free: the frontend maps these identifiers to\n * actual icon components.\n */\nexport type ResourceTypeIcon =\n | 'tools'\n | 'robot'\n | 'flash'\n | 'plug'\n | 'puzzle'\n | 'store';\n\nexport interface ResourceTypeInfo {\n type: ResourceType;\n /** Singular display label, e.g. \"Skill\" (card type caption). */\n label: string;\n /** Plural display label, e.g. \"Skills\" (stat tiles, filters). */\n pluralLabel: string;\n /** Icon identifier resolved to a component by the frontend. */\n icon: ResourceTypeIcon;\n /**\n * Colour role name; the frontend resolves it to the plugin-owned\n * `--devaihub-type-<role>` custom properties (ADR-0008, NOS palette).\n */\n colorRole: ResourceType;\n}\n\n/**\n * The typed type→card registry (ADR-0003/ADR-0008): producer vocabulary and\n * consumer rendering share this single source, so they cannot drift. Adding\n * a type is one entry here plus one CSS token pair in the frontend.\n */\nexport const RESOURCE_TYPE_REGISTRY: Record<ResourceType, ResourceTypeInfo> = {\n skill: {\n type: 'skill',\n label: 'Skill',\n pluralLabel: 'Skills',\n icon: 'tools',\n colorRole: 'skill',\n },\n agent: {\n type: 'agent',\n label: 'Agent',\n pluralLabel: 'Agents',\n icon: 'robot',\n colorRole: 'agent',\n },\n hook: {\n type: 'hook',\n label: 'Hook',\n pluralLabel: 'Hooks',\n icon: 'flash',\n colorRole: 'hook',\n },\n 'mcp-config': {\n type: 'mcp-config',\n label: 'MCP Config',\n pluralLabel: 'MCP Configs',\n icon: 'plug',\n colorRole: 'mcp-config',\n },\n plugin: {\n type: 'plugin',\n label: 'Plugin',\n pluralLabel: 'Plugins',\n icon: 'puzzle',\n colorRole: 'plugin',\n },\n marketplace: {\n type: 'marketplace',\n label: 'Marketplace',\n pluralLabel: 'Marketplaces',\n icon: 'store',\n colorRole: 'marketplace',\n },\n};\n\n/**\n * The flat contract the backend returns to the frontend. The frontend never\n * sees a raw Backstage `Entity` — only this (architecture.md).\n */\nexport interface ResourceSummary {\n entityRef: string;\n name: string;\n title?: string;\n description?: string;\n tags: string[];\n type: ResourceType;\n lifecycle: string;\n owner?: string;\n sourceLocation?: string;\n frameworks: string[];\n version?: string;\n kind: string;\n /**\n * Entity refs this container declares as its contents — a `marketplace`'s\n * `spec.plugins`, a `plugin`'s `spec.skills`; empty for leaf types. Filtered\n * to the caller's visible set and to the child types the container renders\n * (ADR-0015). The frontend resolves each ref against the summaries it holds.\n */\n children: string[];\n /**\n * Entity refs of the containers that declare this resource — the in-memory\n * inverse of `children`, built over the same caller-visible catalog read\n * (ADR-0015). A resource can belong to several containers.\n */\n parents: string[];\n /** Count of `children` (their declared, caller-visible members). */\n childCount?: number;\n helpText?: string;\n annotations: Record<string, string>;\n}\n\nexport interface ResourceListResponse {\n items: ResourceSummary[];\n}\n\n/**\n * The shape of a resource's body (CONTEXT.md: bodies are type-shaped, not\n * uniformly markdown). Drives detail-panel rendering: markdown is rendered,\n * json is pretty-printed and syntax-highlighted (ADR-0014). `plugin` and\n * `marketplace` bodies are the real Claude Code `plugin.json` /\n * `marketplace.json` manifest — not a hand-authored pointer doc, superseding\n * ADR-0010's markdown-only body for `marketplace`.\n */\nexport type BodyShape = 'markdown' | 'json';\n\nexport function getBodyShape(type: ResourceType): BodyShape {\n return type === 'mcp-config' || type === 'plugin' || type === 'marketplace'\n ? 'json'\n : 'markdown';\n}\n\n/**\n * Whether downloading the body delivers the artifact itself. True for every\n * type: `plugin`/`marketplace` bodies are now the real manifest JSON, so a\n * download delivers that file itself rather than a non-actionable pointer\n * doc (ADR-0014, superseding ADR-0009's amendment and ADR-0010 on this\n * point).\n */\nexport function hasDownloadableArtifact(_type: ResourceType): boolean {\n return true;\n}\n\n/**\n * Whether copying the body to the clipboard is a meaningful install action.\n * True for every type: `marketplace`'s body is now the real manifest JSON,\n * not an instructions doc, so copying it is no longer a misrepresentation\n * (ADR-0014, superseding ADR-0010 on this point).\n */\nexport function hasCopyableBody(_type: ResourceType): boolean {\n return true;\n}\n\n/**\n * How a body reaches its install path.\n *\n * `drop-in` — the body becomes the file (or directory) at `path`, which is\n * the resource's own; writing it touches nothing else.\n *\n * `merge` — `path` is a shared settings file the user already owns, and the\n * body is a fragment to merge into it. The distinction is not cosmetic:\n * following a `merge` target as if it were a `drop-in` overwrites the user's\n * existing configuration, so the UI must never present the two identically.\n */\nexport type InstallMode = 'drop-in' | 'merge';\n\nexport interface ResourceInstallTarget {\n /** Workspace-relative path. */\n path: string;\n mode: InstallMode;\n}\n\n/**\n * The concrete framework tokens a body can be installed into — `all` is a\n * wildcard over these, never a destination itself.\n */\nexport const INSTALLABLE_FRAMEWORKS = [\n 'claude-code',\n 'github-copilot',\n 'google-gemini',\n 'cursor',\n 'opencode',\n] as const;\n\n/**\n * Convention table: (type, framework) → where the body goes and how.\n * A missing entry means the pair has no filesystem convention — `plugin` and\n * `marketplace` bodies are pointers that install through their framework\n * (ADR-0009, ADR-0010), so they carry install commands rather than paths.\n */\nconst INSTALL_PATHS: Record<\n ResourceType,\n Record<string, { mode: InstallMode; path: (name: string) => string }>\n> = {\n // Every host reads a skill as a self-contained directory. Copilot also\n // picks up `.claude/skills`, but each tool's own path is what we recommend;\n // `.agents/skills` is the cross-tool alias Copilot and Gemini both honour,\n // which makes it the right answer for an unrecognised framework.\n skill: {\n 'claude-code': { mode: 'drop-in', path: name => `.claude/skills/${name}/` },\n 'github-copilot': {\n mode: 'drop-in',\n path: name => `.github/skills/${name}/`,\n },\n 'google-gemini': {\n mode: 'drop-in',\n path: name => `.gemini/skills/${name}/`,\n },\n cursor: { mode: 'drop-in', path: name => `.cursor/skills/${name}/` },\n opencode: { mode: 'drop-in', path: name => `.opencode/skills/${name}/` },\n default: { mode: 'drop-in', path: name => `.agents/skills/${name}/` },\n },\n agent: {\n 'claude-code': {\n mode: 'drop-in',\n path: name => `.claude/agents/${name}.md`,\n },\n 'github-copilot': {\n mode: 'drop-in',\n path: name => `.github/agents/${name}.agent.md`,\n },\n 'google-gemini': {\n mode: 'drop-in',\n path: name => `.gemini/agents/${name}.md`,\n },\n cursor: { mode: 'drop-in', path: name => `.cursor/rules/${name}.mdc` },\n opencode: { mode: 'drop-in', path: name => `.opencode/agents/${name}.md` },\n default: { mode: 'drop-in', path: name => `.ai/agents/${name}.md` },\n },\n // All four hosts have a hook system, but only Copilot gives each hook its\n // own file; the rest register hooks inside a shared settings document.\n // OpenCode has no declarative hook config at all — hooks are exclusively\n // JS/TS plugin modules — so it deliberately carries no entry here, the same\n // way it carries no `default`.\n hook: {\n 'claude-code': { mode: 'merge', path: () => `.claude/settings.json` },\n 'github-copilot': {\n mode: 'drop-in',\n path: name => `.github/hooks/${name}.json`,\n },\n 'google-gemini': { mode: 'merge', path: () => `.gemini/settings.json` },\n cursor: { mode: 'merge', path: () => `.cursor/hooks.json` },\n },\n 'mcp-config': {\n 'claude-code': { mode: 'merge', path: () => `.mcp.json` },\n 'github-copilot': { mode: 'merge', path: () => `.vscode/mcp.json` },\n 'google-gemini': { mode: 'merge', path: () => `.gemini/settings.json` },\n cursor: { mode: 'merge', path: () => `.cursor/mcp.json` },\n opencode: { mode: 'merge', path: () => `opencode.json` },\n },\n plugin: {},\n marketplace: {},\n};\n\n/**\n * Where a resource's body installs for one framework, and whether that path\n * is the body's own file or a settings file to merge into. Returns\n * `undefined` when the (type, framework) pair has no filesystem convention.\n */\nexport function getResourceInstallTarget(\n type: ResourceType,\n framework: string,\n name: string,\n): ResourceInstallTarget | undefined {\n const conventions = INSTALL_PATHS[type];\n const entry =\n conventions[normalizeFramework(framework)] ?? conventions.default;\n return entry ? { path: entry.path(name), mode: entry.mode } : undefined;\n}\n\n/**\n * The frameworks to show install paths for. An empty list means the resource\n * declared no compatibility and gets the neutral `default` convention; `all`\n * is a wildcard and expands to every installable host, so it never collapses\n * to a single row — or, for types with no `default`, to no rows at all.\n */\nexport function expandInstallFrameworks(frameworks: string[]): string[] {\n const normalized = dedupe(frameworks.map(normalizeFramework));\n if (normalized.length === 0) {\n return ['default'];\n }\n if (normalized.includes('all')) {\n return [...INSTALLABLE_FRAMEWORKS];\n }\n return normalized;\n}\n\n/*\n * Marketplace journey helpers (ADR-0010). A marketplace installs in two\n * steps — register the catalog with the AI tool, then install plugins from\n * it. Both Claude Code and Copilot CLI read the same\n * `.claude-plugin/marketplace.json` format and address the repo as\n * `owner/repo`; the plugin derives that slug from the mandatory\n * `source-location` (the producer contract requires the body doc to live\n * inside the marketplace repo). No derivation → the UI falls back to the\n * rendered body, which stays canonical.\n */\n\n/** Frameworks with a marketplace-add concept (Cursor/Gemini have none). */\nexport const MARKETPLACE_CAPABLE_FRAMEWORKS = [\n 'claude-code',\n 'github-copilot',\n] as const;\n\n/**\n * Narrow a resource's declared frameworks to those a given journey can\n * actually launch. A deep link is a claim about compatibility: offering\n * \"Install in Claude\" for a resource that never declared `claude-code` tells\n * the user something untrue about the resource.\n *\n * `all` and an empty list both mean \"unrestricted\" and expand to every\n * capable host. Anything else is intersected, so a declared framework with no\n * handler (Gemini) — or an unknown token — yields no links rather than\n * silently widening to every host.\n */\nexport function resolveCapableFrameworks(\n declared: string[],\n capable: readonly string[],\n): string[] {\n const normalized = dedupe(declared.map(normalizeFramework));\n if (normalized.length === 0 || normalized.includes('all')) {\n return [...capable];\n }\n return normalized.filter(f => capable.includes(f));\n}\n\n/**\n * Derive the `owner/repo` slug from a marketplace's `source-location`.\n * GitHub URLs only — other hosts return `undefined` (body-only fallback).\n */\nexport function getMarketplaceRepoSlug(\n sourceLocation: string | undefined,\n): string | undefined {\n if (!sourceLocation) {\n return undefined;\n }\n let url: URL;\n try {\n url = new URL(sourceLocation.replace(/^url:/, ''));\n } catch {\n return undefined;\n }\n if (url.hostname !== 'github.com') {\n return undefined;\n }\n const [owner, repo] = url.pathname.split('/').filter(Boolean);\n if (!owner || !repo) {\n return undefined;\n }\n return `${owner}/${repo.replace(/\\.git$/, '')}`;\n}\n\n/**\n * A one-click launcher for an add command: a custom-scheme URL that opens\n * the host app with the command pre-filled in the prompt box. The host\n * never auto-executes it — the user reviews and presses Enter — and an\n * unregistered scheme is a harmless no-op, so these complement the copy\n * button rather than replace it.\n */\nexport interface ResourceDeepLink {\n /** Host application name; render as \"Add in <label>\" / \"Install in <label>\". */\n label: string;\n href: string;\n}\n\nexport interface MarketplaceAddCommand {\n framework: string;\n command: string;\n deepLinks: ResourceDeepLink[];\n}\n\n/**\n * The copyable marketplace-add command per compatible framework. `all`,\n * unknown-only, or empty framework lists expand to every capable framework;\n * frameworks without a marketplace concept produce no row.\n *\n * Claude Code carries a single deep link on the CLI's documented\n * `claude-cli://open?q=` handler, which opens a Claude Code terminal\n * session with the command pre-filled regardless of the user's editor.\n *\n * Copilot's CLI still has no URI scheme, but Copilot's *other* surface does:\n * VS Code 1.113 ships `vscode://chat-plugin/add-marketplace?ref=`, which\n * accepts a plain or base64 `owner/repo` and always shows a confirmation\n * dialog before registering the marketplace (it also dedupes against the\n * user's existing entries). So the Copilot row keeps its terminal command for\n * CLI users *and* gains editor launchers, mirroring how `mcp-config` already\n * maps `github-copilot` onto the VS Code stable/Insiders pair.\n */\nexport function getMarketplaceAddCommands(\n frameworks: string[],\n repoSlug: string,\n): MarketplaceAddCommand[] {\n const capable = resolveCapableFrameworks(\n frameworks,\n MARKETPLACE_CAPABLE_FRAMEWORKS,\n );\n const commands: MarketplaceAddCommand[] = [];\n for (const framework of capable) {\n if (framework === 'claude-code') {\n const command = `/plugin marketplace add ${repoSlug}`;\n commands.push({\n framework,\n command,\n deepLinks: [\n {\n label: 'Claude',\n href: `claude-cli://open?q=${encodeURIComponent(command)}`,\n },\n ],\n });\n } else if (framework === 'github-copilot') {\n const ref = encodeURIComponent(repoSlug);\n commands.push({\n framework,\n command: `copilot plugin marketplace add ${repoSlug}`,\n deepLinks: [\n {\n label: 'VS Code',\n href: `vscode://chat-plugin/add-marketplace?ref=${ref}`,\n },\n {\n label: 'VS Code Insiders',\n href: `vscode-insiders://chat-plugin/add-marketplace?ref=${ref}`,\n },\n ],\n });\n }\n }\n return commands;\n}\n\n/** The raw-file URL for a GitHub blob `source-location`; else `undefined`. */\nfunction getRawGithubFileUrl(\n sourceLocation: string | undefined,\n): string | undefined {\n if (!sourceLocation) {\n return undefined;\n }\n let url: URL;\n try {\n url = new URL(sourceLocation.replace(/^url:/, ''));\n } catch {\n return undefined;\n }\n if (url.hostname !== 'github.com') {\n return undefined;\n }\n const [owner, repo, marker, ref, ...path] = url.pathname\n .split('/')\n .filter(Boolean);\n if (!owner || !repo || marker !== 'blob' || !ref || path.length === 0) {\n return undefined;\n }\n return `https://raw.githubusercontent.com/${owner}/${repo}/${ref}/${path.join(\n '/',\n )}`;\n}\n\n/**\n * A launcher that opens an agent with an install prompt pre-filled. Neither\n * host auto-executes: the user reads the prompt and presses Enter. Claude Code\n * and Cursor are the two tools exposing a generic prompt URI — Copilot's\n * editor has purpose-built routes but no prompt handler, and Gemini has\n * neither (its `gemini.google.com/app?prompt=` link drives the web app, which\n * cannot write to a local workspace).\n *\n * Cursor truncates a deeplink at the first raw `&`, so the prompt must be\n * percent-encoded — `encodeURIComponent` handles that. Cursor also caps the\n * URL at ~8000 characters; these prompts are a URL plus a path, far under it.\n */\nfunction claudePromptLink(prompt: string): ResourceDeepLink {\n return {\n label: 'Claude',\n href: `claude-cli://open?q=${encodeURIComponent(prompt)}`,\n };\n}\n\nfunction cursorPromptLink(prompt: string): ResourceDeepLink {\n return {\n label: 'Cursor',\n href: `cursor://anysphere.cursor-deeplink/prompt?text=${encodeURIComponent(\n prompt,\n )}`,\n };\n}\n\n/**\n * Frameworks with a one-click agent-install handler (Gemini has none).\n * OpenCode's Desktop app does register a real `opencode://` scheme, but every\n * route (`open-project`, `new-session`) requires an absolute local\n * `directory` param a web page cannot supply, so it's excluded here too.\n */\nexport const AGENT_LINK_CAPABLE_FRAMEWORKS = [\n 'claude-code',\n 'github-copilot',\n 'cursor',\n] as const;\n\n/** Hosts reachable by a generic prompt URI — the only route for skill/hook. */\nexport const PROMPT_LINK_CAPABLE_FRAMEWORKS = [\n 'claude-code',\n 'cursor',\n] as const;\n\n/**\n * The plain http(s) URL behind a `source-location`. Unlike\n * `getRawGithubFileUrl` this accepts **directory (`tree`) URLs**, which is what\n * a multi-file `skill` actually carries — a raw-file URL would resolve to\n * `undefined` for exactly the type that most needs a launcher.\n */\nfunction getSourceUrl(sourceLocation: string | undefined): string | undefined {\n if (!sourceLocation) {\n return undefined;\n }\n try {\n const url = new URL(sourceLocation.replace(/^url:/, ''));\n return url.protocol === 'http:' || url.protocol === 'https:'\n ? url.toString()\n : undefined;\n } catch {\n return undefined;\n }\n}\n\n/**\n * One-click launchers for the types with no purpose-built install route —\n * `skill` and `hook`. Both ride the generic prompt handlers, so the prompt\n * names the *host's own* install path and respects its install mode: a `merge`\n * target is asked to be merged into, never overwritten, so a launcher can\n * never cost the user their existing settings.\n *\n * Returns `[]` when the source URL cannot be derived, or for hosts with no\n * prompt route (Copilot, Gemini) — the path list and copy/download remain.\n */\nexport function getPromptInstallLinks(\n type: ResourceType,\n sourceLocation: string | undefined,\n name: string,\n frameworks: string[] = [],\n): ResourceDeepLink[] {\n return getInstallSteps(type, sourceLocation, name, frameworks)\n .map(step => step.link)\n .filter((link): link is ResourceDeepLink => !!link);\n}\n\n/**\n * Everything a single host needs to install a resource: where the body goes,\n * the prompt that puts it there, and a launcher when the host has a URI to\n * launch.\n *\n * `link` is absent for hosts with no prompt route — Copilot and Gemini. That\n * absence is why the step carries `prompt` as text: a host without a URI is\n * not a host without an install path, and the same instruction that a launcher\n * would pre-fill can be pasted into that agent by hand. Every declared\n * framework therefore gets something actionable, not a bare path.\n */\nexport interface ResourceInstallStep {\n framework: string;\n target: ResourceInstallTarget;\n /** The install instruction, ready to run in that host's agent. */\n prompt: string;\n /** One-click launcher, where the host exposes a generic prompt URI. */\n link?: ResourceDeepLink;\n}\n\nexport function getInstallSteps(\n type: ResourceType,\n sourceLocation: string | undefined,\n name: string,\n frameworks: string[] = [],\n): ResourceInstallStep[] {\n const sourceUrl = getSourceUrl(sourceLocation);\n if (!sourceUrl) {\n return [];\n }\n const label = RESOURCE_TYPE_REGISTRY[type].label.toLowerCase();\n const steps: ResourceInstallStep[] = [];\n for (const framework of expandInstallFrameworks(frameworks)) {\n const target = getResourceInstallTarget(type, framework, name);\n if (!target) {\n continue;\n }\n const prompt =\n target.mode === 'merge'\n ? `Install the \"${name}\" ${label}: fetch ${sourceUrl} and merge it into ${target.path}, keeping my existing settings intact.`\n : `Install the \"${name}\" ${label}: fetch ${sourceUrl} and save it to ${target.path}.`;\n let link: ResourceDeepLink | undefined;\n if (framework === 'claude-code') {\n link = claudePromptLink(prompt);\n } else if (framework === 'cursor') {\n link = cursorPromptLink(prompt);\n }\n steps.push({ framework, target, prompt, link });\n }\n return steps;\n}\n\n/** Frameworks with a one-click MCP-install handler (Gemini has none). */\nexport const MCP_LINK_CAPABLE_FRAMEWORKS = [\n 'claude-code',\n 'github-copilot',\n 'cursor',\n] as const;\n\n/**\n * One-click install links for an `agent` resource, restricted to the\n * resource's own compatible frameworks. VS Code stable and Insiders use the\n * native `chat-agent/install?url=` handler (the mechanism behind\n * awesome-copilot's Install buttons) and are Copilot's hosts, so they follow\n * `github-copilot`: VS Code downloads the file at `url` and asks the user\n * where to save it. Claude Code uses the `claude-cli://open?q=` handler with\n * an install prompt pre-filled to the convention path — reviewed and sent by\n * the user, never auto-executed. All derive from the GitHub blob URL in\n * `source-location`; anything else returns `[]` (copy/download fallback).\n */\nexport function getAgentInstallLinks(\n sourceLocation: string | undefined,\n name: string,\n frameworks: string[] = [],\n): ResourceDeepLink[] {\n const rawUrl = getRawGithubFileUrl(sourceLocation);\n if (!rawUrl) {\n return [];\n }\n const capable = resolveCapableFrameworks(\n frameworks,\n AGENT_LINK_CAPABLE_FRAMEWORKS,\n );\n const encodedRawUrl = encodeURIComponent(rawUrl);\n const installPrompt = (framework: string) => {\n const target = getResourceInstallTarget('agent', framework, name);\n return `Install this agent: fetch ${rawUrl} and save it to ${target?.path}`;\n };\n const links: ResourceDeepLink[] = [];\n for (const framework of capable) {\n if (framework === 'claude-code') {\n links.push(claudePromptLink(installPrompt('claude-code')));\n } else if (framework === 'cursor') {\n links.push(cursorPromptLink(installPrompt('cursor')));\n } else if (framework === 'github-copilot') {\n links.push({\n label: 'VS Code',\n href: `vscode:chat-agent/install?url=${encodedRawUrl}`,\n });\n links.push({\n label: 'VS Code Insiders',\n href: `vscode-insiders:chat-agent/install?url=${encodedRawUrl}`,\n });\n }\n }\n return links;\n}\n\n/**\n * One-click install links for an `mcp-config` resource, derived from its body —\n * the canonical `.mcp.json` snippet (spec §3.4). VS Code carries a native\n * `vscode:mcp/install?{json}` handler (Insiders scheme twin), Cursor a\n * documented `cursor://anysphere.cursor-deeplink/mcp/install` one, and\n * Claude Code gets a `claude-cli://open?q=` prompt around\n * `claude mcp add-json` — pre-filled, reviewed, never auto-executed.\n * Hosts follow the resource's own compatible frameworks: `all`/empty expand\n * to every capable host, anything else is intersected, so an mcp-config\n * declaring only Gemini (no handler) gets no links rather than every host's.\n * A missing, unparseable, or multi-server body returns `[]` (copy fallback) —\n * the body stays canonical, links are conveniences derived from it.\n */\nexport function getMcpInstallLinks(\n frameworks: string[],\n resourceName: string,\n bodyContent: string | undefined,\n): ResourceDeepLink[] {\n if (!bodyContent) {\n return [];\n }\n let parsed: unknown;\n try {\n parsed = JSON.parse(bodyContent);\n } catch {\n return [];\n }\n if (typeof parsed !== 'object' || parsed === null) {\n return [];\n }\n\n let serverName = resourceName;\n let config: Record<string, unknown>;\n const servers = (parsed as { mcpServers?: unknown }).mcpServers;\n if (servers !== undefined) {\n if (typeof servers !== 'object' || servers === null) {\n return [];\n }\n const entries = Object.entries(servers as Record<string, unknown>);\n if (\n entries.length !== 1 ||\n typeof entries[0][1] !== 'object' ||\n entries[0][1] === null\n ) {\n return [];\n }\n serverName = entries[0][0];\n config = entries[0][1] as Record<string, unknown>;\n } else if ('command' in parsed || 'url' in parsed) {\n config = parsed as Record<string, unknown>;\n } else {\n return [];\n }\n\n const capable = resolveCapableFrameworks(\n frameworks,\n MCP_LINK_CAPABLE_FRAMEWORKS,\n );\n\n const configJson = JSON.stringify(config);\n const links: ResourceDeepLink[] = [];\n for (const framework of capable) {\n if (framework === 'claude-code') {\n const prompt = encodeURIComponent(\n `Install this MCP config by running: claude mcp add-json ${serverName} '${configJson}'`,\n );\n links.push({ label: 'Claude', href: `claude-cli://open?q=${prompt}` });\n } else if (framework === 'github-copilot') {\n const vsConfig = encodeURIComponent(\n JSON.stringify({ name: serverName, ...config }),\n );\n links.push({ label: 'VS Code', href: `vscode:mcp/install?${vsConfig}` });\n links.push({\n label: 'VS Code Insiders',\n href: `vscode-insiders:mcp/install?${vsConfig}`,\n });\n } else if (framework === 'cursor') {\n try {\n links.push({\n label: 'Cursor',\n href: `cursor://anysphere.cursor-deeplink/mcp/install?name=${encodeURIComponent(\n serverName,\n )}&config=${btoa(configJson)}`,\n });\n } catch {\n // btoa rejects non-Latin1 config content — skip the Cursor link.\n }\n }\n }\n return links;\n}\n\n/**\n * Step two of the journey: how to install a plugin from the marketplace.\n * `marketplaceName` is the entity name, which the producer contract requires\n * to equal the `name` in `marketplace.json`.\n */\nexport function getMarketplaceInstallTemplate(marketplaceName: string): string {\n return `/plugin install <plugin>@${marketplaceName}`;\n}\n\n/**\n * The `.claude/settings.json` snippet a team commits so collaborators are\n * prompted to install the marketplace automatically (Claude Code).\n */\nexport function getMarketplaceTeamSnippet(\n marketplaceName: string,\n repoSlug: string,\n): string {\n return JSON.stringify(\n {\n extraKnownMarketplaces: {\n [marketplaceName]: { source: { source: 'github', repo: repoSlug } },\n },\n },\n null,\n 2,\n );\n}\n"],"names":[],"mappings":"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"}
@@ -9,9 +9,20 @@ const RESOURCE_TYPES = [
9
9
  function isResourceType(value) {
10
10
  return typeof value === "string" && RESOURCE_TYPES.includes(value);
11
11
  }
12
+ const PLUGIN_MEMBER_TYPES = [
13
+ "skill",
14
+ "agent",
15
+ "hook",
16
+ "mcp-config"
17
+ ];
18
+ const MARKETPLACE_MEMBER_TYPES = ["plugin"];
19
+ function isRenderableContainment(parent, child) {
20
+ if (parent === "marketplace") return MARKETPLACE_MEMBER_TYPES.includes(child);
21
+ if (parent === "plugin") return PLUGIN_MEMBER_TYPES.includes(child);
22
+ return false;
23
+ }
12
24
  const DEVAIHUB_ANNOTATION_PREFIX = "devaihub.io";
13
25
  const ANNOTATION_COMPATIBLE_FRAMEWORKS = `${DEVAIHUB_ANNOTATION_PREFIX}/compatible-frameworks`;
14
- const ANNOTATION_VERSION = `${DEVAIHUB_ANNOTATION_PREFIX}/version`;
15
26
  const ANNOTATION_HELP = `${DEVAIHUB_ANNOTATION_PREFIX}/help`;
16
27
  const KNOWN_FRAMEWORKS = [
17
28
  "github-copilot",
@@ -450,5 +461,5 @@ function getMarketplaceTeamSnippet(marketplaceName, repoSlug) {
450
461
  );
451
462
  }
452
463
 
453
- export { AGENT_LINK_CAPABLE_FRAMEWORKS, ANNOTATION_COMPATIBLE_FRAMEWORKS, ANNOTATION_HELP, ANNOTATION_VERSION, DEVAIHUB_ANNOTATION_PREFIX, INSTALLABLE_FRAMEWORKS, KNOWN_FRAMEWORKS, MARKETPLACE_CAPABLE_FRAMEWORKS, MCP_LINK_CAPABLE_FRAMEWORKS, PROMPT_LINK_CAPABLE_FRAMEWORKS, RESOURCE_TYPES, RESOURCE_TYPE_REGISTRY, expandInstallFrameworks, getAgentInstallLinks, getBodyShape, getFrameworks, getInstallSteps, getMarketplaceAddCommands, getMarketplaceInstallTemplate, getMarketplaceRepoSlug, getMarketplaceTeamSnippet, getMcpInstallLinks, getPromptInstallLinks, getResourceInstallTarget, hasCopyableBody, hasDownloadableArtifact, isResourceType, normalizeFramework, resolveCapableFrameworks };
464
+ export { AGENT_LINK_CAPABLE_FRAMEWORKS, ANNOTATION_COMPATIBLE_FRAMEWORKS, ANNOTATION_HELP, DEVAIHUB_ANNOTATION_PREFIX, INSTALLABLE_FRAMEWORKS, KNOWN_FRAMEWORKS, MARKETPLACE_CAPABLE_FRAMEWORKS, MARKETPLACE_MEMBER_TYPES, MCP_LINK_CAPABLE_FRAMEWORKS, PLUGIN_MEMBER_TYPES, PROMPT_LINK_CAPABLE_FRAMEWORKS, RESOURCE_TYPES, RESOURCE_TYPE_REGISTRY, expandInstallFrameworks, getAgentInstallLinks, getBodyShape, getFrameworks, getInstallSteps, getMarketplaceAddCommands, getMarketplaceInstallTemplate, getMarketplaceRepoSlug, getMarketplaceTeamSnippet, getMcpInstallLinks, getPromptInstallLinks, getResourceInstallTarget, hasCopyableBody, hasDownloadableArtifact, isRenderableContainment, isResourceType, normalizeFramework, resolveCapableFrameworks };
454
465
  //# sourceMappingURL=resources.esm.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"resources.esm.js","sources":["../src/resources.ts"],"sourcesContent":["/**\n * The v2 read contract: AiResource vocabulary, the flat ResourceSummary\n * returned by the backend, and the shared framework resolver (ADR-0003).\n *\n * The legacy asset-model types in `types.ts` coexist with this module until\n * the legacy silo is deleted (slice 8, issue #34).\n */\n\n/** The six canonical `spec.type` values of an AiResource (ADR-0003, ADR-0010). */\nexport const RESOURCE_TYPES = [\n 'skill',\n 'agent',\n 'hook',\n 'mcp-config',\n 'plugin',\n 'marketplace',\n] as const;\n\nexport type ResourceType = (typeof RESOURCE_TYPES)[number];\n\nexport function isResourceType(value: unknown): value is ResourceType {\n return (\n typeof value === 'string' &&\n (RESOURCE_TYPES as readonly string[]).includes(value)\n );\n}\n\n/** Annotation prefix owned by DevAI Hub. */\nexport const DEVAIHUB_ANNOTATION_PREFIX = 'devaihub.io';\n\n/** Comma-separated compatible-framework tokens (non-skill read path, ADR-0003). */\nexport const ANNOTATION_COMPATIBLE_FRAMEWORKS = `${DEVAIHUB_ANNOTATION_PREFIX}/compatible-frameworks`;\n\n/** Optional semantic version string, displayed on cards. */\nexport const ANNOTATION_VERSION = `${DEVAIHUB_ANNOTATION_PREFIX}/version`;\n\n/** Optional free-form help text shown on the detail panel. */\nexport const ANNOTATION_HELP = `${DEVAIHUB_ANNOTATION_PREFIX}/help`;\n\n/** Known framework tokens; unknown tokens pass through as-is (ADR-0003). */\nexport const KNOWN_FRAMEWORKS = [\n 'github-copilot',\n 'claude-code',\n 'cursor',\n 'google-gemini',\n 'opencode',\n 'all',\n] as const;\n\nexport type FrameworkToken = (typeof KNOWN_FRAMEWORKS)[number];\n\nconst FRAMEWORK_ALIASES: Record<string, FrameworkToken> = {\n copilot: 'github-copilot',\n claude: 'claude-code',\n gemini: 'google-gemini',\n};\n\n/**\n * Normalise a framework token: trim, lowercase, resolve aliases\n * (`claude` → `claude-code`). Unknown tokens pass through literally.\n */\nexport function normalizeFramework(token: string): string {\n const t = token.trim().toLowerCase();\n return FRAMEWORK_ALIASES[t] ?? t;\n}\n\n/**\n * The minimal structural shape of an AiResource entity that the read helpers\n * need. A real `@backstage/catalog-model` `Entity` satisfies it, but keeping\n * the shape local means `-common` (and therefore the frontend) never depends\n * on catalog packages (architecture.md).\n */\nexport interface AiResourceEntityLike {\n metadata: {\n annotations?: Record<string, string>;\n };\n spec?: {\n type?: unknown;\n /** Native compatible-frameworks field, honoured regardless of `spec.type`. */\n agents?: unknown;\n [key: string]: unknown;\n };\n}\n\n/**\n * Resolve the compatible frameworks of an AiResource (ADR-0003):\n * 1. a non-empty `spec.agents` → the native field, for any `spec.type`;\n * 2. otherwise → the `devaihub.io/compatible-frameworks` annotation\n * (also the fallback when `spec.agents` is empty/absent);\n * 3. otherwise → `[]`.\n * Tokens are normalised; unknown tokens pass through. `spec.agents` is\n * preferred over the annotation because it lives on the entity itself\n * rather than in an annotation namespace we may retire.\n */\nexport function getFrameworks(entity: AiResourceEntityLike): string[] {\n if (Array.isArray(entity.spec?.agents)) {\n const agents = entity.spec.agents\n .filter((a): a is string => typeof a === 'string')\n .map(normalizeFramework)\n .filter(Boolean);\n if (agents.length > 0) {\n return dedupe(agents);\n }\n }\n\n const raw = entity.metadata.annotations?.[ANNOTATION_COMPATIBLE_FRAMEWORKS];\n if (!raw) {\n return [];\n }\n return dedupe(raw.split(',').map(normalizeFramework).filter(Boolean));\n}\n\nfunction dedupe(tokens: string[]): string[] {\n return Array.from(new Set(tokens));\n}\n\n/**\n * Icon identifiers for the type→card registry. `-common` is imported by the\n * backend, so it stays React-free: the frontend maps these identifiers to\n * actual icon components.\n */\nexport type ResourceTypeIcon =\n | 'tools'\n | 'robot'\n | 'flash'\n | 'plug'\n | 'puzzle'\n | 'store';\n\nexport interface ResourceTypeInfo {\n type: ResourceType;\n /** Singular display label, e.g. \"Skill\" (card type caption). */\n label: string;\n /** Plural display label, e.g. \"Skills\" (stat tiles, filters). */\n pluralLabel: string;\n /** Icon identifier resolved to a component by the frontend. */\n icon: ResourceTypeIcon;\n /**\n * Colour role name; the frontend resolves it to the plugin-owned\n * `--devaihub-type-<role>` custom properties (ADR-0008, NOS palette).\n */\n colorRole: ResourceType;\n}\n\n/**\n * The typed type→card registry (ADR-0003/ADR-0008): producer vocabulary and\n * consumer rendering share this single source, so they cannot drift. Adding\n * a type is one entry here plus one CSS token pair in the frontend.\n */\nexport const RESOURCE_TYPE_REGISTRY: Record<ResourceType, ResourceTypeInfo> = {\n skill: {\n type: 'skill',\n label: 'Skill',\n pluralLabel: 'Skills',\n icon: 'tools',\n colorRole: 'skill',\n },\n agent: {\n type: 'agent',\n label: 'Agent',\n pluralLabel: 'Agents',\n icon: 'robot',\n colorRole: 'agent',\n },\n hook: {\n type: 'hook',\n label: 'Hook',\n pluralLabel: 'Hooks',\n icon: 'flash',\n colorRole: 'hook',\n },\n 'mcp-config': {\n type: 'mcp-config',\n label: 'MCP Config',\n pluralLabel: 'MCP Configs',\n icon: 'plug',\n colorRole: 'mcp-config',\n },\n plugin: {\n type: 'plugin',\n label: 'Plugin',\n pluralLabel: 'Plugins',\n icon: 'puzzle',\n colorRole: 'plugin',\n },\n marketplace: {\n type: 'marketplace',\n label: 'Marketplace',\n pluralLabel: 'Marketplaces',\n icon: 'store',\n colorRole: 'marketplace',\n },\n};\n\n/**\n * The flat contract the backend returns to the frontend. The frontend never\n * sees a raw Backstage `Entity` — only this (architecture.md).\n */\nexport interface ResourceSummary {\n entityRef: string;\n name: string;\n title?: string;\n description?: string;\n tags: string[];\n type: ResourceType;\n lifecycle: string;\n owner?: string;\n sourceLocation?: string;\n frameworks: string[];\n version?: string;\n kind: string;\n /** `plugin` dependsOn count; unpopulated until containment lands (issue #32). */\n childCount?: number;\n helpText?: string;\n annotations: Record<string, string>;\n}\n\nexport interface ResourceListResponse {\n items: ResourceSummary[];\n}\n\n/**\n * The shape of a resource's body (CONTEXT.md: bodies are type-shaped, not\n * uniformly markdown). Drives detail-panel rendering: markdown is rendered,\n * json is pretty-printed and syntax-highlighted (ADR-0014). `plugin` and\n * `marketplace` bodies are the real Claude Code `plugin.json` /\n * `marketplace.json` manifest — not a hand-authored pointer doc, superseding\n * ADR-0010's markdown-only body for `marketplace`.\n */\nexport type BodyShape = 'markdown' | 'json';\n\nexport function getBodyShape(type: ResourceType): BodyShape {\n return type === 'mcp-config' || type === 'plugin' || type === 'marketplace'\n ? 'json'\n : 'markdown';\n}\n\n/**\n * Whether downloading the body delivers the artifact itself. True for every\n * type: `plugin`/`marketplace` bodies are now the real manifest JSON, so a\n * download delivers that file itself rather than a non-actionable pointer\n * doc (ADR-0014, superseding ADR-0009's amendment and ADR-0010 on this\n * point).\n */\nexport function hasDownloadableArtifact(_type: ResourceType): boolean {\n return true;\n}\n\n/**\n * Whether copying the body to the clipboard is a meaningful install action.\n * True for every type: `marketplace`'s body is now the real manifest JSON,\n * not an instructions doc, so copying it is no longer a misrepresentation\n * (ADR-0014, superseding ADR-0010 on this point).\n */\nexport function hasCopyableBody(_type: ResourceType): boolean {\n return true;\n}\n\n/**\n * How a body reaches its install path.\n *\n * `drop-in` — the body becomes the file (or directory) at `path`, which is\n * the resource's own; writing it touches nothing else.\n *\n * `merge` — `path` is a shared settings file the user already owns, and the\n * body is a fragment to merge into it. The distinction is not cosmetic:\n * following a `merge` target as if it were a `drop-in` overwrites the user's\n * existing configuration, so the UI must never present the two identically.\n */\nexport type InstallMode = 'drop-in' | 'merge';\n\nexport interface ResourceInstallTarget {\n /** Workspace-relative path. */\n path: string;\n mode: InstallMode;\n}\n\n/**\n * The concrete framework tokens a body can be installed into — `all` is a\n * wildcard over these, never a destination itself.\n */\nexport const INSTALLABLE_FRAMEWORKS = [\n 'claude-code',\n 'github-copilot',\n 'google-gemini',\n 'cursor',\n 'opencode',\n] as const;\n\n/**\n * Convention table: (type, framework) → where the body goes and how.\n * A missing entry means the pair has no filesystem convention — `plugin` and\n * `marketplace` bodies are pointers that install through their framework\n * (ADR-0009, ADR-0010), so they carry install commands rather than paths.\n */\nconst INSTALL_PATHS: Record<\n ResourceType,\n Record<string, { mode: InstallMode; path: (name: string) => string }>\n> = {\n // Every host reads a skill as a self-contained directory. Copilot also\n // picks up `.claude/skills`, but each tool's own path is what we recommend;\n // `.agents/skills` is the cross-tool alias Copilot and Gemini both honour,\n // which makes it the right answer for an unrecognised framework.\n skill: {\n 'claude-code': { mode: 'drop-in', path: name => `.claude/skills/${name}/` },\n 'github-copilot': {\n mode: 'drop-in',\n path: name => `.github/skills/${name}/`,\n },\n 'google-gemini': {\n mode: 'drop-in',\n path: name => `.gemini/skills/${name}/`,\n },\n cursor: { mode: 'drop-in', path: name => `.cursor/skills/${name}/` },\n opencode: { mode: 'drop-in', path: name => `.opencode/skills/${name}/` },\n default: { mode: 'drop-in', path: name => `.agents/skills/${name}/` },\n },\n agent: {\n 'claude-code': {\n mode: 'drop-in',\n path: name => `.claude/agents/${name}.md`,\n },\n 'github-copilot': {\n mode: 'drop-in',\n path: name => `.github/agents/${name}.agent.md`,\n },\n 'google-gemini': {\n mode: 'drop-in',\n path: name => `.gemini/agents/${name}.md`,\n },\n cursor: { mode: 'drop-in', path: name => `.cursor/rules/${name}.mdc` },\n opencode: { mode: 'drop-in', path: name => `.opencode/agents/${name}.md` },\n default: { mode: 'drop-in', path: name => `.ai/agents/${name}.md` },\n },\n // All four hosts have a hook system, but only Copilot gives each hook its\n // own file; the rest register hooks inside a shared settings document.\n // OpenCode has no declarative hook config at all — hooks are exclusively\n // JS/TS plugin modules — so it deliberately carries no entry here, the same\n // way it carries no `default`.\n hook: {\n 'claude-code': { mode: 'merge', path: () => `.claude/settings.json` },\n 'github-copilot': {\n mode: 'drop-in',\n path: name => `.github/hooks/${name}.json`,\n },\n 'google-gemini': { mode: 'merge', path: () => `.gemini/settings.json` },\n cursor: { mode: 'merge', path: () => `.cursor/hooks.json` },\n },\n 'mcp-config': {\n 'claude-code': { mode: 'merge', path: () => `.mcp.json` },\n 'github-copilot': { mode: 'merge', path: () => `.vscode/mcp.json` },\n 'google-gemini': { mode: 'merge', path: () => `.gemini/settings.json` },\n cursor: { mode: 'merge', path: () => `.cursor/mcp.json` },\n opencode: { mode: 'merge', path: () => `opencode.json` },\n },\n plugin: {},\n marketplace: {},\n};\n\n/**\n * Where a resource's body installs for one framework, and whether that path\n * is the body's own file or a settings file to merge into. Returns\n * `undefined` when the (type, framework) pair has no filesystem convention.\n */\nexport function getResourceInstallTarget(\n type: ResourceType,\n framework: string,\n name: string,\n): ResourceInstallTarget | undefined {\n const conventions = INSTALL_PATHS[type];\n const entry =\n conventions[normalizeFramework(framework)] ?? conventions.default;\n return entry ? { path: entry.path(name), mode: entry.mode } : undefined;\n}\n\n/**\n * The frameworks to show install paths for. An empty list means the resource\n * declared no compatibility and gets the neutral `default` convention; `all`\n * is a wildcard and expands to every installable host, so it never collapses\n * to a single row — or, for types with no `default`, to no rows at all.\n */\nexport function expandInstallFrameworks(frameworks: string[]): string[] {\n const normalized = dedupe(frameworks.map(normalizeFramework));\n if (normalized.length === 0) {\n return ['default'];\n }\n if (normalized.includes('all')) {\n return [...INSTALLABLE_FRAMEWORKS];\n }\n return normalized;\n}\n\n/*\n * Marketplace journey helpers (ADR-0010). A marketplace installs in two\n * steps — register the catalog with the AI tool, then install plugins from\n * it. Both Claude Code and Copilot CLI read the same\n * `.claude-plugin/marketplace.json` format and address the repo as\n * `owner/repo`; the plugin derives that slug from the mandatory\n * `source-location` (the producer contract requires the body doc to live\n * inside the marketplace repo). No derivation → the UI falls back to the\n * rendered body, which stays canonical.\n */\n\n/** Frameworks with a marketplace-add concept (Cursor/Gemini have none). */\nexport const MARKETPLACE_CAPABLE_FRAMEWORKS = [\n 'claude-code',\n 'github-copilot',\n] as const;\n\n/**\n * Narrow a resource's declared frameworks to those a given journey can\n * actually launch. A deep link is a claim about compatibility: offering\n * \"Install in Claude\" for a resource that never declared `claude-code` tells\n * the user something untrue about the resource.\n *\n * `all` and an empty list both mean \"unrestricted\" and expand to every\n * capable host. Anything else is intersected, so a declared framework with no\n * handler (Gemini) — or an unknown token — yields no links rather than\n * silently widening to every host.\n */\nexport function resolveCapableFrameworks(\n declared: string[],\n capable: readonly string[],\n): string[] {\n const normalized = dedupe(declared.map(normalizeFramework));\n if (normalized.length === 0 || normalized.includes('all')) {\n return [...capable];\n }\n return normalized.filter(f => capable.includes(f));\n}\n\n/**\n * Derive the `owner/repo` slug from a marketplace's `source-location`.\n * GitHub URLs only — other hosts return `undefined` (body-only fallback).\n */\nexport function getMarketplaceRepoSlug(\n sourceLocation: string | undefined,\n): string | undefined {\n if (!sourceLocation) {\n return undefined;\n }\n let url: URL;\n try {\n url = new URL(sourceLocation.replace(/^url:/, ''));\n } catch {\n return undefined;\n }\n if (url.hostname !== 'github.com') {\n return undefined;\n }\n const [owner, repo] = url.pathname.split('/').filter(Boolean);\n if (!owner || !repo) {\n return undefined;\n }\n return `${owner}/${repo.replace(/\\.git$/, '')}`;\n}\n\n/**\n * A one-click launcher for an add command: a custom-scheme URL that opens\n * the host app with the command pre-filled in the prompt box. The host\n * never auto-executes it — the user reviews and presses Enter — and an\n * unregistered scheme is a harmless no-op, so these complement the copy\n * button rather than replace it.\n */\nexport interface ResourceDeepLink {\n /** Host application name; render as \"Add in <label>\" / \"Install in <label>\". */\n label: string;\n href: string;\n}\n\nexport interface MarketplaceAddCommand {\n framework: string;\n command: string;\n deepLinks: ResourceDeepLink[];\n}\n\n/**\n * The copyable marketplace-add command per compatible framework. `all`,\n * unknown-only, or empty framework lists expand to every capable framework;\n * frameworks without a marketplace concept produce no row.\n *\n * Claude Code carries a single deep link on the CLI's documented\n * `claude-cli://open?q=` handler, which opens a Claude Code terminal\n * session with the command pre-filled regardless of the user's editor.\n *\n * Copilot's CLI still has no URI scheme, but Copilot's *other* surface does:\n * VS Code 1.113 ships `vscode://chat-plugin/add-marketplace?ref=`, which\n * accepts a plain or base64 `owner/repo` and always shows a confirmation\n * dialog before registering the marketplace (it also dedupes against the\n * user's existing entries). So the Copilot row keeps its terminal command for\n * CLI users *and* gains editor launchers, mirroring how `mcp-config` already\n * maps `github-copilot` onto the VS Code stable/Insiders pair.\n */\nexport function getMarketplaceAddCommands(\n frameworks: string[],\n repoSlug: string,\n): MarketplaceAddCommand[] {\n const capable = resolveCapableFrameworks(\n frameworks,\n MARKETPLACE_CAPABLE_FRAMEWORKS,\n );\n const commands: MarketplaceAddCommand[] = [];\n for (const framework of capable) {\n if (framework === 'claude-code') {\n const command = `/plugin marketplace add ${repoSlug}`;\n commands.push({\n framework,\n command,\n deepLinks: [\n {\n label: 'Claude',\n href: `claude-cli://open?q=${encodeURIComponent(command)}`,\n },\n ],\n });\n } else if (framework === 'github-copilot') {\n const ref = encodeURIComponent(repoSlug);\n commands.push({\n framework,\n command: `copilot plugin marketplace add ${repoSlug}`,\n deepLinks: [\n {\n label: 'VS Code',\n href: `vscode://chat-plugin/add-marketplace?ref=${ref}`,\n },\n {\n label: 'VS Code Insiders',\n href: `vscode-insiders://chat-plugin/add-marketplace?ref=${ref}`,\n },\n ],\n });\n }\n }\n return commands;\n}\n\n/** The raw-file URL for a GitHub blob `source-location`; else `undefined`. */\nfunction getRawGithubFileUrl(\n sourceLocation: string | undefined,\n): string | undefined {\n if (!sourceLocation) {\n return undefined;\n }\n let url: URL;\n try {\n url = new URL(sourceLocation.replace(/^url:/, ''));\n } catch {\n return undefined;\n }\n if (url.hostname !== 'github.com') {\n return undefined;\n }\n const [owner, repo, marker, ref, ...path] = url.pathname\n .split('/')\n .filter(Boolean);\n if (!owner || !repo || marker !== 'blob' || !ref || path.length === 0) {\n return undefined;\n }\n return `https://raw.githubusercontent.com/${owner}/${repo}/${ref}/${path.join(\n '/',\n )}`;\n}\n\n/**\n * A launcher that opens an agent with an install prompt pre-filled. Neither\n * host auto-executes: the user reads the prompt and presses Enter. Claude Code\n * and Cursor are the two tools exposing a generic prompt URI — Copilot's\n * editor has purpose-built routes but no prompt handler, and Gemini has\n * neither (its `gemini.google.com/app?prompt=` link drives the web app, which\n * cannot write to a local workspace).\n *\n * Cursor truncates a deeplink at the first raw `&`, so the prompt must be\n * percent-encoded — `encodeURIComponent` handles that. Cursor also caps the\n * URL at ~8000 characters; these prompts are a URL plus a path, far under it.\n */\nfunction claudePromptLink(prompt: string): ResourceDeepLink {\n return {\n label: 'Claude',\n href: `claude-cli://open?q=${encodeURIComponent(prompt)}`,\n };\n}\n\nfunction cursorPromptLink(prompt: string): ResourceDeepLink {\n return {\n label: 'Cursor',\n href: `cursor://anysphere.cursor-deeplink/prompt?text=${encodeURIComponent(\n prompt,\n )}`,\n };\n}\n\n/**\n * Frameworks with a one-click agent-install handler (Gemini has none).\n * OpenCode's Desktop app does register a real `opencode://` scheme, but every\n * route (`open-project`, `new-session`) requires an absolute local\n * `directory` param a web page cannot supply, so it's excluded here too.\n */\nexport const AGENT_LINK_CAPABLE_FRAMEWORKS = [\n 'claude-code',\n 'github-copilot',\n 'cursor',\n] as const;\n\n/** Hosts reachable by a generic prompt URI — the only route for skill/hook. */\nexport const PROMPT_LINK_CAPABLE_FRAMEWORKS = [\n 'claude-code',\n 'cursor',\n] as const;\n\n/**\n * The plain http(s) URL behind a `source-location`. Unlike\n * `getRawGithubFileUrl` this accepts **directory (`tree`) URLs**, which is what\n * a multi-file `skill` actually carries — a raw-file URL would resolve to\n * `undefined` for exactly the type that most needs a launcher.\n */\nfunction getSourceUrl(sourceLocation: string | undefined): string | undefined {\n if (!sourceLocation) {\n return undefined;\n }\n try {\n const url = new URL(sourceLocation.replace(/^url:/, ''));\n return url.protocol === 'http:' || url.protocol === 'https:'\n ? url.toString()\n : undefined;\n } catch {\n return undefined;\n }\n}\n\n/**\n * One-click launchers for the types with no purpose-built install route —\n * `skill` and `hook`. Both ride the generic prompt handlers, so the prompt\n * names the *host's own* install path and respects its install mode: a `merge`\n * target is asked to be merged into, never overwritten, so a launcher can\n * never cost the user their existing settings.\n *\n * Returns `[]` when the source URL cannot be derived, or for hosts with no\n * prompt route (Copilot, Gemini) — the path list and copy/download remain.\n */\nexport function getPromptInstallLinks(\n type: ResourceType,\n sourceLocation: string | undefined,\n name: string,\n frameworks: string[] = [],\n): ResourceDeepLink[] {\n return getInstallSteps(type, sourceLocation, name, frameworks)\n .map(step => step.link)\n .filter((link): link is ResourceDeepLink => !!link);\n}\n\n/**\n * Everything a single host needs to install a resource: where the body goes,\n * the prompt that puts it there, and a launcher when the host has a URI to\n * launch.\n *\n * `link` is absent for hosts with no prompt route — Copilot and Gemini. That\n * absence is why the step carries `prompt` as text: a host without a URI is\n * not a host without an install path, and the same instruction that a launcher\n * would pre-fill can be pasted into that agent by hand. Every declared\n * framework therefore gets something actionable, not a bare path.\n */\nexport interface ResourceInstallStep {\n framework: string;\n target: ResourceInstallTarget;\n /** The install instruction, ready to run in that host's agent. */\n prompt: string;\n /** One-click launcher, where the host exposes a generic prompt URI. */\n link?: ResourceDeepLink;\n}\n\nexport function getInstallSteps(\n type: ResourceType,\n sourceLocation: string | undefined,\n name: string,\n frameworks: string[] = [],\n): ResourceInstallStep[] {\n const sourceUrl = getSourceUrl(sourceLocation);\n if (!sourceUrl) {\n return [];\n }\n const label = RESOURCE_TYPE_REGISTRY[type].label.toLowerCase();\n const steps: ResourceInstallStep[] = [];\n for (const framework of expandInstallFrameworks(frameworks)) {\n const target = getResourceInstallTarget(type, framework, name);\n if (!target) {\n continue;\n }\n const prompt =\n target.mode === 'merge'\n ? `Install the \"${name}\" ${label}: fetch ${sourceUrl} and merge it into ${target.path}, keeping my existing settings intact.`\n : `Install the \"${name}\" ${label}: fetch ${sourceUrl} and save it to ${target.path}.`;\n let link: ResourceDeepLink | undefined;\n if (framework === 'claude-code') {\n link = claudePromptLink(prompt);\n } else if (framework === 'cursor') {\n link = cursorPromptLink(prompt);\n }\n steps.push({ framework, target, prompt, link });\n }\n return steps;\n}\n\n/** Frameworks with a one-click MCP-install handler (Gemini has none). */\nexport const MCP_LINK_CAPABLE_FRAMEWORKS = [\n 'claude-code',\n 'github-copilot',\n 'cursor',\n] as const;\n\n/**\n * One-click install links for an `agent` resource, restricted to the\n * resource's own compatible frameworks. VS Code stable and Insiders use the\n * native `chat-agent/install?url=` handler (the mechanism behind\n * awesome-copilot's Install buttons) and are Copilot's hosts, so they follow\n * `github-copilot`: VS Code downloads the file at `url` and asks the user\n * where to save it. Claude Code uses the `claude-cli://open?q=` handler with\n * an install prompt pre-filled to the convention path — reviewed and sent by\n * the user, never auto-executed. All derive from the GitHub blob URL in\n * `source-location`; anything else returns `[]` (copy/download fallback).\n */\nexport function getAgentInstallLinks(\n sourceLocation: string | undefined,\n name: string,\n frameworks: string[] = [],\n): ResourceDeepLink[] {\n const rawUrl = getRawGithubFileUrl(sourceLocation);\n if (!rawUrl) {\n return [];\n }\n const capable = resolveCapableFrameworks(\n frameworks,\n AGENT_LINK_CAPABLE_FRAMEWORKS,\n );\n const encodedRawUrl = encodeURIComponent(rawUrl);\n const installPrompt = (framework: string) => {\n const target = getResourceInstallTarget('agent', framework, name);\n return `Install this agent: fetch ${rawUrl} and save it to ${target?.path}`;\n };\n const links: ResourceDeepLink[] = [];\n for (const framework of capable) {\n if (framework === 'claude-code') {\n links.push(claudePromptLink(installPrompt('claude-code')));\n } else if (framework === 'cursor') {\n links.push(cursorPromptLink(installPrompt('cursor')));\n } else if (framework === 'github-copilot') {\n links.push({\n label: 'VS Code',\n href: `vscode:chat-agent/install?url=${encodedRawUrl}`,\n });\n links.push({\n label: 'VS Code Insiders',\n href: `vscode-insiders:chat-agent/install?url=${encodedRawUrl}`,\n });\n }\n }\n return links;\n}\n\n/**\n * One-click install links for an `mcp-config` resource, derived from its body —\n * the canonical `.mcp.json` snippet (spec §3.4). VS Code carries a native\n * `vscode:mcp/install?{json}` handler (Insiders scheme twin), Cursor a\n * documented `cursor://anysphere.cursor-deeplink/mcp/install` one, and\n * Claude Code gets a `claude-cli://open?q=` prompt around\n * `claude mcp add-json` — pre-filled, reviewed, never auto-executed.\n * Hosts follow the resource's own compatible frameworks: `all`/empty expand\n * to every capable host, anything else is intersected, so an mcp-config\n * declaring only Gemini (no handler) gets no links rather than every host's.\n * A missing, unparseable, or multi-server body returns `[]` (copy fallback) —\n * the body stays canonical, links are conveniences derived from it.\n */\nexport function getMcpInstallLinks(\n frameworks: string[],\n resourceName: string,\n bodyContent: string | undefined,\n): ResourceDeepLink[] {\n if (!bodyContent) {\n return [];\n }\n let parsed: unknown;\n try {\n parsed = JSON.parse(bodyContent);\n } catch {\n return [];\n }\n if (typeof parsed !== 'object' || parsed === null) {\n return [];\n }\n\n let serverName = resourceName;\n let config: Record<string, unknown>;\n const servers = (parsed as { mcpServers?: unknown }).mcpServers;\n if (servers !== undefined) {\n if (typeof servers !== 'object' || servers === null) {\n return [];\n }\n const entries = Object.entries(servers as Record<string, unknown>);\n if (\n entries.length !== 1 ||\n typeof entries[0][1] !== 'object' ||\n entries[0][1] === null\n ) {\n return [];\n }\n serverName = entries[0][0];\n config = entries[0][1] as Record<string, unknown>;\n } else if ('command' in parsed || 'url' in parsed) {\n config = parsed as Record<string, unknown>;\n } else {\n return [];\n }\n\n const capable = resolveCapableFrameworks(\n frameworks,\n MCP_LINK_CAPABLE_FRAMEWORKS,\n );\n\n const configJson = JSON.stringify(config);\n const links: ResourceDeepLink[] = [];\n for (const framework of capable) {\n if (framework === 'claude-code') {\n const prompt = encodeURIComponent(\n `Install this MCP config by running: claude mcp add-json ${serverName} '${configJson}'`,\n );\n links.push({ label: 'Claude', href: `claude-cli://open?q=${prompt}` });\n } else if (framework === 'github-copilot') {\n const vsConfig = encodeURIComponent(\n JSON.stringify({ name: serverName, ...config }),\n );\n links.push({ label: 'VS Code', href: `vscode:mcp/install?${vsConfig}` });\n links.push({\n label: 'VS Code Insiders',\n href: `vscode-insiders:mcp/install?${vsConfig}`,\n });\n } else if (framework === 'cursor') {\n try {\n links.push({\n label: 'Cursor',\n href: `cursor://anysphere.cursor-deeplink/mcp/install?name=${encodeURIComponent(\n serverName,\n )}&config=${btoa(configJson)}`,\n });\n } catch {\n // btoa rejects non-Latin1 config content — skip the Cursor link.\n }\n }\n }\n return links;\n}\n\n/**\n * Step two of the journey: how to install a plugin from the marketplace.\n * `marketplaceName` is the entity name, which the producer contract requires\n * to equal the `name` in `marketplace.json`.\n */\nexport function getMarketplaceInstallTemplate(marketplaceName: string): string {\n return `/plugin install <plugin>@${marketplaceName}`;\n}\n\n/**\n * The `.claude/settings.json` snippet a team commits so collaborators are\n * prompted to install the marketplace automatically (Claude Code).\n */\nexport function getMarketplaceTeamSnippet(\n marketplaceName: string,\n repoSlug: string,\n): string {\n return JSON.stringify(\n {\n extraKnownMarketplaces: {\n [marketplaceName]: { source: { source: 'github', repo: repoSlug } },\n },\n },\n null,\n 2,\n );\n}\n"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"resources.esm.js","sources":["../src/resources.ts"],"sourcesContent":["/**\n * The v2 read contract: AiResource vocabulary, the flat ResourceSummary\n * returned by the backend, and the shared framework resolver (ADR-0003).\n *\n * The legacy asset-model types in `types.ts` coexist with this module until\n * the legacy silo is deleted (slice 8, issue #34).\n */\n\n/** The six canonical `spec.type` values of an AiResource (ADR-0003, ADR-0010). */\nexport const RESOURCE_TYPES = [\n 'skill',\n 'agent',\n 'hook',\n 'mcp-config',\n 'plugin',\n 'marketplace',\n] as const;\n\nexport type ResourceType = (typeof RESOURCE_TYPES)[number];\n\nexport function isResourceType(value: unknown): value is ResourceType {\n return (\n typeof value === 'string' &&\n (RESOURCE_TYPES as readonly string[]).includes(value)\n );\n}\n\n/**\n * The AiResource types a `plugin` may contain — restricted to the component\n * kinds a real Claude Code / GitHub Copilot plugin manifest allows: `skills`,\n * `agents`, `hooks` and `mcpServers` (schemastore `claude-code-plugin` /\n * `claude-code-marketplace.json`). A plugin's other manifest components\n * (`commands`, `lspServers`, `outputStyles`, `themes`, `channels`, `monitors`)\n * have no AiResource type, so they are never members here; other plugins are\n * `dependencies`, not containment.\n */\nexport const PLUGIN_MEMBER_TYPES: readonly ResourceType[] = [\n 'skill',\n 'agent',\n 'hook',\n 'mcp-config',\n];\n\n/**\n * The AiResource types a `marketplace` may contain: `plugin` only. A real\n * `.claude-plugin/marketplace.json` `plugins` array lists plugins and nothing\n * else (required by the schema Claude Code and Copilot CLI both read).\n */\nexport const MARKETPLACE_MEMBER_TYPES: readonly ResourceType[] = ['plugin'];\n\n/**\n * Whether a container of `parent` type renders a child of `child` type as a\n * member — restricted to what a real Claude/Copilot plugin or marketplace\n * manifest permits (ADR-0010/0015). A `marketplace` contains `plugin` only; a\n * `plugin` contains the component types DevAI Hub models. Any other pairing is\n * silently not rendered. This is a rendering convention, not catalog\n * validation (upstream `allowedKinds` is the broad `[\"AiResource\"]`).\n */\nexport function isRenderableContainment(\n parent: ResourceType,\n child: ResourceType,\n): boolean {\n if (parent === 'marketplace') return MARKETPLACE_MEMBER_TYPES.includes(child);\n if (parent === 'plugin') return PLUGIN_MEMBER_TYPES.includes(child);\n return false;\n}\n\n/** Annotation prefix owned by DevAI Hub. */\nexport const DEVAIHUB_ANNOTATION_PREFIX = 'devaihub.io';\n\n/** Comma-separated compatible-framework tokens (non-skill read path, ADR-0003). */\nexport const ANNOTATION_COMPATIBLE_FRAMEWORKS = `${DEVAIHUB_ANNOTATION_PREFIX}/compatible-frameworks`;\n\n/** Optional free-form help text shown on the detail panel. */\nexport const ANNOTATION_HELP = `${DEVAIHUB_ANNOTATION_PREFIX}/help`;\n\n/** Known framework tokens; unknown tokens pass through as-is (ADR-0003). */\nexport const KNOWN_FRAMEWORKS = [\n 'github-copilot',\n 'claude-code',\n 'cursor',\n 'google-gemini',\n 'opencode',\n 'all',\n] as const;\n\nexport type FrameworkToken = (typeof KNOWN_FRAMEWORKS)[number];\n\nconst FRAMEWORK_ALIASES: Record<string, FrameworkToken> = {\n copilot: 'github-copilot',\n claude: 'claude-code',\n gemini: 'google-gemini',\n};\n\n/**\n * Normalise a framework token: trim, lowercase, resolve aliases\n * (`claude` → `claude-code`). Unknown tokens pass through literally.\n */\nexport function normalizeFramework(token: string): string {\n const t = token.trim().toLowerCase();\n return FRAMEWORK_ALIASES[t] ?? t;\n}\n\n/**\n * The minimal structural shape of an AiResource entity that the read helpers\n * need. A real `@backstage/catalog-model` `Entity` satisfies it, but keeping\n * the shape local means `-common` (and therefore the frontend) never depends\n * on catalog packages (architecture.md).\n */\nexport interface AiResourceEntityLike {\n metadata: {\n annotations?: Record<string, string>;\n };\n spec?: {\n type?: unknown;\n /** Native compatible-frameworks field, honoured regardless of `spec.type`. */\n agents?: unknown;\n [key: string]: unknown;\n };\n}\n\n/**\n * Resolve the compatible frameworks of an AiResource (ADR-0003):\n * 1. a non-empty `spec.agents` → the native field, for any `spec.type`;\n * 2. otherwise → the `devaihub.io/compatible-frameworks` annotation\n * (also the fallback when `spec.agents` is empty/absent);\n * 3. otherwise → `[]`.\n * Tokens are normalised; unknown tokens pass through. `spec.agents` is\n * preferred over the annotation because it lives on the entity itself\n * rather than in an annotation namespace we may retire.\n */\nexport function getFrameworks(entity: AiResourceEntityLike): string[] {\n if (Array.isArray(entity.spec?.agents)) {\n const agents = entity.spec.agents\n .filter((a): a is string => typeof a === 'string')\n .map(normalizeFramework)\n .filter(Boolean);\n if (agents.length > 0) {\n return dedupe(agents);\n }\n }\n\n const raw = entity.metadata.annotations?.[ANNOTATION_COMPATIBLE_FRAMEWORKS];\n if (!raw) {\n return [];\n }\n return dedupe(raw.split(',').map(normalizeFramework).filter(Boolean));\n}\n\nfunction dedupe(tokens: string[]): string[] {\n return Array.from(new Set(tokens));\n}\n\n/**\n * Icon identifiers for the type→card registry. `-common` is imported by the\n * backend, so it stays React-free: the frontend maps these identifiers to\n * actual icon components.\n */\nexport type ResourceTypeIcon =\n | 'tools'\n | 'robot'\n | 'flash'\n | 'plug'\n | 'puzzle'\n | 'store';\n\nexport interface ResourceTypeInfo {\n type: ResourceType;\n /** Singular display label, e.g. \"Skill\" (card type caption). */\n label: string;\n /** Plural display label, e.g. \"Skills\" (stat tiles, filters). */\n pluralLabel: string;\n /** Icon identifier resolved to a component by the frontend. */\n icon: ResourceTypeIcon;\n /**\n * Colour role name; the frontend resolves it to the plugin-owned\n * `--devaihub-type-<role>` custom properties (ADR-0008, NOS palette).\n */\n colorRole: ResourceType;\n}\n\n/**\n * The typed type→card registry (ADR-0003/ADR-0008): producer vocabulary and\n * consumer rendering share this single source, so they cannot drift. Adding\n * a type is one entry here plus one CSS token pair in the frontend.\n */\nexport const RESOURCE_TYPE_REGISTRY: Record<ResourceType, ResourceTypeInfo> = {\n skill: {\n type: 'skill',\n label: 'Skill',\n pluralLabel: 'Skills',\n icon: 'tools',\n colorRole: 'skill',\n },\n agent: {\n type: 'agent',\n label: 'Agent',\n pluralLabel: 'Agents',\n icon: 'robot',\n colorRole: 'agent',\n },\n hook: {\n type: 'hook',\n label: 'Hook',\n pluralLabel: 'Hooks',\n icon: 'flash',\n colorRole: 'hook',\n },\n 'mcp-config': {\n type: 'mcp-config',\n label: 'MCP Config',\n pluralLabel: 'MCP Configs',\n icon: 'plug',\n colorRole: 'mcp-config',\n },\n plugin: {\n type: 'plugin',\n label: 'Plugin',\n pluralLabel: 'Plugins',\n icon: 'puzzle',\n colorRole: 'plugin',\n },\n marketplace: {\n type: 'marketplace',\n label: 'Marketplace',\n pluralLabel: 'Marketplaces',\n icon: 'store',\n colorRole: 'marketplace',\n },\n};\n\n/**\n * The flat contract the backend returns to the frontend. The frontend never\n * sees a raw Backstage `Entity` — only this (architecture.md).\n */\nexport interface ResourceSummary {\n entityRef: string;\n name: string;\n title?: string;\n description?: string;\n tags: string[];\n type: ResourceType;\n lifecycle: string;\n owner?: string;\n sourceLocation?: string;\n frameworks: string[];\n version?: string;\n kind: string;\n /**\n * Entity refs this container declares as its contents — a `marketplace`'s\n * `spec.plugins`, a `plugin`'s `spec.skills`; empty for leaf types. Filtered\n * to the caller's visible set and to the child types the container renders\n * (ADR-0015). The frontend resolves each ref against the summaries it holds.\n */\n children: string[];\n /**\n * Entity refs of the containers that declare this resource — the in-memory\n * inverse of `children`, built over the same caller-visible catalog read\n * (ADR-0015). A resource can belong to several containers.\n */\n parents: string[];\n /** Count of `children` (their declared, caller-visible members). */\n childCount?: number;\n helpText?: string;\n annotations: Record<string, string>;\n}\n\nexport interface ResourceListResponse {\n items: ResourceSummary[];\n}\n\n/**\n * The shape of a resource's body (CONTEXT.md: bodies are type-shaped, not\n * uniformly markdown). Drives detail-panel rendering: markdown is rendered,\n * json is pretty-printed and syntax-highlighted (ADR-0014). `plugin` and\n * `marketplace` bodies are the real Claude Code `plugin.json` /\n * `marketplace.json` manifest — not a hand-authored pointer doc, superseding\n * ADR-0010's markdown-only body for `marketplace`.\n */\nexport type BodyShape = 'markdown' | 'json';\n\nexport function getBodyShape(type: ResourceType): BodyShape {\n return type === 'mcp-config' || type === 'plugin' || type === 'marketplace'\n ? 'json'\n : 'markdown';\n}\n\n/**\n * Whether downloading the body delivers the artifact itself. True for every\n * type: `plugin`/`marketplace` bodies are now the real manifest JSON, so a\n * download delivers that file itself rather than a non-actionable pointer\n * doc (ADR-0014, superseding ADR-0009's amendment and ADR-0010 on this\n * point).\n */\nexport function hasDownloadableArtifact(_type: ResourceType): boolean {\n return true;\n}\n\n/**\n * Whether copying the body to the clipboard is a meaningful install action.\n * True for every type: `marketplace`'s body is now the real manifest JSON,\n * not an instructions doc, so copying it is no longer a misrepresentation\n * (ADR-0014, superseding ADR-0010 on this point).\n */\nexport function hasCopyableBody(_type: ResourceType): boolean {\n return true;\n}\n\n/**\n * How a body reaches its install path.\n *\n * `drop-in` — the body becomes the file (or directory) at `path`, which is\n * the resource's own; writing it touches nothing else.\n *\n * `merge` — `path` is a shared settings file the user already owns, and the\n * body is a fragment to merge into it. The distinction is not cosmetic:\n * following a `merge` target as if it were a `drop-in` overwrites the user's\n * existing configuration, so the UI must never present the two identically.\n */\nexport type InstallMode = 'drop-in' | 'merge';\n\nexport interface ResourceInstallTarget {\n /** Workspace-relative path. */\n path: string;\n mode: InstallMode;\n}\n\n/**\n * The concrete framework tokens a body can be installed into — `all` is a\n * wildcard over these, never a destination itself.\n */\nexport const INSTALLABLE_FRAMEWORKS = [\n 'claude-code',\n 'github-copilot',\n 'google-gemini',\n 'cursor',\n 'opencode',\n] as const;\n\n/**\n * Convention table: (type, framework) → where the body goes and how.\n * A missing entry means the pair has no filesystem convention — `plugin` and\n * `marketplace` bodies are pointers that install through their framework\n * (ADR-0009, ADR-0010), so they carry install commands rather than paths.\n */\nconst INSTALL_PATHS: Record<\n ResourceType,\n Record<string, { mode: InstallMode; path: (name: string) => string }>\n> = {\n // Every host reads a skill as a self-contained directory. Copilot also\n // picks up `.claude/skills`, but each tool's own path is what we recommend;\n // `.agents/skills` is the cross-tool alias Copilot and Gemini both honour,\n // which makes it the right answer for an unrecognised framework.\n skill: {\n 'claude-code': { mode: 'drop-in', path: name => `.claude/skills/${name}/` },\n 'github-copilot': {\n mode: 'drop-in',\n path: name => `.github/skills/${name}/`,\n },\n 'google-gemini': {\n mode: 'drop-in',\n path: name => `.gemini/skills/${name}/`,\n },\n cursor: { mode: 'drop-in', path: name => `.cursor/skills/${name}/` },\n opencode: { mode: 'drop-in', path: name => `.opencode/skills/${name}/` },\n default: { mode: 'drop-in', path: name => `.agents/skills/${name}/` },\n },\n agent: {\n 'claude-code': {\n mode: 'drop-in',\n path: name => `.claude/agents/${name}.md`,\n },\n 'github-copilot': {\n mode: 'drop-in',\n path: name => `.github/agents/${name}.agent.md`,\n },\n 'google-gemini': {\n mode: 'drop-in',\n path: name => `.gemini/agents/${name}.md`,\n },\n cursor: { mode: 'drop-in', path: name => `.cursor/rules/${name}.mdc` },\n opencode: { mode: 'drop-in', path: name => `.opencode/agents/${name}.md` },\n default: { mode: 'drop-in', path: name => `.ai/agents/${name}.md` },\n },\n // All four hosts have a hook system, but only Copilot gives each hook its\n // own file; the rest register hooks inside a shared settings document.\n // OpenCode has no declarative hook config at all — hooks are exclusively\n // JS/TS plugin modules — so it deliberately carries no entry here, the same\n // way it carries no `default`.\n hook: {\n 'claude-code': { mode: 'merge', path: () => `.claude/settings.json` },\n 'github-copilot': {\n mode: 'drop-in',\n path: name => `.github/hooks/${name}.json`,\n },\n 'google-gemini': { mode: 'merge', path: () => `.gemini/settings.json` },\n cursor: { mode: 'merge', path: () => `.cursor/hooks.json` },\n },\n 'mcp-config': {\n 'claude-code': { mode: 'merge', path: () => `.mcp.json` },\n 'github-copilot': { mode: 'merge', path: () => `.vscode/mcp.json` },\n 'google-gemini': { mode: 'merge', path: () => `.gemini/settings.json` },\n cursor: { mode: 'merge', path: () => `.cursor/mcp.json` },\n opencode: { mode: 'merge', path: () => `opencode.json` },\n },\n plugin: {},\n marketplace: {},\n};\n\n/**\n * Where a resource's body installs for one framework, and whether that path\n * is the body's own file or a settings file to merge into. Returns\n * `undefined` when the (type, framework) pair has no filesystem convention.\n */\nexport function getResourceInstallTarget(\n type: ResourceType,\n framework: string,\n name: string,\n): ResourceInstallTarget | undefined {\n const conventions = INSTALL_PATHS[type];\n const entry =\n conventions[normalizeFramework(framework)] ?? conventions.default;\n return entry ? { path: entry.path(name), mode: entry.mode } : undefined;\n}\n\n/**\n * The frameworks to show install paths for. An empty list means the resource\n * declared no compatibility and gets the neutral `default` convention; `all`\n * is a wildcard and expands to every installable host, so it never collapses\n * to a single row — or, for types with no `default`, to no rows at all.\n */\nexport function expandInstallFrameworks(frameworks: string[]): string[] {\n const normalized = dedupe(frameworks.map(normalizeFramework));\n if (normalized.length === 0) {\n return ['default'];\n }\n if (normalized.includes('all')) {\n return [...INSTALLABLE_FRAMEWORKS];\n }\n return normalized;\n}\n\n/*\n * Marketplace journey helpers (ADR-0010). A marketplace installs in two\n * steps — register the catalog with the AI tool, then install plugins from\n * it. Both Claude Code and Copilot CLI read the same\n * `.claude-plugin/marketplace.json` format and address the repo as\n * `owner/repo`; the plugin derives that slug from the mandatory\n * `source-location` (the producer contract requires the body doc to live\n * inside the marketplace repo). No derivation → the UI falls back to the\n * rendered body, which stays canonical.\n */\n\n/** Frameworks with a marketplace-add concept (Cursor/Gemini have none). */\nexport const MARKETPLACE_CAPABLE_FRAMEWORKS = [\n 'claude-code',\n 'github-copilot',\n] as const;\n\n/**\n * Narrow a resource's declared frameworks to those a given journey can\n * actually launch. A deep link is a claim about compatibility: offering\n * \"Install in Claude\" for a resource that never declared `claude-code` tells\n * the user something untrue about the resource.\n *\n * `all` and an empty list both mean \"unrestricted\" and expand to every\n * capable host. Anything else is intersected, so a declared framework with no\n * handler (Gemini) — or an unknown token — yields no links rather than\n * silently widening to every host.\n */\nexport function resolveCapableFrameworks(\n declared: string[],\n capable: readonly string[],\n): string[] {\n const normalized = dedupe(declared.map(normalizeFramework));\n if (normalized.length === 0 || normalized.includes('all')) {\n return [...capable];\n }\n return normalized.filter(f => capable.includes(f));\n}\n\n/**\n * Derive the `owner/repo` slug from a marketplace's `source-location`.\n * GitHub URLs only — other hosts return `undefined` (body-only fallback).\n */\nexport function getMarketplaceRepoSlug(\n sourceLocation: string | undefined,\n): string | undefined {\n if (!sourceLocation) {\n return undefined;\n }\n let url: URL;\n try {\n url = new URL(sourceLocation.replace(/^url:/, ''));\n } catch {\n return undefined;\n }\n if (url.hostname !== 'github.com') {\n return undefined;\n }\n const [owner, repo] = url.pathname.split('/').filter(Boolean);\n if (!owner || !repo) {\n return undefined;\n }\n return `${owner}/${repo.replace(/\\.git$/, '')}`;\n}\n\n/**\n * A one-click launcher for an add command: a custom-scheme URL that opens\n * the host app with the command pre-filled in the prompt box. The host\n * never auto-executes it — the user reviews and presses Enter — and an\n * unregistered scheme is a harmless no-op, so these complement the copy\n * button rather than replace it.\n */\nexport interface ResourceDeepLink {\n /** Host application name; render as \"Add in <label>\" / \"Install in <label>\". */\n label: string;\n href: string;\n}\n\nexport interface MarketplaceAddCommand {\n framework: string;\n command: string;\n deepLinks: ResourceDeepLink[];\n}\n\n/**\n * The copyable marketplace-add command per compatible framework. `all`,\n * unknown-only, or empty framework lists expand to every capable framework;\n * frameworks without a marketplace concept produce no row.\n *\n * Claude Code carries a single deep link on the CLI's documented\n * `claude-cli://open?q=` handler, which opens a Claude Code terminal\n * session with the command pre-filled regardless of the user's editor.\n *\n * Copilot's CLI still has no URI scheme, but Copilot's *other* surface does:\n * VS Code 1.113 ships `vscode://chat-plugin/add-marketplace?ref=`, which\n * accepts a plain or base64 `owner/repo` and always shows a confirmation\n * dialog before registering the marketplace (it also dedupes against the\n * user's existing entries). So the Copilot row keeps its terminal command for\n * CLI users *and* gains editor launchers, mirroring how `mcp-config` already\n * maps `github-copilot` onto the VS Code stable/Insiders pair.\n */\nexport function getMarketplaceAddCommands(\n frameworks: string[],\n repoSlug: string,\n): MarketplaceAddCommand[] {\n const capable = resolveCapableFrameworks(\n frameworks,\n MARKETPLACE_CAPABLE_FRAMEWORKS,\n );\n const commands: MarketplaceAddCommand[] = [];\n for (const framework of capable) {\n if (framework === 'claude-code') {\n const command = `/plugin marketplace add ${repoSlug}`;\n commands.push({\n framework,\n command,\n deepLinks: [\n {\n label: 'Claude',\n href: `claude-cli://open?q=${encodeURIComponent(command)}`,\n },\n ],\n });\n } else if (framework === 'github-copilot') {\n const ref = encodeURIComponent(repoSlug);\n commands.push({\n framework,\n command: `copilot plugin marketplace add ${repoSlug}`,\n deepLinks: [\n {\n label: 'VS Code',\n href: `vscode://chat-plugin/add-marketplace?ref=${ref}`,\n },\n {\n label: 'VS Code Insiders',\n href: `vscode-insiders://chat-plugin/add-marketplace?ref=${ref}`,\n },\n ],\n });\n }\n }\n return commands;\n}\n\n/** The raw-file URL for a GitHub blob `source-location`; else `undefined`. */\nfunction getRawGithubFileUrl(\n sourceLocation: string | undefined,\n): string | undefined {\n if (!sourceLocation) {\n return undefined;\n }\n let url: URL;\n try {\n url = new URL(sourceLocation.replace(/^url:/, ''));\n } catch {\n return undefined;\n }\n if (url.hostname !== 'github.com') {\n return undefined;\n }\n const [owner, repo, marker, ref, ...path] = url.pathname\n .split('/')\n .filter(Boolean);\n if (!owner || !repo || marker !== 'blob' || !ref || path.length === 0) {\n return undefined;\n }\n return `https://raw.githubusercontent.com/${owner}/${repo}/${ref}/${path.join(\n '/',\n )}`;\n}\n\n/**\n * A launcher that opens an agent with an install prompt pre-filled. Neither\n * host auto-executes: the user reads the prompt and presses Enter. Claude Code\n * and Cursor are the two tools exposing a generic prompt URI — Copilot's\n * editor has purpose-built routes but no prompt handler, and Gemini has\n * neither (its `gemini.google.com/app?prompt=` link drives the web app, which\n * cannot write to a local workspace).\n *\n * Cursor truncates a deeplink at the first raw `&`, so the prompt must be\n * percent-encoded — `encodeURIComponent` handles that. Cursor also caps the\n * URL at ~8000 characters; these prompts are a URL plus a path, far under it.\n */\nfunction claudePromptLink(prompt: string): ResourceDeepLink {\n return {\n label: 'Claude',\n href: `claude-cli://open?q=${encodeURIComponent(prompt)}`,\n };\n}\n\nfunction cursorPromptLink(prompt: string): ResourceDeepLink {\n return {\n label: 'Cursor',\n href: `cursor://anysphere.cursor-deeplink/prompt?text=${encodeURIComponent(\n prompt,\n )}`,\n };\n}\n\n/**\n * Frameworks with a one-click agent-install handler (Gemini has none).\n * OpenCode's Desktop app does register a real `opencode://` scheme, but every\n * route (`open-project`, `new-session`) requires an absolute local\n * `directory` param a web page cannot supply, so it's excluded here too.\n */\nexport const AGENT_LINK_CAPABLE_FRAMEWORKS = [\n 'claude-code',\n 'github-copilot',\n 'cursor',\n] as const;\n\n/** Hosts reachable by a generic prompt URI — the only route for skill/hook. */\nexport const PROMPT_LINK_CAPABLE_FRAMEWORKS = [\n 'claude-code',\n 'cursor',\n] as const;\n\n/**\n * The plain http(s) URL behind a `source-location`. Unlike\n * `getRawGithubFileUrl` this accepts **directory (`tree`) URLs**, which is what\n * a multi-file `skill` actually carries — a raw-file URL would resolve to\n * `undefined` for exactly the type that most needs a launcher.\n */\nfunction getSourceUrl(sourceLocation: string | undefined): string | undefined {\n if (!sourceLocation) {\n return undefined;\n }\n try {\n const url = new URL(sourceLocation.replace(/^url:/, ''));\n return url.protocol === 'http:' || url.protocol === 'https:'\n ? url.toString()\n : undefined;\n } catch {\n return undefined;\n }\n}\n\n/**\n * One-click launchers for the types with no purpose-built install route —\n * `skill` and `hook`. Both ride the generic prompt handlers, so the prompt\n * names the *host's own* install path and respects its install mode: a `merge`\n * target is asked to be merged into, never overwritten, so a launcher can\n * never cost the user their existing settings.\n *\n * Returns `[]` when the source URL cannot be derived, or for hosts with no\n * prompt route (Copilot, Gemini) — the path list and copy/download remain.\n */\nexport function getPromptInstallLinks(\n type: ResourceType,\n sourceLocation: string | undefined,\n name: string,\n frameworks: string[] = [],\n): ResourceDeepLink[] {\n return getInstallSteps(type, sourceLocation, name, frameworks)\n .map(step => step.link)\n .filter((link): link is ResourceDeepLink => !!link);\n}\n\n/**\n * Everything a single host needs to install a resource: where the body goes,\n * the prompt that puts it there, and a launcher when the host has a URI to\n * launch.\n *\n * `link` is absent for hosts with no prompt route — Copilot and Gemini. That\n * absence is why the step carries `prompt` as text: a host without a URI is\n * not a host without an install path, and the same instruction that a launcher\n * would pre-fill can be pasted into that agent by hand. Every declared\n * framework therefore gets something actionable, not a bare path.\n */\nexport interface ResourceInstallStep {\n framework: string;\n target: ResourceInstallTarget;\n /** The install instruction, ready to run in that host's agent. */\n prompt: string;\n /** One-click launcher, where the host exposes a generic prompt URI. */\n link?: ResourceDeepLink;\n}\n\nexport function getInstallSteps(\n type: ResourceType,\n sourceLocation: string | undefined,\n name: string,\n frameworks: string[] = [],\n): ResourceInstallStep[] {\n const sourceUrl = getSourceUrl(sourceLocation);\n if (!sourceUrl) {\n return [];\n }\n const label = RESOURCE_TYPE_REGISTRY[type].label.toLowerCase();\n const steps: ResourceInstallStep[] = [];\n for (const framework of expandInstallFrameworks(frameworks)) {\n const target = getResourceInstallTarget(type, framework, name);\n if (!target) {\n continue;\n }\n const prompt =\n target.mode === 'merge'\n ? `Install the \"${name}\" ${label}: fetch ${sourceUrl} and merge it into ${target.path}, keeping my existing settings intact.`\n : `Install the \"${name}\" ${label}: fetch ${sourceUrl} and save it to ${target.path}.`;\n let link: ResourceDeepLink | undefined;\n if (framework === 'claude-code') {\n link = claudePromptLink(prompt);\n } else if (framework === 'cursor') {\n link = cursorPromptLink(prompt);\n }\n steps.push({ framework, target, prompt, link });\n }\n return steps;\n}\n\n/** Frameworks with a one-click MCP-install handler (Gemini has none). */\nexport const MCP_LINK_CAPABLE_FRAMEWORKS = [\n 'claude-code',\n 'github-copilot',\n 'cursor',\n] as const;\n\n/**\n * One-click install links for an `agent` resource, restricted to the\n * resource's own compatible frameworks. VS Code stable and Insiders use the\n * native `chat-agent/install?url=` handler (the mechanism behind\n * awesome-copilot's Install buttons) and are Copilot's hosts, so they follow\n * `github-copilot`: VS Code downloads the file at `url` and asks the user\n * where to save it. Claude Code uses the `claude-cli://open?q=` handler with\n * an install prompt pre-filled to the convention path — reviewed and sent by\n * the user, never auto-executed. All derive from the GitHub blob URL in\n * `source-location`; anything else returns `[]` (copy/download fallback).\n */\nexport function getAgentInstallLinks(\n sourceLocation: string | undefined,\n name: string,\n frameworks: string[] = [],\n): ResourceDeepLink[] {\n const rawUrl = getRawGithubFileUrl(sourceLocation);\n if (!rawUrl) {\n return [];\n }\n const capable = resolveCapableFrameworks(\n frameworks,\n AGENT_LINK_CAPABLE_FRAMEWORKS,\n );\n const encodedRawUrl = encodeURIComponent(rawUrl);\n const installPrompt = (framework: string) => {\n const target = getResourceInstallTarget('agent', framework, name);\n return `Install this agent: fetch ${rawUrl} and save it to ${target?.path}`;\n };\n const links: ResourceDeepLink[] = [];\n for (const framework of capable) {\n if (framework === 'claude-code') {\n links.push(claudePromptLink(installPrompt('claude-code')));\n } else if (framework === 'cursor') {\n links.push(cursorPromptLink(installPrompt('cursor')));\n } else if (framework === 'github-copilot') {\n links.push({\n label: 'VS Code',\n href: `vscode:chat-agent/install?url=${encodedRawUrl}`,\n });\n links.push({\n label: 'VS Code Insiders',\n href: `vscode-insiders:chat-agent/install?url=${encodedRawUrl}`,\n });\n }\n }\n return links;\n}\n\n/**\n * One-click install links for an `mcp-config` resource, derived from its body —\n * the canonical `.mcp.json` snippet (spec §3.4). VS Code carries a native\n * `vscode:mcp/install?{json}` handler (Insiders scheme twin), Cursor a\n * documented `cursor://anysphere.cursor-deeplink/mcp/install` one, and\n * Claude Code gets a `claude-cli://open?q=` prompt around\n * `claude mcp add-json` — pre-filled, reviewed, never auto-executed.\n * Hosts follow the resource's own compatible frameworks: `all`/empty expand\n * to every capable host, anything else is intersected, so an mcp-config\n * declaring only Gemini (no handler) gets no links rather than every host's.\n * A missing, unparseable, or multi-server body returns `[]` (copy fallback) —\n * the body stays canonical, links are conveniences derived from it.\n */\nexport function getMcpInstallLinks(\n frameworks: string[],\n resourceName: string,\n bodyContent: string | undefined,\n): ResourceDeepLink[] {\n if (!bodyContent) {\n return [];\n }\n let parsed: unknown;\n try {\n parsed = JSON.parse(bodyContent);\n } catch {\n return [];\n }\n if (typeof parsed !== 'object' || parsed === null) {\n return [];\n }\n\n let serverName = resourceName;\n let config: Record<string, unknown>;\n const servers = (parsed as { mcpServers?: unknown }).mcpServers;\n if (servers !== undefined) {\n if (typeof servers !== 'object' || servers === null) {\n return [];\n }\n const entries = Object.entries(servers as Record<string, unknown>);\n if (\n entries.length !== 1 ||\n typeof entries[0][1] !== 'object' ||\n entries[0][1] === null\n ) {\n return [];\n }\n serverName = entries[0][0];\n config = entries[0][1] as Record<string, unknown>;\n } else if ('command' in parsed || 'url' in parsed) {\n config = parsed as Record<string, unknown>;\n } else {\n return [];\n }\n\n const capable = resolveCapableFrameworks(\n frameworks,\n MCP_LINK_CAPABLE_FRAMEWORKS,\n );\n\n const configJson = JSON.stringify(config);\n const links: ResourceDeepLink[] = [];\n for (const framework of capable) {\n if (framework === 'claude-code') {\n const prompt = encodeURIComponent(\n `Install this MCP config by running: claude mcp add-json ${serverName} '${configJson}'`,\n );\n links.push({ label: 'Claude', href: `claude-cli://open?q=${prompt}` });\n } else if (framework === 'github-copilot') {\n const vsConfig = encodeURIComponent(\n JSON.stringify({ name: serverName, ...config }),\n );\n links.push({ label: 'VS Code', href: `vscode:mcp/install?${vsConfig}` });\n links.push({\n label: 'VS Code Insiders',\n href: `vscode-insiders:mcp/install?${vsConfig}`,\n });\n } else if (framework === 'cursor') {\n try {\n links.push({\n label: 'Cursor',\n href: `cursor://anysphere.cursor-deeplink/mcp/install?name=${encodeURIComponent(\n serverName,\n )}&config=${btoa(configJson)}`,\n });\n } catch {\n // btoa rejects non-Latin1 config content — skip the Cursor link.\n }\n }\n }\n return links;\n}\n\n/**\n * Step two of the journey: how to install a plugin from the marketplace.\n * `marketplaceName` is the entity name, which the producer contract requires\n * to equal the `name` in `marketplace.json`.\n */\nexport function getMarketplaceInstallTemplate(marketplaceName: string): string {\n return `/plugin install <plugin>@${marketplaceName}`;\n}\n\n/**\n * The `.claude/settings.json` snippet a team commits so collaborators are\n * prompted to install the marketplace automatically (Claude Code).\n */\nexport function getMarketplaceTeamSnippet(\n marketplaceName: string,\n repoSlug: string,\n): string {\n return JSON.stringify(\n {\n extraKnownMarketplaces: {\n [marketplaceName]: { source: { source: 'github', repo: repoSlug } },\n },\n },\n null,\n 2,\n );\n}\n"],"names":[],"mappings":"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"}
@@ -23,12 +23,36 @@ function isParseableEntityRef(ref) {
23
23
  return false;
24
24
  }
25
25
  }
26
+ function normalizeChildRef(ref, containerNamespace) {
27
+ if (typeof ref !== "string") return void 0;
28
+ try {
29
+ return catalogModel.stringifyEntityRef(
30
+ catalogModel.parseEntityRef(ref, {
31
+ defaultKind: "AiResource",
32
+ defaultNamespace: containerNamespace
33
+ })
34
+ );
35
+ } catch {
36
+ return void 0;
37
+ }
38
+ }
39
+ function declaredChildren(entity) {
40
+ const type = entity.spec?.type;
41
+ let raw;
42
+ if (type === "marketplace") raw = entity.spec?.plugins;
43
+ else if (type === "plugin") raw = entity.spec?.skills;
44
+ if (!Array.isArray(raw)) return [];
45
+ const containerNamespace = entity.metadata.namespace ?? "default";
46
+ const refs = raw.map((ref) => normalizeChildRef(ref, containerNamespace)).filter((r) => r !== void 0);
47
+ return Array.from(new Set(refs));
48
+ }
26
49
  function toResourceSummary(entity) {
27
50
  const type = entity.spec?.type;
28
51
  if (!resources.isResourceType(type)) {
29
52
  return void 0;
30
53
  }
31
54
  const annotations = entity.metadata.annotations ?? {};
55
+ const children = declaredChildren(entity);
32
56
  return {
33
57
  entityRef: catalogModel.stringifyEntityRef(entity),
34
58
  name: entity.metadata.name,
@@ -40,13 +64,41 @@ function toResourceSummary(entity) {
40
64
  owner: typeof entity.spec?.owner === "string" ? normalizeOwner(entity.spec.owner) : void 0,
41
65
  sourceLocation: annotations[catalogModel.ANNOTATION_SOURCE_LOCATION],
42
66
  frameworks: resources.getFrameworks(entity),
43
- version: annotations[resources.ANNOTATION_VERSION],
67
+ version: typeof entity.spec?.version === "string" ? entity.spec.version : void 0,
44
68
  kind: entity.kind,
69
+ // `parents` needs the whole read to invert; a single entity can't know
70
+ // them, so `toResourceSummaries` fills them in. `children` here is still
71
+ // the raw declared set — visibility filtering also needs the whole read.
72
+ children,
73
+ parents: [],
74
+ childCount: children.length,
45
75
  helpText: annotations[resources.ANNOTATION_HELP],
46
76
  annotations
47
77
  };
48
78
  }
79
+ function toResourceSummaries(entities) {
80
+ const summaries = entities.map(toResourceSummary).filter((s) => s !== void 0);
81
+ const byRef = new Map(summaries.map((s) => [s.entityRef, s]));
82
+ const parentsByChild = /* @__PURE__ */ new Map();
83
+ for (const summary of summaries) {
84
+ summary.children = summary.children.filter((ref) => {
85
+ const child = byRef.get(ref);
86
+ return child !== void 0 && resources.isRenderableContainment(summary.type, child.type);
87
+ });
88
+ summary.childCount = summary.children.length;
89
+ for (const childRef of summary.children) {
90
+ const bucket = parentsByChild.get(childRef);
91
+ if (bucket) bucket.push(summary.entityRef);
92
+ else parentsByChild.set(childRef, [summary.entityRef]);
93
+ }
94
+ }
95
+ for (const summary of summaries) {
96
+ summary.parents = parentsByChild.get(summary.entityRef) ?? [];
97
+ }
98
+ return summaries;
99
+ }
49
100
 
50
101
  exports.isParseableEntityRef = isParseableEntityRef;
102
+ exports.toResourceSummaries = toResourceSummaries;
51
103
  exports.toResourceSummary = toResourceSummary;
52
104
  //# sourceMappingURL=toResourceSummary.cjs.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"toResourceSummary.cjs.js","sources":["../src/toResourceSummary.ts"],"sourcesContent":["import {\n ANNOTATION_SOURCE_LOCATION,\n parseEntityRef,\n stringifyEntityRef,\n type Entity,\n} from '@backstage/catalog-model';\nimport {\n ANNOTATION_HELP,\n ANNOTATION_VERSION,\n getFrameworks,\n isResourceType,\n type ResourceSummary,\n} from './resources';\n\n/**\n * Map a catalog AiResource entity to the flat contract served to the\n * frontend. Returns undefined for entities with an unsupported `spec.type`,\n * which are silently dropped (ADR-0003).\n *\n * Lives in `common` rather than the backend because it is pure and\n * isomorphic — `Entity` in, `ResourceSummary` out — and both the backend\n * router and the frontend's e2e fixtures need it to agree. The e2e suite\n * builds its mock `GET /resources` payload by running the real examples in\n * `examples/catalog/` through this function, so a change to the mapping\n * cannot silently diverge from what the tests assert against.\n */\n/**\n * Normalise `spec.owner` to a full entity ref. Producers write the short form\n * (`group:platforms-developer-experience`, or a bare group name), while\n * anything that compares or links refs needs the canonical\n * `group:default/platforms-developer-experience`.\n *\n * An unparseable ref passes through untouched: the owner is a display field,\n * never worth failing the whole resource over.\n */\nfunction normalizeOwner(owner: string): string {\n try {\n return stringifyEntityRef(\n parseEntityRef(owner, {\n defaultKind: 'group',\n defaultNamespace: 'default',\n }),\n );\n } catch {\n return owner;\n }\n}\n\n/**\n * Whether a string is a parseable entity ref. The frontend links `owner` to\n * its catalog page (via `EntityRefLink`, which throws on an unparseable\n * ref) — this lets it fall back to plain text instead, matching\n * `normalizeOwner`'s own stance that a malformed owner is never worth\n * failing over.\n */\nexport function isParseableEntityRef(ref: string): boolean {\n try {\n parseEntityRef(ref, { defaultKind: 'group', defaultNamespace: 'default' });\n return true;\n } catch {\n return false;\n }\n}\n\nexport function toResourceSummary(entity: Entity): ResourceSummary | undefined {\n const type = entity.spec?.type;\n if (!isResourceType(type)) {\n return undefined;\n }\n\n const annotations = entity.metadata.annotations ?? {};\n\n return {\n entityRef: stringifyEntityRef(entity),\n name: entity.metadata.name,\n title: entity.metadata.title,\n description: entity.metadata.description,\n tags: entity.metadata.tags ?? [],\n type,\n lifecycle:\n typeof entity.spec?.lifecycle === 'string' ? entity.spec.lifecycle : '',\n owner:\n typeof entity.spec?.owner === 'string'\n ? normalizeOwner(entity.spec.owner)\n : undefined,\n sourceLocation: annotations[ANNOTATION_SOURCE_LOCATION],\n frameworks: getFrameworks(entity),\n version: annotations[ANNOTATION_VERSION],\n kind: entity.kind,\n helpText: annotations[ANNOTATION_HELP],\n annotations,\n };\n}\n"],"names":["stringifyEntityRef","parseEntityRef","isResourceType","ANNOTATION_SOURCE_LOCATION","getFrameworks","ANNOTATION_VERSION","ANNOTATION_HELP"],"mappings":";;;;;AAmCA,SAAS,eAAe,KAAA,EAAuB;AAC7C,EAAA,IAAI;AACF,IAAA,OAAOA,+BAAA;AAAA,MACLC,4BAAe,KAAA,EAAO;AAAA,QACpB,WAAA,EAAa,OAAA;AAAA,QACb,gBAAA,EAAkB;AAAA,OACnB;AAAA,KACH;AAAA,EACF,CAAA,CAAA,MAAQ;AACN,IAAA,OAAO,KAAA;AAAA,EACT;AACF;AASO,SAAS,qBAAqB,GAAA,EAAsB;AACzD,EAAA,IAAI;AACF,IAAAA,2BAAA,CAAe,KAAK,EAAE,WAAA,EAAa,OAAA,EAAS,gBAAA,EAAkB,WAAW,CAAA;AACzE,IAAA,OAAO,IAAA;AAAA,EACT,CAAA,CAAA,MAAQ;AACN,IAAA,OAAO,KAAA;AAAA,EACT;AACF;AAEO,SAAS,kBAAkB,MAAA,EAA6C;AAC7E,EAAA,MAAM,IAAA,GAAO,OAAO,IAAA,EAAM,IAAA;AAC1B,EAAA,IAAI,CAACC,wBAAA,CAAe,IAAI,CAAA,EAAG;AACzB,IAAA,OAAO,MAAA;AAAA,EACT;AAEA,EAAA,MAAM,WAAA,GAAc,MAAA,CAAO,QAAA,CAAS,WAAA,IAAe,EAAC;AAEpD,EAAA,OAAO;AAAA,IACL,SAAA,EAAWF,gCAAmB,MAAM,CAAA;AAAA,IACpC,IAAA,EAAM,OAAO,QAAA,CAAS,IAAA;AAAA,IACtB,KAAA,EAAO,OAAO,QAAA,CAAS,KAAA;AAAA,IACvB,WAAA,EAAa,OAAO,QAAA,CAAS,WAAA;AAAA,IAC7B,IAAA,EAAM,MAAA,CAAO,QAAA,CAAS,IAAA,IAAQ,EAAC;AAAA,IAC/B,IAAA;AAAA,IACA,SAAA,EACE,OAAO,MAAA,CAAO,IAAA,EAAM,cAAc,QAAA,GAAW,MAAA,CAAO,KAAK,SAAA,GAAY,EAAA;AAAA,IACvE,KAAA,EACE,OAAO,MAAA,CAAO,IAAA,EAAM,KAAA,KAAU,WAC1B,cAAA,CAAe,MAAA,CAAO,IAAA,CAAK,KAAK,CAAA,GAChC,MAAA;AAAA,IACN,cAAA,EAAgB,YAAYG,uCAA0B,CAAA;AAAA,IACtD,UAAA,EAAYC,wBAAc,MAAM,CAAA;AAAA,IAChC,OAAA,EAAS,YAAYC,4BAAkB,CAAA;AAAA,IACvC,MAAM,MAAA,CAAO,IAAA;AAAA,IACb,QAAA,EAAU,YAAYC,yBAAe,CAAA;AAAA,IACrC;AAAA,GACF;AACF;;;;;"}
1
+ {"version":3,"file":"toResourceSummary.cjs.js","sources":["../src/toResourceSummary.ts"],"sourcesContent":["import {\n ANNOTATION_SOURCE_LOCATION,\n parseEntityRef,\n stringifyEntityRef,\n type Entity,\n} from '@backstage/catalog-model';\nimport {\n ANNOTATION_HELP,\n getFrameworks,\n isRenderableContainment,\n isResourceType,\n type ResourceSummary,\n} from './resources';\n\n/**\n * Map a catalog AiResource entity to the flat contract served to the\n * frontend. Returns undefined for entities with an unsupported `spec.type`,\n * which are silently dropped (ADR-0003).\n *\n * Lives in `common` rather than the backend because it is pure and\n * isomorphic — `Entity` in, `ResourceSummary` out — and both the backend\n * router and the frontend's e2e fixtures need it to agree. The e2e suite\n * builds its mock `GET /resources` payload by running the real examples in\n * `examples/catalog/` through this function, so a change to the mapping\n * cannot silently diverge from what the tests assert against.\n */\n/**\n * Normalise `spec.owner` to a full entity ref. Producers write the short form\n * (`group:platforms-developer-experience`, or a bare group name), while\n * anything that compares or links refs needs the canonical\n * `group:default/platforms-developer-experience`.\n *\n * An unparseable ref passes through untouched: the owner is a display field,\n * never worth failing the whole resource over.\n */\nfunction normalizeOwner(owner: string): string {\n try {\n return stringifyEntityRef(\n parseEntityRef(owner, {\n defaultKind: 'group',\n defaultNamespace: 'default',\n }),\n );\n } catch {\n return owner;\n }\n}\n\n/**\n * Whether a string is a parseable entity ref. The frontend links `owner` to\n * its catalog page (via `EntityRefLink`, which throws on an unparseable\n * ref) — this lets it fall back to plain text instead, matching\n * `normalizeOwner`'s own stance that a malformed owner is never worth\n * failing over.\n */\nexport function isParseableEntityRef(ref: string): boolean {\n try {\n parseEntityRef(ref, { defaultKind: 'group', defaultNamespace: 'default' });\n return true;\n } catch {\n return false;\n }\n}\n\n/**\n * Normalise a declared child ref to the canonical, lowercased form\n * `stringifyEntityRef` emits, so it compares equal to a container entity's own\n * ref regardless of how the producer wrote it (bare name or full ref). A bare\n * name resolves in the *container's own* namespace — mirroring Backstage's\n * native relation fields, which declare `defaultNamespace: 'inherit'` — not\n * always `default`; a plugin in `team-a` listing a bare `child` must resolve\n * to `airesource:team-a/child`, or it silently loses the relationship. An\n * unparseable ref is dropped: a member the catalog could never resolve is not\n * a relationship worth carrying.\n */\nfunction normalizeChildRef(\n ref: unknown,\n containerNamespace: string,\n): string | undefined {\n if (typeof ref !== 'string') return undefined;\n try {\n return stringifyEntityRef(\n parseEntityRef(ref, {\n defaultKind: 'AiResource',\n defaultNamespace: containerNamespace,\n }),\n );\n } catch {\n return undefined;\n }\n}\n\n/**\n * The refs a container declares as its contents: a `marketplace`'s\n * `spec.plugins`, a `plugin`'s `spec.skills` (ADR-0015). Leaf types declare\n * nothing. Unparseable and duplicate refs are dropped; visibility filtering\n * happens in `toResourceSummaries`, which alone sees the whole read.\n */\nfunction declaredChildren(entity: Entity): string[] {\n const type = entity.spec?.type;\n let raw: unknown;\n if (type === 'marketplace') raw = entity.spec?.plugins;\n else if (type === 'plugin') raw = entity.spec?.skills;\n if (!Array.isArray(raw)) return [];\n const containerNamespace = entity.metadata.namespace ?? 'default';\n const refs = raw\n .map(ref => normalizeChildRef(ref, containerNamespace))\n .filter((r): r is string => r !== undefined);\n return Array.from(new Set(refs));\n}\n\nexport function toResourceSummary(entity: Entity): ResourceSummary | undefined {\n const type = entity.spec?.type;\n if (!isResourceType(type)) {\n return undefined;\n }\n\n const annotations = entity.metadata.annotations ?? {};\n const children = declaredChildren(entity);\n\n return {\n entityRef: stringifyEntityRef(entity),\n name: entity.metadata.name,\n title: entity.metadata.title,\n description: entity.metadata.description,\n tags: entity.metadata.tags ?? [],\n type,\n lifecycle:\n typeof entity.spec?.lifecycle === 'string' ? entity.spec.lifecycle : '',\n owner:\n typeof entity.spec?.owner === 'string'\n ? normalizeOwner(entity.spec.owner)\n : undefined,\n sourceLocation: annotations[ANNOTATION_SOURCE_LOCATION],\n frameworks: getFrameworks(entity),\n version:\n typeof entity.spec?.version === 'string'\n ? entity.spec.version\n : undefined,\n kind: entity.kind,\n // `parents` needs the whole read to invert; a single entity can't know\n // them, so `toResourceSummaries` fills them in. `children` here is still\n // the raw declared set — visibility filtering also needs the whole read.\n children,\n parents: [],\n childCount: children.length,\n helpText: annotations[ANNOTATION_HELP],\n annotations,\n };\n}\n\n/**\n * Map a whole caller-visible catalog read to summaries, resolving containment\n * in both directions (ADR-0015). This is the function the served payload uses;\n * `toResourceSummary` alone cannot, because containment is only knowable across\n * the whole set.\n *\n * Upstream imposes no type restriction on membership — `spec.skills` /\n * `spec.plugins` carry `allowedKinds: [\"AiResource\"]`, so any AiResource type\n * is a legal member. DevAI Hub narrows that to its rendering convention\n * (ADR-0010/0015): a `marketplace` renders `plugin` children only, a `plugin`\n * renders `skill`/`agent`/`hook`/`mcp-config`. That convention is applied here,\n * once, so `children`, `childCount`, `parents`, the card chip and the detail\n * sections cannot disagree:\n *\n * - `children` keeps only refs that resolve within this read **and** whose type\n * the container legitimately contains — so a card never counts, nor a detail\n * panel links, a hidden member (ADR-0006) or an off-convention one.\n * - `parents` is the in-memory inverse of those filtered children.\n */\nexport function toResourceSummaries(entities: Entity[]): ResourceSummary[] {\n const summaries = entities\n .map(toResourceSummary)\n .filter((s): s is ResourceSummary => s !== undefined);\n\n const byRef = new Map(summaries.map(s => [s.entityRef, s]));\n const parentsByChild = new Map<string, string[]>();\n\n for (const summary of summaries) {\n summary.children = summary.children.filter(ref => {\n const child = byRef.get(ref);\n return (\n child !== undefined && isRenderableContainment(summary.type, child.type)\n );\n });\n summary.childCount = summary.children.length;\n for (const childRef of summary.children) {\n const bucket = parentsByChild.get(childRef);\n if (bucket) bucket.push(summary.entityRef);\n else parentsByChild.set(childRef, [summary.entityRef]);\n }\n }\n\n for (const summary of summaries) {\n summary.parents = parentsByChild.get(summary.entityRef) ?? [];\n }\n\n return summaries;\n}\n"],"names":["stringifyEntityRef","parseEntityRef","isResourceType","ANNOTATION_SOURCE_LOCATION","getFrameworks","ANNOTATION_HELP","isRenderableContainment"],"mappings":";;;;;AAmCA,SAAS,eAAe,KAAA,EAAuB;AAC7C,EAAA,IAAI;AACF,IAAA,OAAOA,+BAAA;AAAA,MACLC,4BAAe,KAAA,EAAO;AAAA,QACpB,WAAA,EAAa,OAAA;AAAA,QACb,gBAAA,EAAkB;AAAA,OACnB;AAAA,KACH;AAAA,EACF,CAAA,CAAA,MAAQ;AACN,IAAA,OAAO,KAAA;AAAA,EACT;AACF;AASO,SAAS,qBAAqB,GAAA,EAAsB;AACzD,EAAA,IAAI;AACF,IAAAA,2BAAA,CAAe,KAAK,EAAE,WAAA,EAAa,OAAA,EAAS,gBAAA,EAAkB,WAAW,CAAA;AACzE,IAAA,OAAO,IAAA;AAAA,EACT,CAAA,CAAA,MAAQ;AACN,IAAA,OAAO,KAAA;AAAA,EACT;AACF;AAaA,SAAS,iBAAA,CACP,KACA,kBAAA,EACoB;AACpB,EAAA,IAAI,OAAO,GAAA,KAAQ,QAAA,EAAU,OAAO,MAAA;AACpC,EAAA,IAAI;AACF,IAAA,OAAOD,+BAAA;AAAA,MACLC,4BAAe,GAAA,EAAK;AAAA,QAClB,WAAA,EAAa,YAAA;AAAA,QACb,gBAAA,EAAkB;AAAA,OACnB;AAAA,KACH;AAAA,EACF,CAAA,CAAA,MAAQ;AACN,IAAA,OAAO,MAAA;AAAA,EACT;AACF;AAQA,SAAS,iBAAiB,MAAA,EAA0B;AAClD,EAAA,MAAM,IAAA,GAAO,OAAO,IAAA,EAAM,IAAA;AAC1B,EAAA,IAAI,GAAA;AACJ,EAAA,IAAI,IAAA,KAAS,aAAA,EAAe,GAAA,GAAM,MAAA,CAAO,IAAA,EAAM,OAAA;AAAA,OAAA,IACtC,IAAA,KAAS,QAAA,EAAU,GAAA,GAAM,MAAA,CAAO,IAAA,EAAM,MAAA;AAC/C,EAAA,IAAI,CAAC,KAAA,CAAM,OAAA,CAAQ,GAAG,CAAA,SAAU,EAAC;AACjC,EAAA,MAAM,kBAAA,GAAqB,MAAA,CAAO,QAAA,CAAS,SAAA,IAAa,SAAA;AACxD,EAAA,MAAM,IAAA,GAAO,GAAA,CACV,GAAA,CAAI,CAAA,GAAA,KAAO,iBAAA,CAAkB,GAAA,EAAK,kBAAkB,CAAC,CAAA,CACrD,MAAA,CAAO,CAAC,CAAA,KAAmB,MAAM,MAAS,CAAA;AAC7C,EAAA,OAAO,KAAA,CAAM,IAAA,CAAK,IAAI,GAAA,CAAI,IAAI,CAAC,CAAA;AACjC;AAEO,SAAS,kBAAkB,MAAA,EAA6C;AAC7E,EAAA,MAAM,IAAA,GAAO,OAAO,IAAA,EAAM,IAAA;AAC1B,EAAA,IAAI,CAACC,wBAAA,CAAe,IAAI,CAAA,EAAG;AACzB,IAAA,OAAO,MAAA;AAAA,EACT;AAEA,EAAA,MAAM,WAAA,GAAc,MAAA,CAAO,QAAA,CAAS,WAAA,IAAe,EAAC;AACpD,EAAA,MAAM,QAAA,GAAW,iBAAiB,MAAM,CAAA;AAExC,EAAA,OAAO;AAAA,IACL,SAAA,EAAWF,gCAAmB,MAAM,CAAA;AAAA,IACpC,IAAA,EAAM,OAAO,QAAA,CAAS,IAAA;AAAA,IACtB,KAAA,EAAO,OAAO,QAAA,CAAS,KAAA;AAAA,IACvB,WAAA,EAAa,OAAO,QAAA,CAAS,WAAA;AAAA,IAC7B,IAAA,EAAM,MAAA,CAAO,QAAA,CAAS,IAAA,IAAQ,EAAC;AAAA,IAC/B,IAAA;AAAA,IACA,SAAA,EACE,OAAO,MAAA,CAAO,IAAA,EAAM,cAAc,QAAA,GAAW,MAAA,CAAO,KAAK,SAAA,GAAY,EAAA;AAAA,IACvE,KAAA,EACE,OAAO,MAAA,CAAO,IAAA,EAAM,KAAA,KAAU,WAC1B,cAAA,CAAe,MAAA,CAAO,IAAA,CAAK,KAAK,CAAA,GAChC,MAAA;AAAA,IACN,cAAA,EAAgB,YAAYG,uCAA0B,CAAA;AAAA,IACtD,UAAA,EAAYC,wBAAc,MAAM,CAAA;AAAA,IAChC,OAAA,EACE,OAAO,MAAA,CAAO,IAAA,EAAM,YAAY,QAAA,GAC5B,MAAA,CAAO,KAAK,OAAA,GACZ,MAAA;AAAA,IACN,MAAM,MAAA,CAAO,IAAA;AAAA;AAAA;AAAA;AAAA,IAIb,QAAA;AAAA,IACA,SAAS,EAAC;AAAA,IACV,YAAY,QAAA,CAAS,MAAA;AAAA,IACrB,QAAA,EAAU,YAAYC,yBAAe,CAAA;AAAA,IACrC;AAAA,GACF;AACF;AAqBO,SAAS,oBAAoB,QAAA,EAAuC;AACzE,EAAA,MAAM,SAAA,GAAY,SACf,GAAA,CAAI,iBAAiB,EACrB,MAAA,CAAO,CAAC,CAAA,KAA4B,CAAA,KAAM,MAAS,CAAA;AAEtD,EAAA,MAAM,KAAA,GAAQ,IAAI,GAAA,CAAI,SAAA,CAAU,GAAA,CAAI,CAAA,CAAA,KAAK,CAAC,CAAA,CAAE,SAAA,EAAW,CAAC,CAAC,CAAC,CAAA;AAC1D,EAAA,MAAM,cAAA,uBAAqB,GAAA,EAAsB;AAEjD,EAAA,KAAA,MAAW,WAAW,SAAA,EAAW;AAC/B,IAAA,OAAA,CAAQ,QAAA,GAAW,OAAA,CAAQ,QAAA,CAAS,MAAA,CAAO,CAAA,GAAA,KAAO;AAChD,MAAA,MAAM,KAAA,GAAQ,KAAA,CAAM,GAAA,CAAI,GAAG,CAAA;AAC3B,MAAA,OACE,UAAU,MAAA,IAAaC,iCAAA,CAAwB,OAAA,CAAQ,IAAA,EAAM,MAAM,IAAI,CAAA;AAAA,IAE3E,CAAC,CAAA;AACD,IAAA,OAAA,CAAQ,UAAA,GAAa,QAAQ,QAAA,CAAS,MAAA;AACtC,IAAA,KAAA,MAAW,QAAA,IAAY,QAAQ,QAAA,EAAU;AACvC,MAAA,MAAM,MAAA,GAAS,cAAA,CAAe,GAAA,CAAI,QAAQ,CAAA;AAC1C,MAAA,IAAI,MAAA,EAAQ,MAAA,CAAO,IAAA,CAAK,OAAA,CAAQ,SAAS,CAAA;AAAA,0BACrB,GAAA,CAAI,QAAA,EAAU,CAAC,OAAA,CAAQ,SAAS,CAAC,CAAA;AAAA,IACvD;AAAA,EACF;AAEA,EAAA,KAAA,MAAW,WAAW,SAAA,EAAW;AAC/B,IAAA,OAAA,CAAQ,UAAU,cAAA,CAAe,GAAA,CAAI,OAAA,CAAQ,SAAS,KAAK,EAAC;AAAA,EAC9D;AAEA,EAAA,OAAO,SAAA;AACT;;;;;;"}
@@ -1,5 +1,5 @@
1
1
  import { parseEntityRef, ANNOTATION_SOURCE_LOCATION, stringifyEntityRef } from '@backstage/catalog-model';
2
- import { isResourceType, ANNOTATION_HELP, ANNOTATION_VERSION, getFrameworks } from './resources.esm.js';
2
+ import { isRenderableContainment, isResourceType, ANNOTATION_HELP, getFrameworks } from './resources.esm.js';
3
3
 
4
4
  function normalizeOwner(owner) {
5
5
  try {
@@ -21,12 +21,36 @@ function isParseableEntityRef(ref) {
21
21
  return false;
22
22
  }
23
23
  }
24
+ function normalizeChildRef(ref, containerNamespace) {
25
+ if (typeof ref !== "string") return void 0;
26
+ try {
27
+ return stringifyEntityRef(
28
+ parseEntityRef(ref, {
29
+ defaultKind: "AiResource",
30
+ defaultNamespace: containerNamespace
31
+ })
32
+ );
33
+ } catch {
34
+ return void 0;
35
+ }
36
+ }
37
+ function declaredChildren(entity) {
38
+ const type = entity.spec?.type;
39
+ let raw;
40
+ if (type === "marketplace") raw = entity.spec?.plugins;
41
+ else if (type === "plugin") raw = entity.spec?.skills;
42
+ if (!Array.isArray(raw)) return [];
43
+ const containerNamespace = entity.metadata.namespace ?? "default";
44
+ const refs = raw.map((ref) => normalizeChildRef(ref, containerNamespace)).filter((r) => r !== void 0);
45
+ return Array.from(new Set(refs));
46
+ }
24
47
  function toResourceSummary(entity) {
25
48
  const type = entity.spec?.type;
26
49
  if (!isResourceType(type)) {
27
50
  return void 0;
28
51
  }
29
52
  const annotations = entity.metadata.annotations ?? {};
53
+ const children = declaredChildren(entity);
30
54
  return {
31
55
  entityRef: stringifyEntityRef(entity),
32
56
  name: entity.metadata.name,
@@ -38,12 +62,39 @@ function toResourceSummary(entity) {
38
62
  owner: typeof entity.spec?.owner === "string" ? normalizeOwner(entity.spec.owner) : void 0,
39
63
  sourceLocation: annotations[ANNOTATION_SOURCE_LOCATION],
40
64
  frameworks: getFrameworks(entity),
41
- version: annotations[ANNOTATION_VERSION],
65
+ version: typeof entity.spec?.version === "string" ? entity.spec.version : void 0,
42
66
  kind: entity.kind,
67
+ // `parents` needs the whole read to invert; a single entity can't know
68
+ // them, so `toResourceSummaries` fills them in. `children` here is still
69
+ // the raw declared set — visibility filtering also needs the whole read.
70
+ children,
71
+ parents: [],
72
+ childCount: children.length,
43
73
  helpText: annotations[ANNOTATION_HELP],
44
74
  annotations
45
75
  };
46
76
  }
77
+ function toResourceSummaries(entities) {
78
+ const summaries = entities.map(toResourceSummary).filter((s) => s !== void 0);
79
+ const byRef = new Map(summaries.map((s) => [s.entityRef, s]));
80
+ const parentsByChild = /* @__PURE__ */ new Map();
81
+ for (const summary of summaries) {
82
+ summary.children = summary.children.filter((ref) => {
83
+ const child = byRef.get(ref);
84
+ return child !== void 0 && isRenderableContainment(summary.type, child.type);
85
+ });
86
+ summary.childCount = summary.children.length;
87
+ for (const childRef of summary.children) {
88
+ const bucket = parentsByChild.get(childRef);
89
+ if (bucket) bucket.push(summary.entityRef);
90
+ else parentsByChild.set(childRef, [summary.entityRef]);
91
+ }
92
+ }
93
+ for (const summary of summaries) {
94
+ summary.parents = parentsByChild.get(summary.entityRef) ?? [];
95
+ }
96
+ return summaries;
97
+ }
47
98
 
48
- export { isParseableEntityRef, toResourceSummary };
99
+ export { isParseableEntityRef, toResourceSummaries, toResourceSummary };
49
100
  //# sourceMappingURL=toResourceSummary.esm.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"toResourceSummary.esm.js","sources":["../src/toResourceSummary.ts"],"sourcesContent":["import {\n ANNOTATION_SOURCE_LOCATION,\n parseEntityRef,\n stringifyEntityRef,\n type Entity,\n} from '@backstage/catalog-model';\nimport {\n ANNOTATION_HELP,\n ANNOTATION_VERSION,\n getFrameworks,\n isResourceType,\n type ResourceSummary,\n} from './resources';\n\n/**\n * Map a catalog AiResource entity to the flat contract served to the\n * frontend. Returns undefined for entities with an unsupported `spec.type`,\n * which are silently dropped (ADR-0003).\n *\n * Lives in `common` rather than the backend because it is pure and\n * isomorphic — `Entity` in, `ResourceSummary` out — and both the backend\n * router and the frontend's e2e fixtures need it to agree. The e2e suite\n * builds its mock `GET /resources` payload by running the real examples in\n * `examples/catalog/` through this function, so a change to the mapping\n * cannot silently diverge from what the tests assert against.\n */\n/**\n * Normalise `spec.owner` to a full entity ref. Producers write the short form\n * (`group:platforms-developer-experience`, or a bare group name), while\n * anything that compares or links refs needs the canonical\n * `group:default/platforms-developer-experience`.\n *\n * An unparseable ref passes through untouched: the owner is a display field,\n * never worth failing the whole resource over.\n */\nfunction normalizeOwner(owner: string): string {\n try {\n return stringifyEntityRef(\n parseEntityRef(owner, {\n defaultKind: 'group',\n defaultNamespace: 'default',\n }),\n );\n } catch {\n return owner;\n }\n}\n\n/**\n * Whether a string is a parseable entity ref. The frontend links `owner` to\n * its catalog page (via `EntityRefLink`, which throws on an unparseable\n * ref) — this lets it fall back to plain text instead, matching\n * `normalizeOwner`'s own stance that a malformed owner is never worth\n * failing over.\n */\nexport function isParseableEntityRef(ref: string): boolean {\n try {\n parseEntityRef(ref, { defaultKind: 'group', defaultNamespace: 'default' });\n return true;\n } catch {\n return false;\n }\n}\n\nexport function toResourceSummary(entity: Entity): ResourceSummary | undefined {\n const type = entity.spec?.type;\n if (!isResourceType(type)) {\n return undefined;\n }\n\n const annotations = entity.metadata.annotations ?? {};\n\n return {\n entityRef: stringifyEntityRef(entity),\n name: entity.metadata.name,\n title: entity.metadata.title,\n description: entity.metadata.description,\n tags: entity.metadata.tags ?? [],\n type,\n lifecycle:\n typeof entity.spec?.lifecycle === 'string' ? entity.spec.lifecycle : '',\n owner:\n typeof entity.spec?.owner === 'string'\n ? normalizeOwner(entity.spec.owner)\n : undefined,\n sourceLocation: annotations[ANNOTATION_SOURCE_LOCATION],\n frameworks: getFrameworks(entity),\n version: annotations[ANNOTATION_VERSION],\n kind: entity.kind,\n helpText: annotations[ANNOTATION_HELP],\n annotations,\n };\n}\n"],"names":[],"mappings":";;;AAmCA,SAAS,eAAe,KAAA,EAAuB;AAC7C,EAAA,IAAI;AACF,IAAA,OAAO,kBAAA;AAAA,MACL,eAAe,KAAA,EAAO;AAAA,QACpB,WAAA,EAAa,OAAA;AAAA,QACb,gBAAA,EAAkB;AAAA,OACnB;AAAA,KACH;AAAA,EACF,CAAA,CAAA,MAAQ;AACN,IAAA,OAAO,KAAA;AAAA,EACT;AACF;AASO,SAAS,qBAAqB,GAAA,EAAsB;AACzD,EAAA,IAAI;AACF,IAAA,cAAA,CAAe,KAAK,EAAE,WAAA,EAAa,OAAA,EAAS,gBAAA,EAAkB,WAAW,CAAA;AACzE,IAAA,OAAO,IAAA;AAAA,EACT,CAAA,CAAA,MAAQ;AACN,IAAA,OAAO,KAAA;AAAA,EACT;AACF;AAEO,SAAS,kBAAkB,MAAA,EAA6C;AAC7E,EAAA,MAAM,IAAA,GAAO,OAAO,IAAA,EAAM,IAAA;AAC1B,EAAA,IAAI,CAAC,cAAA,CAAe,IAAI,CAAA,EAAG;AACzB,IAAA,OAAO,MAAA;AAAA,EACT;AAEA,EAAA,MAAM,WAAA,GAAc,MAAA,CAAO,QAAA,CAAS,WAAA,IAAe,EAAC;AAEpD,EAAA,OAAO;AAAA,IACL,SAAA,EAAW,mBAAmB,MAAM,CAAA;AAAA,IACpC,IAAA,EAAM,OAAO,QAAA,CAAS,IAAA;AAAA,IACtB,KAAA,EAAO,OAAO,QAAA,CAAS,KAAA;AAAA,IACvB,WAAA,EAAa,OAAO,QAAA,CAAS,WAAA;AAAA,IAC7B,IAAA,EAAM,MAAA,CAAO,QAAA,CAAS,IAAA,IAAQ,EAAC;AAAA,IAC/B,IAAA;AAAA,IACA,SAAA,EACE,OAAO,MAAA,CAAO,IAAA,EAAM,cAAc,QAAA,GAAW,MAAA,CAAO,KAAK,SAAA,GAAY,EAAA;AAAA,IACvE,KAAA,EACE,OAAO,MAAA,CAAO,IAAA,EAAM,KAAA,KAAU,WAC1B,cAAA,CAAe,MAAA,CAAO,IAAA,CAAK,KAAK,CAAA,GAChC,MAAA;AAAA,IACN,cAAA,EAAgB,YAAY,0BAA0B,CAAA;AAAA,IACtD,UAAA,EAAY,cAAc,MAAM,CAAA;AAAA,IAChC,OAAA,EAAS,YAAY,kBAAkB,CAAA;AAAA,IACvC,MAAM,MAAA,CAAO,IAAA;AAAA,IACb,QAAA,EAAU,YAAY,eAAe,CAAA;AAAA,IACrC;AAAA,GACF;AACF;;;;"}
1
+ {"version":3,"file":"toResourceSummary.esm.js","sources":["../src/toResourceSummary.ts"],"sourcesContent":["import {\n ANNOTATION_SOURCE_LOCATION,\n parseEntityRef,\n stringifyEntityRef,\n type Entity,\n} from '@backstage/catalog-model';\nimport {\n ANNOTATION_HELP,\n getFrameworks,\n isRenderableContainment,\n isResourceType,\n type ResourceSummary,\n} from './resources';\n\n/**\n * Map a catalog AiResource entity to the flat contract served to the\n * frontend. Returns undefined for entities with an unsupported `spec.type`,\n * which are silently dropped (ADR-0003).\n *\n * Lives in `common` rather than the backend because it is pure and\n * isomorphic — `Entity` in, `ResourceSummary` out — and both the backend\n * router and the frontend's e2e fixtures need it to agree. The e2e suite\n * builds its mock `GET /resources` payload by running the real examples in\n * `examples/catalog/` through this function, so a change to the mapping\n * cannot silently diverge from what the tests assert against.\n */\n/**\n * Normalise `spec.owner` to a full entity ref. Producers write the short form\n * (`group:platforms-developer-experience`, or a bare group name), while\n * anything that compares or links refs needs the canonical\n * `group:default/platforms-developer-experience`.\n *\n * An unparseable ref passes through untouched: the owner is a display field,\n * never worth failing the whole resource over.\n */\nfunction normalizeOwner(owner: string): string {\n try {\n return stringifyEntityRef(\n parseEntityRef(owner, {\n defaultKind: 'group',\n defaultNamespace: 'default',\n }),\n );\n } catch {\n return owner;\n }\n}\n\n/**\n * Whether a string is a parseable entity ref. The frontend links `owner` to\n * its catalog page (via `EntityRefLink`, which throws on an unparseable\n * ref) — this lets it fall back to plain text instead, matching\n * `normalizeOwner`'s own stance that a malformed owner is never worth\n * failing over.\n */\nexport function isParseableEntityRef(ref: string): boolean {\n try {\n parseEntityRef(ref, { defaultKind: 'group', defaultNamespace: 'default' });\n return true;\n } catch {\n return false;\n }\n}\n\n/**\n * Normalise a declared child ref to the canonical, lowercased form\n * `stringifyEntityRef` emits, so it compares equal to a container entity's own\n * ref regardless of how the producer wrote it (bare name or full ref). A bare\n * name resolves in the *container's own* namespace — mirroring Backstage's\n * native relation fields, which declare `defaultNamespace: 'inherit'` — not\n * always `default`; a plugin in `team-a` listing a bare `child` must resolve\n * to `airesource:team-a/child`, or it silently loses the relationship. An\n * unparseable ref is dropped: a member the catalog could never resolve is not\n * a relationship worth carrying.\n */\nfunction normalizeChildRef(\n ref: unknown,\n containerNamespace: string,\n): string | undefined {\n if (typeof ref !== 'string') return undefined;\n try {\n return stringifyEntityRef(\n parseEntityRef(ref, {\n defaultKind: 'AiResource',\n defaultNamespace: containerNamespace,\n }),\n );\n } catch {\n return undefined;\n }\n}\n\n/**\n * The refs a container declares as its contents: a `marketplace`'s\n * `spec.plugins`, a `plugin`'s `spec.skills` (ADR-0015). Leaf types declare\n * nothing. Unparseable and duplicate refs are dropped; visibility filtering\n * happens in `toResourceSummaries`, which alone sees the whole read.\n */\nfunction declaredChildren(entity: Entity): string[] {\n const type = entity.spec?.type;\n let raw: unknown;\n if (type === 'marketplace') raw = entity.spec?.plugins;\n else if (type === 'plugin') raw = entity.spec?.skills;\n if (!Array.isArray(raw)) return [];\n const containerNamespace = entity.metadata.namespace ?? 'default';\n const refs = raw\n .map(ref => normalizeChildRef(ref, containerNamespace))\n .filter((r): r is string => r !== undefined);\n return Array.from(new Set(refs));\n}\n\nexport function toResourceSummary(entity: Entity): ResourceSummary | undefined {\n const type = entity.spec?.type;\n if (!isResourceType(type)) {\n return undefined;\n }\n\n const annotations = entity.metadata.annotations ?? {};\n const children = declaredChildren(entity);\n\n return {\n entityRef: stringifyEntityRef(entity),\n name: entity.metadata.name,\n title: entity.metadata.title,\n description: entity.metadata.description,\n tags: entity.metadata.tags ?? [],\n type,\n lifecycle:\n typeof entity.spec?.lifecycle === 'string' ? entity.spec.lifecycle : '',\n owner:\n typeof entity.spec?.owner === 'string'\n ? normalizeOwner(entity.spec.owner)\n : undefined,\n sourceLocation: annotations[ANNOTATION_SOURCE_LOCATION],\n frameworks: getFrameworks(entity),\n version:\n typeof entity.spec?.version === 'string'\n ? entity.spec.version\n : undefined,\n kind: entity.kind,\n // `parents` needs the whole read to invert; a single entity can't know\n // them, so `toResourceSummaries` fills them in. `children` here is still\n // the raw declared set — visibility filtering also needs the whole read.\n children,\n parents: [],\n childCount: children.length,\n helpText: annotations[ANNOTATION_HELP],\n annotations,\n };\n}\n\n/**\n * Map a whole caller-visible catalog read to summaries, resolving containment\n * in both directions (ADR-0015). This is the function the served payload uses;\n * `toResourceSummary` alone cannot, because containment is only knowable across\n * the whole set.\n *\n * Upstream imposes no type restriction on membership — `spec.skills` /\n * `spec.plugins` carry `allowedKinds: [\"AiResource\"]`, so any AiResource type\n * is a legal member. DevAI Hub narrows that to its rendering convention\n * (ADR-0010/0015): a `marketplace` renders `plugin` children only, a `plugin`\n * renders `skill`/`agent`/`hook`/`mcp-config`. That convention is applied here,\n * once, so `children`, `childCount`, `parents`, the card chip and the detail\n * sections cannot disagree:\n *\n * - `children` keeps only refs that resolve within this read **and** whose type\n * the container legitimately contains — so a card never counts, nor a detail\n * panel links, a hidden member (ADR-0006) or an off-convention one.\n * - `parents` is the in-memory inverse of those filtered children.\n */\nexport function toResourceSummaries(entities: Entity[]): ResourceSummary[] {\n const summaries = entities\n .map(toResourceSummary)\n .filter((s): s is ResourceSummary => s !== undefined);\n\n const byRef = new Map(summaries.map(s => [s.entityRef, s]));\n const parentsByChild = new Map<string, string[]>();\n\n for (const summary of summaries) {\n summary.children = summary.children.filter(ref => {\n const child = byRef.get(ref);\n return (\n child !== undefined && isRenderableContainment(summary.type, child.type)\n );\n });\n summary.childCount = summary.children.length;\n for (const childRef of summary.children) {\n const bucket = parentsByChild.get(childRef);\n if (bucket) bucket.push(summary.entityRef);\n else parentsByChild.set(childRef, [summary.entityRef]);\n }\n }\n\n for (const summary of summaries) {\n summary.parents = parentsByChild.get(summary.entityRef) ?? [];\n }\n\n return summaries;\n}\n"],"names":[],"mappings":"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"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@nospt/plugin-dev-ai-hub-common",
3
- "version": "1.0.2",
3
+ "version": "1.1.0",
4
4
  "description": "Common types and schemas for Dev AI Hub",
5
5
  "license": "Apache-2.0",
6
6
  "main": "./dist/index.cjs.js",
@@ -52,11 +52,11 @@
52
52
  "tsc": "tsc --noEmit"
53
53
  },
54
54
  "dependencies": {
55
- "@backstage/catalog-model": "^1.9.0",
55
+ "@backstage/catalog-model": "^1.10.0",
56
56
  "zod": "^3.22.0"
57
57
  },
58
58
  "devDependencies": {
59
- "@backstage/cli": "^0.36.2",
59
+ "@backstage/cli": "^0.36.5",
60
60
  "@types/jest": "^29.0.0",
61
61
  "jest": "^29.0.0",
62
62
  "jest-environment-jsdom": "^29.0.0"