@nospt/plugin-dev-ai-hub 0.2.4 → 0.3.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.
Files changed (122) hide show
  1. package/dist/api/DevAiHubResourceClient.esm.js +97 -0
  2. package/dist/api/DevAiHubResourceClient.esm.js.map +1 -0
  3. package/dist/components/AiResourcesPage/AiResourcesPage.esm.js +150 -0
  4. package/dist/components/AiResourcesPage/AiResourcesPage.esm.js.map +1 -0
  5. package/dist/components/AiResourcesPage/AiResourcesPage.module.css.esm.js +8 -0
  6. package/dist/components/AiResourcesPage/AiResourcesPage.module.css.esm.js.map +1 -0
  7. package/dist/components/AiResourcesPage/ResourceCard.esm.js +158 -0
  8. package/dist/components/AiResourcesPage/ResourceCard.esm.js.map +1 -0
  9. package/dist/components/AiResourcesPage/ResourceCard.module.css.esm.js +8 -0
  10. package/dist/components/AiResourcesPage/ResourceCard.module.css.esm.js.map +1 -0
  11. package/dist/components/AiResourcesPage/ResourceDetailPanel.esm.js +234 -0
  12. package/dist/components/AiResourcesPage/ResourceDetailPanel.esm.js.map +1 -0
  13. package/dist/components/AiResourcesPage/ResourceDetailPanel.module.css.esm.js +8 -0
  14. package/dist/components/AiResourcesPage/ResourceDetailPanel.module.css.esm.js.map +1 -0
  15. package/dist/components/AiResourcesPage/ResourceFilters.esm.js +242 -0
  16. package/dist/components/AiResourcesPage/ResourceFilters.esm.js.map +1 -0
  17. package/dist/components/AiResourcesPage/ResourceFilters.module.css.esm.js +8 -0
  18. package/dist/components/AiResourcesPage/ResourceFilters.module.css.esm.js.map +1 -0
  19. package/dist/components/AiResourcesPage/ResourceInstallDialog.esm.js +366 -0
  20. package/dist/components/AiResourcesPage/ResourceInstallDialog.esm.js.map +1 -0
  21. package/dist/components/AiResourcesPage/ResourceInstallDialog.module.css.esm.js +8 -0
  22. package/dist/components/AiResourcesPage/ResourceInstallDialog.module.css.esm.js.map +1 -0
  23. package/dist/components/AiResourcesPage/StatTiles.esm.js +49 -0
  24. package/dist/components/AiResourcesPage/StatTiles.esm.js.map +1 -0
  25. package/dist/components/AiResourcesPage/StatTiles.module.css.esm.js +8 -0
  26. package/dist/components/AiResourcesPage/StatTiles.module.css.esm.js.map +1 -0
  27. package/dist/components/AiResourcesPage/index.esm.js +6 -0
  28. package/dist/components/AiResourcesPage/typeMeta.esm.js +35 -0
  29. package/dist/components/AiResourcesPage/typeMeta.esm.js.map +1 -0
  30. package/dist/components/ToolIcon/ToolIcon.esm.js +26 -9
  31. package/dist/components/ToolIcon/ToolIcon.esm.js.map +1 -1
  32. package/dist/hooks/useResourceBody.esm.js +39 -0
  33. package/dist/hooks/useResourceBody.esm.js.map +1 -0
  34. package/dist/hooks/useResources.esm.js +26 -0
  35. package/dist/hooks/useResources.esm.js.map +1 -0
  36. package/dist/hooks/useTelemetryCounts.esm.js +24 -0
  37. package/dist/hooks/useTelemetryCounts.esm.js.map +1 -0
  38. package/dist/index.d.ts +1 -38
  39. package/dist/index.esm.js +1 -3
  40. package/dist/index.esm.js.map +1 -1
  41. package/dist/plugin.esm.js +15 -24
  42. package/dist/plugin.esm.js.map +1 -1
  43. package/dist/routes.esm.js +1 -2
  44. package/dist/routes.esm.js.map +1 -1
  45. package/package.json +17 -10
  46. package/.eslintrc.js +0 -1
  47. package/dev/index.tsx +0 -32
  48. package/dist/api/DevAiHubClient.esm.js +0 -78
  49. package/dist/api/DevAiHubClient.esm.js.map +0 -1
  50. package/dist/components/AssetCard/AssetCard.esm.js +0 -120
  51. package/dist/components/AssetCard/AssetCard.esm.js.map +0 -1
  52. package/dist/components/AssetCard/AssetCard.module.css.esm.js +0 -8
  53. package/dist/components/AssetCard/AssetCard.module.css.esm.js.map +0 -1
  54. package/dist/components/AssetDetailPanel/AssetDetailPanel.esm.js +0 -218
  55. package/dist/components/AssetDetailPanel/AssetDetailPanel.esm.js.map +0 -1
  56. package/dist/components/AssetDetailPanel/AssetDetailPanel.module.css.esm.js +0 -8
  57. package/dist/components/AssetDetailPanel/AssetDetailPanel.module.css.esm.js.map +0 -1
  58. package/dist/components/AssetFilters/AssetFilters.esm.js +0 -176
  59. package/dist/components/AssetFilters/AssetFilters.esm.js.map +0 -1
  60. package/dist/components/AssetFilters/AssetFilters.module.css.esm.js +0 -8
  61. package/dist/components/AssetFilters/AssetFilters.module.css.esm.js.map +0 -1
  62. package/dist/components/AssetInstallDialog/AssetInstallDialog.esm.js +0 -138
  63. package/dist/components/AssetInstallDialog/AssetInstallDialog.esm.js.map +0 -1
  64. package/dist/components/AssetInstallDialog/AssetInstallDialog.module.css.esm.js +0 -8
  65. package/dist/components/AssetInstallDialog/AssetInstallDialog.module.css.esm.js.map +0 -1
  66. package/dist/components/DevAiHubPage/DevAiHubPage.esm.js +0 -195
  67. package/dist/components/DevAiHubPage/DevAiHubPage.esm.js.map +0 -1
  68. package/dist/components/DevAiHubPage/DevAiHubPage.module.css.esm.js +0 -8
  69. package/dist/components/DevAiHubPage/DevAiHubPage.module.css.esm.js.map +0 -1
  70. package/dist/components/DevAiHubPage/index.esm.js +0 -6
  71. package/dist/components/McpConfigPage/McpConfigPage.esm.js +0 -200
  72. package/dist/components/McpConfigPage/McpConfigPage.esm.js.map +0 -1
  73. package/dist/components/McpConfigPage/McpConfigPage.module.css.esm.js +0 -8
  74. package/dist/components/McpConfigPage/McpConfigPage.module.css.esm.js.map +0 -1
  75. package/dist/components/McpConfigPage/index.esm.js +0 -6
  76. package/dist/components/McpConfigPage/index.esm.js.map +0 -1
  77. package/dist/hooks/index.esm.js +0 -114
  78. package/dist/hooks/index.esm.js.map +0 -1
  79. package/dist/pluginLegacy.esm.js +0 -27
  80. package/dist/pluginLegacy.esm.js.map +0 -1
  81. package/package.json-prepack +0 -85
  82. package/src/api/DevAiHubClient.ts +0 -110
  83. package/src/components/AssetCard/AssetCard.module.css +0 -153
  84. package/src/components/AssetCard/AssetCard.test.tsx +0 -182
  85. package/src/components/AssetCard/AssetCard.tsx +0 -152
  86. package/src/components/AssetCard/index.ts +0 -1
  87. package/src/components/AssetDetailPanel/AssetDetailPanel.module.css +0 -207
  88. package/src/components/AssetDetailPanel/AssetDetailPanel.tsx +0 -313
  89. package/src/components/AssetDetailPanel/index.ts +0 -1
  90. package/src/components/AssetFilters/AssetFilters.module.css +0 -67
  91. package/src/components/AssetFilters/AssetFilters.tsx +0 -219
  92. package/src/components/AssetFilters/index.ts +0 -2
  93. package/src/components/AssetInstallDialog/AssetInstallDialog.module.css +0 -72
  94. package/src/components/AssetInstallDialog/AssetInstallDialog.tsx +0 -176
  95. package/src/components/AssetInstallDialog/index.ts +0 -1
  96. package/src/components/DevAiHubPage/DevAiHubPage.module.css +0 -105
  97. package/src/components/DevAiHubPage/DevAiHubPage.tsx +0 -197
  98. package/src/components/DevAiHubPage/index.ts +0 -1
  99. package/src/components/McpConfigDialog/McpConfigDialog.module.css +0 -111
  100. package/src/components/McpConfigDialog/McpConfigDialog.tsx +0 -271
  101. package/src/components/McpConfigDialog/index.ts +0 -1
  102. package/src/components/McpConfigPage/McpConfigPage.module.css +0 -121
  103. package/src/components/McpConfigPage/McpConfigPage.tsx +0 -242
  104. package/src/components/McpConfigPage/index.ts +0 -1
  105. package/src/components/ToolIcon/ToolIcon.tsx +0 -65
  106. package/src/components/ToolIcon/index.ts +0 -1
  107. package/src/css-modules.d.ts +0 -4
  108. package/src/hooks/index.ts +0 -152
  109. package/src/index.ts +0 -10
  110. package/src/plugin.tsx +0 -60
  111. package/src/pluginLegacy.ts +0 -42
  112. package/src/routes.ts +0 -4
  113. package/tests/e2e/.auth.json +0 -14
  114. package/tests/e2e/asset-detail.spec.ts +0 -90
  115. package/tests/e2e/asset-filtering.spec.ts +0 -145
  116. package/tests/e2e/asset-installation.spec.ts +0 -74
  117. package/tests/e2e/dev-ai-hub-page.spec.ts +0 -89
  118. package/tests/e2e/fixtures/base.ts +0 -61
  119. package/tests/e2e/fixtures/mock-api.ts +0 -221
  120. package/tests/e2e/mcp-config.spec.ts +0 -88
  121. package/tsconfig.json +0 -10
  122. /package/dist/components/{DevAiHubPage → AiResourcesPage}/index.esm.js.map +0 -0
@@ -1,13 +1,20 @@
1
1
  import { jsx } from 'react/jsx-runtime';
2
- import { RiInfinityLine, RiNavigationLine } from '@remixicon/react';
2
+ import { RiInfinityLine } from '@remixicon/react';
3
3
  import { siGooglegemini, siGithub, siAnthropic } from 'simple-icons';
4
4
 
5
+ const CURSOR_PATH = "m20.42 6.73l-8-4.62a.82.82 0 0 0-.83 0L3.58 6.73c-.22.12-.35.36-.35.61v9.32c0 .25.13.48.35.61l8.01 4.62c.26.15.57.15.83 0l8.01-4.62c.22-.12.35-.36.35-.61V7.34c0-.25-.13-.48-.35-.61Zm-.5.98L12.19 21.1c-.05.09-.19.05-.19-.05v-8.77c0-.18-.09-.34-.25-.43L4.16 7.47c-.09-.05-.05-.19.05-.19h15.46c.22 0 .36.24.25.43";
5
6
  const TOOL_ICON = {
6
- "claude-code": { ...siAnthropic, label: "Claude Code" },
7
- "github-copilot": { ...siGithub, label: "GitHub Copilot" },
7
+ "claude-code": { ...siAnthropic, label: "Claude Code", monochrome: true },
8
+ "github-copilot": { ...siGithub, label: "GitHub Copilot", monochrome: true },
8
9
  "google-gemini": { ...siGooglegemini, label: "Google Gemini" }
9
10
  };
10
- function ToolIcon({ tool, branded = true, className, style, size = 20 }) {
11
+ function ToolIcon({
12
+ tool,
13
+ branded = true,
14
+ className,
15
+ style,
16
+ size = 20
17
+ }) {
11
18
  if (tool === "all") {
12
19
  return /* @__PURE__ */ jsx(
13
20
  RiInfinityLine,
@@ -21,17 +28,27 @@ function ToolIcon({ tool, branded = true, className, style, size = 20 }) {
21
28
  }
22
29
  if (tool === "cursor") {
23
30
  return /* @__PURE__ */ jsx(
24
- RiNavigationLine,
31
+ "svg",
25
32
  {
26
- size,
33
+ xmlns: "http://www.w3.org/2000/svg",
34
+ viewBox: "0 0 24 24",
35
+ width: size,
36
+ height: size,
37
+ fill: "currentColor",
27
38
  className,
28
- style: { color: branded ? "var(--bui-fg-primary)" : "inherit", ...style },
29
- "aria-label": "Cursor"
39
+ style: {
40
+ color: branded ? "var(--bui-fg-primary)" : "inherit",
41
+ ...style
42
+ },
43
+ "aria-label": "Cursor",
44
+ role: "img",
45
+ children: /* @__PURE__ */ jsx("path", { d: CURSOR_PATH })
30
46
  }
31
47
  );
32
48
  }
33
49
  const cfg = TOOL_ICON[tool];
34
50
  if (!cfg) return null;
51
+ const brandedColor = cfg.monochrome ? "var(--bui-fg-primary)" : `#${cfg.hex}`;
35
52
  return /* @__PURE__ */ jsx(
36
53
  "svg",
37
54
  {
@@ -41,7 +58,7 @@ function ToolIcon({ tool, branded = true, className, style, size = 20 }) {
41
58
  height: size,
42
59
  fill: "currentColor",
43
60
  className,
44
- style: { color: branded ? `#${cfg.hex}` : "inherit", ...style },
61
+ style: { color: branded ? brandedColor : "inherit", ...style },
45
62
  "aria-label": cfg.label,
46
63
  role: "img",
47
64
  children: /* @__PURE__ */ jsx("path", { d: cfg.path })
@@ -1 +1 @@
1
- {"version":3,"file":"ToolIcon.esm.js","sources":["../../../src/components/ToolIcon/ToolIcon.tsx"],"sourcesContent":["import type { CSSProperties } from 'react';\nimport { RiInfinityLine, RiNavigationLine } from '@remixicon/react';\nimport { siAnthropic, siGithub, siGooglegemini } from 'simple-icons';\nimport type { AiTool } from '@nospt/plugin-dev-ai-hub-common';\n\ntype SvgTool = Exclude<AiTool, 'all' | 'cursor'>;\n\nconst TOOL_ICON: Record<SvgTool, { path: string; hex: string; label: string }> = {\n 'claude-code': { ...siAnthropic, label: 'Claude Code' },\n 'github-copilot': { ...siGithub, label: 'GitHub Copilot' },\n 'google-gemini': { ...siGooglegemini, label: 'Google Gemini' },\n};\n\ninterface ToolIconProps {\n tool: AiTool;\n /** Use the brand's official color. Defaults to true. */\n branded?: boolean;\n className?: string;\n style?: CSSProperties;\n /** Icon size in pixels. Defaults to 20. */\n size?: number;\n}\n\nexport function ToolIcon({ tool, branded = true, className, style, size = 20 }: ToolIconProps) {\n if (tool === 'all') {\n return (\n <RiInfinityLine\n size={size}\n className={className}\n style={{ color: 'var(--bui-fg-secondary)', ...style }}\n aria-label=\"Universal\"\n />\n );\n }\n\n if (tool === 'cursor') {\n return (\n <RiNavigationLine\n size={size}\n className={className}\n style={{ color: branded ? 'var(--bui-fg-primary)' : 'inherit', ...style }}\n aria-label=\"Cursor\"\n />\n );\n }\n\n const cfg = TOOL_ICON[tool as SvgTool];\n if (!cfg) return null;\n\n return (\n <svg\n xmlns=\"http://www.w3.org/2000/svg\"\n viewBox=\"0 0 24 24\"\n width={size}\n height={size}\n fill=\"currentColor\"\n className={className}\n style={{ color: branded ? `#${cfg.hex}` : 'inherit', ...style }}\n aria-label={cfg.label}\n role=\"img\"\n >\n <path d={cfg.path} />\n </svg>\n );\n}\n"],"names":[],"mappings":";;;;AAOA,MAAM,SAAA,GAA2E;AAAA,EAC/E,aAAA,EAAkB,EAAE,GAAG,WAAA,EAAgB,OAAO,aAAA,EAAc;AAAA,EAC5D,gBAAA,EAAkB,EAAE,GAAG,QAAA,EAAgB,OAAO,gBAAA,EAAiB;AAAA,EAC/D,eAAA,EAAkB,EAAE,GAAG,cAAA,EAAgB,OAAO,eAAA;AAChD,CAAA;AAYO,SAAS,QAAA,CAAS,EAAE,IAAA,EAAM,OAAA,GAAU,MAAM,SAAA,EAAW,KAAA,EAAO,IAAA,GAAO,EAAA,EAAG,EAAkB;AAC7F,EAAA,IAAI,SAAS,KAAA,EAAO;AAClB,IAAA,uBACE,GAAA;AAAA,MAAC,cAAA;AAAA,MAAA;AAAA,QACC,IAAA;AAAA,QACA,SAAA;AAAA,QACA,KAAA,EAAO,EAAE,KAAA,EAAO,yBAAA,EAA2B,GAAG,KAAA,EAAM;AAAA,QACpD,YAAA,EAAW;AAAA;AAAA,KACb;AAAA,EAEJ;AAEA,EAAA,IAAI,SAAS,QAAA,EAAU;AACrB,IAAA,uBACE,GAAA;AAAA,MAAC,gBAAA;AAAA,MAAA;AAAA,QACC,IAAA;AAAA,QACA,SAAA;AAAA,QACA,OAAO,EAAE,KAAA,EAAO,UAAU,uBAAA,GAA0B,SAAA,EAAW,GAAG,KAAA,EAAM;AAAA,QACxE,YAAA,EAAW;AAAA;AAAA,KACb;AAAA,EAEJ;AAEA,EAAA,MAAM,GAAA,GAAM,UAAU,IAAe,CAAA;AACrC,EAAA,IAAI,CAAC,KAAK,OAAO,IAAA;AAEjB,EAAA,uBACE,GAAA;AAAA,IAAC,KAAA;AAAA,IAAA;AAAA,MACC,KAAA,EAAM,4BAAA;AAAA,MACN,OAAA,EAAQ,WAAA;AAAA,MACR,KAAA,EAAO,IAAA;AAAA,MACP,MAAA,EAAQ,IAAA;AAAA,MACR,IAAA,EAAK,cAAA;AAAA,MACL,SAAA;AAAA,MACA,KAAA,EAAO,EAAE,KAAA,EAAO,OAAA,GAAU,CAAA,CAAA,EAAI,IAAI,GAAG,CAAA,CAAA,GAAK,SAAA,EAAW,GAAG,KAAA,EAAM;AAAA,MAC9D,cAAY,GAAA,CAAI,KAAA;AAAA,MAChB,IAAA,EAAK,KAAA;AAAA,MAEL,QAAA,kBAAA,GAAA,CAAC,MAAA,EAAA,EAAK,CAAA,EAAG,GAAA,CAAI,IAAA,EAAM;AAAA;AAAA,GACrB;AAEJ;;;;"}
1
+ {"version":3,"file":"ToolIcon.esm.js","sources":["../../../src/components/ToolIcon/ToolIcon.tsx"],"sourcesContent":["import type { CSSProperties } from 'react';\nimport { RiInfinityLine } from '@remixicon/react';\nimport { siAnthropic, siGithub, siGooglegemini } from 'simple-icons';\nimport type { FrameworkToken } from '@nospt/plugin-dev-ai-hub-common';\n\n/* Cursor's cube mark isn't in simple-icons; path from Boxicons Brands\n (`bxl:cursor-ai`, MIT). Monochrome brand — no branded hex. */\nconst CURSOR_PATH =\n 'm20.42 6.73l-8-4.62a.82.82 0 0 0-.83 0L3.58 6.73c-.22.12-.35.36-.35.61v9.32c0 .25.13.48.35.61l8.01 4.62c.26.15.57.15.83 0l8.01-4.62c.22-.12.35-.36.35-.61V7.34c0-.25-.13-.48-.35-.61Zm-.5.98L12.19 21.1c-.05.09-.19.05-.19-.05v-8.77c0-.18-.09-.34-.25-.43L4.16 7.47c-.09-.05-.05-.19.05-.19h15.46c.22 0 .36.24.25.43';\n\ntype SvgTool = Exclude<FrameworkToken, 'all' | 'cursor'>;\n\n/**\n * Anthropic's and GitHub's marks are monochrome brand assets (near-black,\n * `#191919` / `#181717`) meant to be manually inverted per surface — used\n * literally, they're invisible on the dark theme's dark backgrounds. Treat\n * them like `cursor` below (theme-aware `--bui-fg-primary`) instead of their\n * literal hex. Google's mark is an actual mid-tone brand colour (`#8E75B2`)\n * that reads fine on both themes, so it keeps its literal hex.\n */\nconst TOOL_ICON: Record<\n SvgTool,\n { path: string; hex: string; label: string; monochrome?: boolean }\n> = {\n 'claude-code': { ...siAnthropic, label: 'Claude Code', monochrome: true },\n 'github-copilot': { ...siGithub, label: 'GitHub Copilot', monochrome: true },\n 'google-gemini': { ...siGooglegemini, label: 'Google Gemini' },\n};\n\ninterface ToolIconProps {\n tool: FrameworkToken;\n /** Use the brand's official color. Defaults to true. */\n branded?: boolean;\n className?: string;\n style?: CSSProperties;\n /** Icon size in pixels. Defaults to 20. */\n size?: number;\n}\n\nexport function ToolIcon({\n tool,\n branded = true,\n className,\n style,\n size = 20,\n}: ToolIconProps) {\n if (tool === 'all') {\n return (\n <RiInfinityLine\n size={size}\n className={className}\n style={{ color: 'var(--bui-fg-secondary)', ...style }}\n aria-label=\"Universal\"\n />\n );\n }\n\n if (tool === 'cursor') {\n return (\n <svg\n xmlns=\"http://www.w3.org/2000/svg\"\n viewBox=\"0 0 24 24\"\n width={size}\n height={size}\n fill=\"currentColor\"\n className={className}\n style={{\n color: branded ? 'var(--bui-fg-primary)' : 'inherit',\n ...style,\n }}\n aria-label=\"Cursor\"\n role=\"img\"\n >\n <path d={CURSOR_PATH} />\n </svg>\n );\n }\n\n const cfg = TOOL_ICON[tool as SvgTool];\n if (!cfg) return null;\n\n const brandedColor = cfg.monochrome ? 'var(--bui-fg-primary)' : `#${cfg.hex}`;\n\n return (\n <svg\n xmlns=\"http://www.w3.org/2000/svg\"\n viewBox=\"0 0 24 24\"\n width={size}\n height={size}\n fill=\"currentColor\"\n className={className}\n style={{ color: branded ? brandedColor : 'inherit', ...style }}\n aria-label={cfg.label}\n role=\"img\"\n >\n <path d={cfg.path} />\n </svg>\n );\n}\n"],"names":[],"mappings":";;;;AAOA,MAAM,WAAA,GACJ,uTAAA;AAYF,MAAM,SAAA,GAGF;AAAA,EACF,eAAe,EAAE,GAAG,aAAa,KAAA,EAAO,aAAA,EAAe,YAAY,IAAA,EAAK;AAAA,EACxE,kBAAkB,EAAE,GAAG,UAAU,KAAA,EAAO,gBAAA,EAAkB,YAAY,IAAA,EAAK;AAAA,EAC3E,eAAA,EAAiB,EAAE,GAAG,cAAA,EAAgB,OAAO,eAAA;AAC/C,CAAA;AAYO,SAAS,QAAA,CAAS;AAAA,EACvB,IAAA;AAAA,EACA,OAAA,GAAU,IAAA;AAAA,EACV,SAAA;AAAA,EACA,KAAA;AAAA,EACA,IAAA,GAAO;AACT,CAAA,EAAkB;AAChB,EAAA,IAAI,SAAS,KAAA,EAAO;AAClB,IAAA,uBACE,GAAA;AAAA,MAAC,cAAA;AAAA,MAAA;AAAA,QACC,IAAA;AAAA,QACA,SAAA;AAAA,QACA,KAAA,EAAO,EAAE,KAAA,EAAO,yBAAA,EAA2B,GAAG,KAAA,EAAM;AAAA,QACpD,YAAA,EAAW;AAAA;AAAA,KACb;AAAA,EAEJ;AAEA,EAAA,IAAI,SAAS,QAAA,EAAU;AACrB,IAAA,uBACE,GAAA;AAAA,MAAC,KAAA;AAAA,MAAA;AAAA,QACC,KAAA,EAAM,4BAAA;AAAA,QACN,OAAA,EAAQ,WAAA;AAAA,QACR,KAAA,EAAO,IAAA;AAAA,QACP,MAAA,EAAQ,IAAA;AAAA,QACR,IAAA,EAAK,cAAA;AAAA,QACL,SAAA;AAAA,QACA,KAAA,EAAO;AAAA,UACL,KAAA,EAAO,UAAU,uBAAA,GAA0B,SAAA;AAAA,UAC3C,GAAG;AAAA,SACL;AAAA,QACA,YAAA,EAAW,QAAA;AAAA,QACX,IAAA,EAAK,KAAA;AAAA,QAEL,QAAA,kBAAA,GAAA,CAAC,MAAA,EAAA,EAAK,CAAA,EAAG,WAAA,EAAa;AAAA;AAAA,KACxB;AAAA,EAEJ;AAEA,EAAA,MAAM,GAAA,GAAM,UAAU,IAAe,CAAA;AACrC,EAAA,IAAI,CAAC,KAAK,OAAO,IAAA;AAEjB,EAAA,MAAM,eAAe,GAAA,CAAI,UAAA,GAAa,uBAAA,GAA0B,CAAA,CAAA,EAAI,IAAI,GAAG,CAAA,CAAA;AAE3E,EAAA,uBACE,GAAA;AAAA,IAAC,KAAA;AAAA,IAAA;AAAA,MACC,KAAA,EAAM,4BAAA;AAAA,MACN,OAAA,EAAQ,WAAA;AAAA,MACR,KAAA,EAAO,IAAA;AAAA,MACP,MAAA,EAAQ,IAAA;AAAA,MACR,IAAA,EAAK,cAAA;AAAA,MACL,SAAA;AAAA,MACA,OAAO,EAAE,KAAA,EAAO,UAAU,YAAA,GAAe,SAAA,EAAW,GAAG,KAAA,EAAM;AAAA,MAC7D,cAAY,GAAA,CAAI,KAAA;AAAA,MAChB,IAAA,EAAK,KAAA;AAAA,MAEL,QAAA,kBAAA,GAAA,CAAC,MAAA,EAAA,EAAK,CAAA,EAAG,GAAA,CAAI,IAAA,EAAM;AAAA;AAAA,GACrB;AAEJ;;;;"}
@@ -0,0 +1,39 @@
1
+ import { useState, useCallback, useEffect } from 'react';
2
+ import { useApi } from '@backstage/core-plugin-api';
3
+ import { devAiHubResourceApiRef, ResourceBodyError } from '../api/DevAiHubResourceClient.esm.js';
4
+
5
+ function useResourceBody(entityRef, enabled) {
6
+ const api = useApi(devAiHubResourceApiRef);
7
+ const [body, setBody] = useState();
8
+ const [loading, setLoading] = useState(false);
9
+ const [error, setError] = useState();
10
+ const [attempt, setAttempt] = useState(0);
11
+ const retry = useCallback(() => setAttempt((a) => a + 1), []);
12
+ useEffect(() => {
13
+ setBody(void 0);
14
+ setError(void 0);
15
+ if (!entityRef || !enabled) {
16
+ setLoading(false);
17
+ return void 0;
18
+ }
19
+ let cancelled = false;
20
+ setLoading(true);
21
+ api.getEntityBody(entityRef).then((result) => {
22
+ if (!cancelled) setBody(result);
23
+ }).catch((err) => {
24
+ if (cancelled) return;
25
+ setError(
26
+ err instanceof ResourceBodyError && err.status === 404 ? "not-found" : "upstream"
27
+ );
28
+ }).finally(() => {
29
+ if (!cancelled) setLoading(false);
30
+ });
31
+ return () => {
32
+ cancelled = true;
33
+ };
34
+ }, [api, entityRef, enabled, attempt]);
35
+ return { body, loading, error, retry };
36
+ }
37
+
38
+ export { useResourceBody };
39
+ //# sourceMappingURL=useResourceBody.esm.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"useResourceBody.esm.js","sources":["../../src/hooks/useResourceBody.ts"],"sourcesContent":["import { useCallback, useEffect, useState } from 'react';\nimport { useApi } from '@backstage/core-plugin-api';\nimport {\n devAiHubResourceApiRef,\n ResourceBody,\n ResourceBodyError,\n} from '../api/DevAiHubResourceClient';\n\nexport type ResourceBodyErrorKind = 'not-found' | 'upstream';\n\nexport interface ResourceBodyState {\n body?: ResourceBody;\n loading: boolean;\n error?: ResourceBodyErrorKind;\n retry: () => void;\n}\n\n/**\n * Lazily fetch a resource's viewable body when the detail drawer opens.\n * `enabled` is false for non-actionable resources (no source-location) —\n * nothing is fetched and the state stays empty.\n */\nexport function useResourceBody(\n entityRef: string | undefined,\n enabled: boolean,\n): ResourceBodyState {\n const api = useApi(devAiHubResourceApiRef);\n const [body, setBody] = useState<ResourceBody>();\n const [loading, setLoading] = useState(false);\n const [error, setError] = useState<ResourceBodyErrorKind>();\n const [attempt, setAttempt] = useState(0);\n\n const retry = useCallback(() => setAttempt(a => a + 1), []);\n\n useEffect(() => {\n setBody(undefined);\n setError(undefined);\n if (!entityRef || !enabled) {\n setLoading(false);\n return undefined;\n }\n\n let cancelled = false;\n setLoading(true);\n api\n .getEntityBody(entityRef)\n .then(result => {\n if (!cancelled) setBody(result);\n })\n .catch(err => {\n if (cancelled) return;\n setError(\n err instanceof ResourceBodyError && err.status === 404\n ? 'not-found'\n : 'upstream',\n );\n })\n .finally(() => {\n if (!cancelled) setLoading(false);\n });\n\n return () => {\n cancelled = true;\n };\n }, [api, entityRef, enabled, attempt]);\n\n return { body, loading, error, retry };\n}\n"],"names":[],"mappings":";;;;AAsBO,SAAS,eAAA,CACd,WACA,OAAA,EACmB;AACnB,EAAA,MAAM,GAAA,GAAM,OAAO,sBAAsB,CAAA;AACzC,EAAA,MAAM,CAAC,IAAA,EAAM,OAAO,CAAA,GAAI,QAAA,EAAuB;AAC/C,EAAA,MAAM,CAAC,OAAA,EAAS,UAAU,CAAA,GAAI,SAAS,KAAK,CAAA;AAC5C,EAAA,MAAM,CAAC,KAAA,EAAO,QAAQ,CAAA,GAAI,QAAA,EAAgC;AAC1D,EAAA,MAAM,CAAC,OAAA,EAAS,UAAU,CAAA,GAAI,SAAS,CAAC,CAAA;AAExC,EAAA,MAAM,KAAA,GAAQ,YAAY,MAAM,UAAA,CAAW,OAAK,CAAA,GAAI,CAAC,CAAA,EAAG,EAAE,CAAA;AAE1D,EAAA,SAAA,CAAU,MAAM;AACd,IAAA,OAAA,CAAQ,MAAS,CAAA;AACjB,IAAA,QAAA,CAAS,MAAS,CAAA;AAClB,IAAA,IAAI,CAAC,SAAA,IAAa,CAAC,OAAA,EAAS;AAC1B,MAAA,UAAA,CAAW,KAAK,CAAA;AAChB,MAAA,OAAO,MAAA;AAAA,IACT;AAEA,IAAA,IAAI,SAAA,GAAY,KAAA;AAChB,IAAA,UAAA,CAAW,IAAI,CAAA;AACf,IAAA,GAAA,CACG,aAAA,CAAc,SAAS,CAAA,CACvB,IAAA,CAAK,CAAA,MAAA,KAAU;AACd,MAAA,IAAI,CAAC,SAAA,EAAW,OAAA,CAAQ,MAAM,CAAA;AAAA,IAChC,CAAC,CAAA,CACA,KAAA,CAAM,CAAA,GAAA,KAAO;AACZ,MAAA,IAAI,SAAA,EAAW;AACf,MAAA,QAAA;AAAA,QACE,GAAA,YAAe,iBAAA,IAAqB,GAAA,CAAI,MAAA,KAAW,MAC/C,WAAA,GACA;AAAA,OACN;AAAA,IACF,CAAC,CAAA,CACA,OAAA,CAAQ,MAAM;AACb,MAAA,IAAI,CAAC,SAAA,EAAW,UAAA,CAAW,KAAK,CAAA;AAAA,IAClC,CAAC,CAAA;AAEH,IAAA,OAAO,MAAM;AACX,MAAA,SAAA,GAAY,IAAA;AAAA,IACd,CAAA;AAAA,EACF,GAAG,CAAC,GAAA,EAAK,SAAA,EAAW,OAAA,EAAS,OAAO,CAAC,CAAA;AAErC,EAAA,OAAO,EAAE,IAAA,EAAM,OAAA,EAAS,KAAA,EAAO,KAAA,EAAM;AACvC;;;;"}
@@ -0,0 +1,26 @@
1
+ import { useState, useEffect } from 'react';
2
+ import { useApi } from '@backstage/core-plugin-api';
3
+ import { devAiHubResourceApiRef } from '../api/DevAiHubResourceClient.esm.js';
4
+
5
+ function useResources() {
6
+ const api = useApi(devAiHubResourceApiRef);
7
+ const [items, setItems] = useState(void 0);
8
+ const [error, setError] = useState(void 0);
9
+ useEffect(() => {
10
+ let active = true;
11
+ setItems(void 0);
12
+ setError(void 0);
13
+ api.getResources().then((result) => {
14
+ if (active) setItems(result);
15
+ }).catch((e) => {
16
+ if (active) setError(e instanceof Error ? e : new Error(String(e)));
17
+ });
18
+ return () => {
19
+ active = false;
20
+ };
21
+ }, [api]);
22
+ return { items, error, loading: items === void 0 && error === void 0 };
23
+ }
24
+
25
+ export { useResources };
26
+ //# sourceMappingURL=useResources.esm.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"useResources.esm.js","sources":["../../src/hooks/useResources.ts"],"sourcesContent":["import { useEffect, useState } from 'react';\nimport { useApi } from '@backstage/core-plugin-api';\nimport type { ResourceSummary } from '@nospt/plugin-dev-ai-hub-common';\nimport { devAiHubResourceApiRef } from '../api/DevAiHubResourceClient';\n\nexport function useResources() {\n const api = useApi(devAiHubResourceApiRef);\n const [items, setItems] = useState<ResourceSummary[] | undefined>(undefined);\n const [error, setError] = useState<Error | undefined>(undefined);\n\n useEffect(() => {\n let active = true;\n setItems(undefined);\n setError(undefined);\n api\n .getResources()\n .then(result => {\n if (active) setItems(result);\n })\n .catch(e => {\n if (active) setError(e instanceof Error ? e : new Error(String(e)));\n });\n return () => {\n active = false;\n };\n }, [api]);\n\n return { items, error, loading: items === undefined && error === undefined };\n}\n"],"names":[],"mappings":";;;;AAKO,SAAS,YAAA,GAAe;AAC7B,EAAA,MAAM,GAAA,GAAM,OAAO,sBAAsB,CAAA;AACzC,EAAA,MAAM,CAAC,KAAA,EAAO,QAAQ,CAAA,GAAI,SAAwC,MAAS,CAAA;AAC3E,EAAA,MAAM,CAAC,KAAA,EAAO,QAAQ,CAAA,GAAI,SAA4B,MAAS,CAAA;AAE/D,EAAA,SAAA,CAAU,MAAM;AACd,IAAA,IAAI,MAAA,GAAS,IAAA;AACb,IAAA,QAAA,CAAS,MAAS,CAAA;AAClB,IAAA,QAAA,CAAS,MAAS,CAAA;AAClB,IAAA,GAAA,CACG,YAAA,EAAa,CACb,IAAA,CAAK,CAAA,MAAA,KAAU;AACd,MAAA,IAAI,MAAA,WAAiB,MAAM,CAAA;AAAA,IAC7B,CAAC,CAAA,CACA,KAAA,CAAM,CAAA,CAAA,KAAK;AACV,MAAA,IAAI,MAAA,EAAQ,QAAA,CAAS,CAAA,YAAa,KAAA,GAAQ,CAAA,GAAI,IAAI,KAAA,CAAM,MAAA,CAAO,CAAC,CAAC,CAAC,CAAA;AAAA,IACpE,CAAC,CAAA;AACH,IAAA,OAAO,MAAM;AACX,MAAA,MAAA,GAAS,KAAA;AAAA,IACX,CAAA;AAAA,EACF,CAAA,EAAG,CAAC,GAAG,CAAC,CAAA;AAER,EAAA,OAAO,EAAE,KAAA,EAAO,KAAA,EAAO,SAAS,KAAA,KAAU,MAAA,IAAa,UAAU,MAAA,EAAU;AAC7E;;;;"}
@@ -0,0 +1,24 @@
1
+ import { useState, useEffect } from 'react';
2
+ import { useApi } from '@backstage/core-plugin-api';
3
+ import { devAiHubResourceApiRef } from '../api/DevAiHubResourceClient.esm.js';
4
+
5
+ function useTelemetryCounts(entityRef) {
6
+ const api = useApi(devAiHubResourceApiRef);
7
+ const [counts, setCounts] = useState();
8
+ useEffect(() => {
9
+ setCounts(void 0);
10
+ if (!entityRef) return void 0;
11
+ let cancelled = false;
12
+ api.getInstallCount(entityRef).then((result) => {
13
+ if (!cancelled) setCounts(result);
14
+ }).catch(() => {
15
+ });
16
+ return () => {
17
+ cancelled = true;
18
+ };
19
+ }, [api, entityRef]);
20
+ return counts;
21
+ }
22
+
23
+ export { useTelemetryCounts };
24
+ //# sourceMappingURL=useTelemetryCounts.esm.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"useTelemetryCounts.esm.js","sources":["../../src/hooks/useTelemetryCounts.ts"],"sourcesContent":["import { useEffect, useState } from 'react';\nimport { useApi } from '@backstage/core-plugin-api';\nimport type { TelemetryCounts } from '@nospt/plugin-dev-ai-hub-common';\nimport { devAiHubResourceApiRef } from '../api/DevAiHubResourceClient';\n\n/**\n * Raw per-action telemetry counts for a resource (ADR-0007). Fetch failures\n * are swallowed — a missing count is not worth surfacing as a UI error, the\n * card/detail just renders without it.\n */\nexport function useTelemetryCounts(\n entityRef: string | undefined,\n): TelemetryCounts | undefined {\n const api = useApi(devAiHubResourceApiRef);\n const [counts, setCounts] = useState<TelemetryCounts>();\n\n useEffect(() => {\n setCounts(undefined);\n if (!entityRef) return undefined;\n\n let cancelled = false;\n api\n .getInstallCount(entityRef)\n .then(result => {\n if (!cancelled) setCounts(result);\n })\n .catch(() => {\n // ignored — see above\n });\n return () => {\n cancelled = true;\n };\n }, [api, entityRef]);\n\n return counts;\n}\n"],"names":[],"mappings":";;;;AAUO,SAAS,mBACd,SAAA,EAC6B;AAC7B,EAAA,MAAM,GAAA,GAAM,OAAO,sBAAsB,CAAA;AACzC,EAAA,MAAM,CAAC,MAAA,EAAQ,SAAS,CAAA,GAAI,QAAA,EAA0B;AAEtD,EAAA,SAAA,CAAU,MAAM;AACd,IAAA,SAAA,CAAU,MAAS,CAAA;AACnB,IAAA,IAAI,CAAC,WAAW,OAAO,MAAA;AAEvB,IAAA,IAAI,SAAA,GAAY,KAAA;AAChB,IAAA,GAAA,CACG,eAAA,CAAgB,SAAS,CAAA,CACzB,IAAA,CAAK,CAAA,MAAA,KAAU;AACd,MAAA,IAAI,CAAC,SAAA,EAAW,SAAA,CAAU,MAAM,CAAA;AAAA,IAClC,CAAC,CAAA,CACA,KAAA,CAAM,MAAM;AAAA,IAEb,CAAC,CAAA;AACH,IAAA,OAAO,MAAM;AACX,MAAA,SAAA,GAAY,IAAA;AAAA,IACd,CAAA;AAAA,EACF,CAAA,EAAG,CAAC,GAAA,EAAK,SAAS,CAAC,CAAA;AAEnB,EAAA,OAAO,MAAA;AACT;;;;"}
package/dist/index.d.ts CHANGED
@@ -1,21 +1,9 @@
1
1
  import * as react from 'react';
2
- import { ComponentType } from 'react';
3
2
  import * as _backstage_frontend_plugin_api from '@backstage/frontend-plugin-api';
4
- import { AssetListFilter, AiAssetListResponse, AiAsset, AiHubProvider, AiHubStats } from '@nospt/plugin-dev-ai-hub-common';
5
3
 
6
4
  declare const devAiHubPlugin: _backstage_frontend_plugin_api.OverridableFrontendPlugin<{
7
5
  root: _backstage_frontend_plugin_api.RouteRef<undefined>;
8
- mcpConfig: _backstage_frontend_plugin_api.RouteRef<undefined>;
9
6
  }, {}, {
10
- "api:dev-ai-hub": _backstage_frontend_plugin_api.OverridableExtensionDefinition<{
11
- kind: "api";
12
- name: undefined;
13
- config: {};
14
- configInput: {};
15
- output: _backstage_frontend_plugin_api.ExtensionDataRef<_backstage_frontend_plugin_api.AnyApiFactory, "core.api.factory", {}>;
16
- inputs: {};
17
- params: <TApi, TImpl extends TApi, TDeps extends { [name in string]: unknown; }>(params: _backstage_frontend_plugin_api.ApiFactory<TApi, TImpl, TDeps>) => _backstage_frontend_plugin_api.ExtensionBlueprintParams<_backstage_frontend_plugin_api.AnyApiFactory>;
18
- }>;
19
7
  "page:dev-ai-hub": _backstage_frontend_plugin_api.OverridableExtensionDefinition<{
20
8
  kind: "page";
21
9
  name: undefined;
@@ -58,31 +46,6 @@ declare const devAiHubPlugin: _backstage_frontend_plugin_api.OverridableFrontend
58
46
  }>;
59
47
  }>;
60
48
 
61
- /**
62
- * Legacy Backstage frontend system support.
63
- *
64
- * For New Frontend System (NFS) use `devAiHubPlugin` from `./plugin` instead.
65
- */
66
-
67
- /** Routable page extension for the legacy frontend system. */
68
- declare const DevAiHubPage: ComponentType;
69
-
70
- declare const devAiHubApiRef: _backstage_frontend_plugin_api.ApiRef<DevAiHubApi>;
71
- interface DevAiHubApi {
72
- listAssets(filter?: AssetListFilter): Promise<AiAssetListResponse>;
73
- getAsset(id: string): Promise<AiAsset>;
74
- getAssetRaw(id: string): Promise<string>;
75
- /** Returns the absolute URL for the download endpoint (zip for skills, md for others). */
76
- getDownloadUrl(id: string): Promise<string>;
77
- trackInstall(id: string): Promise<void>;
78
- listProviders(): Promise<AiHubProvider[]>;
79
- getProviderStatus(id: string): Promise<AiHubProvider>;
80
- triggerSync(id: string): Promise<void>;
81
- getStats(): Promise<AiHubStats>;
82
- }
83
-
84
49
  declare const rootRouteRef: _backstage_frontend_plugin_api.RouteRef<undefined>;
85
- declare const mcpConfigRouteRef: _backstage_frontend_plugin_api.RouteRef<undefined>;
86
50
 
87
- export { DevAiHubPage, devAiHubApiRef, devAiHubPlugin, mcpConfigRouteRef, rootRouteRef };
88
- export type { DevAiHubApi };
51
+ export { devAiHubPlugin, rootRouteRef };
package/dist/index.esm.js CHANGED
@@ -1,5 +1,3 @@
1
1
  export { devAiHubPlugin } from './plugin.esm.js';
2
- export { DevAiHubPage } from './pluginLegacy.esm.js';
3
- export { devAiHubApiRef } from './api/DevAiHubClient.esm.js';
4
- export { mcpConfigRouteRef, rootRouteRef } from './routes.esm.js';
2
+ export { rootRouteRef } from './routes.esm.js';
5
3
  //# sourceMappingURL=index.esm.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"index.esm.js","sources":[],"sourcesContent":[],"names":[],"mappings":";;;"}
1
+ {"version":3,"file":"index.esm.js","sources":[],"sourcesContent":[],"names":[],"mappings":";"}
@@ -1,25 +1,15 @@
1
1
  import { jsx } from 'react/jsx-runtime';
2
- import { createFrontendPlugin, ApiBlueprint, createApiFactory, fetchApiRef, discoveryApiRef, PageBlueprint, SubPageBlueprint } from '@backstage/frontend-plugin-api';
2
+ import { createFrontendPlugin, PageBlueprint, ApiBlueprint, createApiFactory, fetchApiRef, discoveryApiRef, SubPageBlueprint } from '@backstage/frontend-plugin-api';
3
3
  import { RiRobot3Fill } from '@remixicon/react';
4
- import { DevAiHubClient, devAiHubApiRef } from './api/DevAiHubClient.esm.js';
5
- import { mcpConfigRouteRef, rootRouteRef } from './routes.esm.js';
4
+ import { DevAiHubResourceClient, devAiHubResourceApiRef } from './api/DevAiHubResourceClient.esm.js';
5
+ import { rootRouteRef } from './routes.esm.js';
6
6
 
7
7
  const devAiHubPlugin = createFrontendPlugin({
8
8
  pluginId: "dev-ai-hub",
9
9
  routes: {
10
- root: rootRouteRef,
11
- mcpConfig: mcpConfigRouteRef
10
+ root: rootRouteRef
12
11
  },
13
12
  extensions: [
14
- ApiBlueprint.make({
15
- params: (defineParams) => defineParams(
16
- createApiFactory({
17
- api: devAiHubApiRef,
18
- deps: { discoveryApi: discoveryApiRef, fetchApi: fetchApiRef },
19
- factory: ({ discoveryApi, fetchApi }) => new DevAiHubClient(discoveryApi, fetchApi)
20
- })
21
- )
22
- }),
23
13
  PageBlueprint.make({
24
14
  params: {
25
15
  path: "/dev-ai-hub",
@@ -28,21 +18,22 @@ const devAiHubPlugin = createFrontendPlugin({
28
18
  routeRef: rootRouteRef
29
19
  }
30
20
  }),
21
+ ApiBlueprint.make({
22
+ name: "resources",
23
+ params: (defineParams) => defineParams(
24
+ createApiFactory({
25
+ api: devAiHubResourceApiRef,
26
+ deps: { discoveryApi: discoveryApiRef, fetchApi: fetchApiRef },
27
+ factory: ({ discoveryApi, fetchApi }) => new DevAiHubResourceClient(discoveryApi, fetchApi)
28
+ })
29
+ )
30
+ }),
31
31
  SubPageBlueprint.make({
32
32
  name: "browse",
33
33
  params: {
34
34
  path: "browse",
35
35
  title: "Browse",
36
- loader: () => import('./components/DevAiHubPage/index.esm.js').then((m) => /* @__PURE__ */ jsx(m.DevAiHubPage, {}))
37
- }
38
- }),
39
- SubPageBlueprint.make({
40
- name: "mcp-config",
41
- params: {
42
- path: "mcp-config",
43
- title: "Configure MCP",
44
- routeRef: mcpConfigRouteRef,
45
- loader: () => import('./components/McpConfigPage/index.esm.js').then((m) => /* @__PURE__ */ jsx(m.McpConfigPage, {}))
36
+ loader: () => import('./components/AiResourcesPage/index.esm.js').then((m) => /* @__PURE__ */ jsx(m.AiResourcesPage, {}))
46
37
  }
47
38
  })
48
39
  ]
@@ -1 +1 @@
1
- {"version":3,"file":"plugin.esm.js","sources":["../src/plugin.tsx"],"sourcesContent":["import {\n createFrontendPlugin,\n PageBlueprint,\n SubPageBlueprint,\n ApiBlueprint,\n createApiFactory,\n discoveryApiRef,\n fetchApiRef,\n} from '@backstage/frontend-plugin-api';\nimport { RiRobot3Fill } from '@remixicon/react';\nimport { devAiHubApiRef, DevAiHubClient } from './api/DevAiHubClient';\nimport { rootRouteRef, mcpConfigRouteRef } from './routes';\n\nexport const devAiHubPlugin = createFrontendPlugin({\n pluginId: 'dev-ai-hub',\n routes: {\n root: rootRouteRef,\n mcpConfig: mcpConfigRouteRef,\n },\n extensions: [\n ApiBlueprint.make({\n params: defineParams =>\n defineParams(\n createApiFactory({\n api: devAiHubApiRef,\n deps: { discoveryApi: discoveryApiRef, fetchApi: fetchApiRef },\n factory: ({ discoveryApi, fetchApi }) =>\n new DevAiHubClient(discoveryApi, fetchApi),\n }),\n ),\n }),\n PageBlueprint.make({\n params: {\n path: '/dev-ai-hub',\n title: 'Dev AI Hub',\n icon: <RiRobot3Fill />,\n routeRef: rootRouteRef,\n },\n }),\n SubPageBlueprint.make({\n name: 'browse',\n params: {\n path: 'browse',\n title: 'Browse',\n loader: () =>\n import('./components/DevAiHubPage').then(m => <m.DevAiHubPage />),\n },\n }),\n SubPageBlueprint.make({\n name: 'mcp-config',\n params: {\n path: 'mcp-config',\n title: 'Configure MCP',\n routeRef: mcpConfigRouteRef,\n loader: () =>\n import('./components/McpConfigPage').then(m => <m.McpConfigPage />),\n },\n }),\n ],\n});\n"],"names":[],"mappings":";;;;;;AAaO,MAAM,iBAAiB,oBAAA,CAAqB;AAAA,EACjD,QAAA,EAAU,YAAA;AAAA,EACV,MAAA,EAAQ;AAAA,IACN,IAAA,EAAM,YAAA;AAAA,IACN,SAAA,EAAW;AAAA,GACb;AAAA,EACA,UAAA,EAAY;AAAA,IACV,aAAa,IAAA,CAAK;AAAA,MAChB,QAAQ,CAAA,YAAA,KACN,YAAA;AAAA,QACE,gBAAA,CAAiB;AAAA,UACf,GAAA,EAAK,cAAA;AAAA,UACL,IAAA,EAAM,EAAE,YAAA,EAAc,eAAA,EAAiB,UAAU,WAAA,EAAY;AAAA,UAC7D,OAAA,EAAS,CAAC,EAAE,YAAA,EAAc,UAAS,KACjC,IAAI,cAAA,CAAe,YAAA,EAAc,QAAQ;AAAA,SAC5C;AAAA;AACH,KACH,CAAA;AAAA,IACD,cAAc,IAAA,CAAK;AAAA,MACjB,MAAA,EAAQ;AAAA,QACN,IAAA,EAAM,aAAA;AAAA,QACN,KAAA,EAAO,YAAA;AAAA,QACP,IAAA,sBAAO,YAAA,EAAA,EAAa,CAAA;AAAA,QACpB,QAAA,EAAU;AAAA;AACZ,KACD,CAAA;AAAA,IACD,iBAAiB,IAAA,CAAK;AAAA,MACpB,IAAA,EAAM,QAAA;AAAA,MACN,MAAA,EAAQ;AAAA,QACN,IAAA,EAAM,QAAA;AAAA,QACN,KAAA,EAAO,QAAA;AAAA,QACP,MAAA,EAAQ,MACN,OAAO,wCAA2B,CAAA,CAAE,IAAA,CAAK,CAAA,CAAA,qBAAK,GAAA,CAAC,CAAA,CAAE,YAAA,EAAF,EAAe,CAAE;AAAA;AACpE,KACD,CAAA;AAAA,IACD,iBAAiB,IAAA,CAAK;AAAA,MACpB,IAAA,EAAM,YAAA;AAAA,MACN,MAAA,EAAQ;AAAA,QACN,IAAA,EAAM,YAAA;AAAA,QACN,KAAA,EAAO,eAAA;AAAA,QACP,QAAA,EAAU,iBAAA;AAAA,QACV,MAAA,EAAQ,MACN,OAAO,yCAA4B,CAAA,CAAE,IAAA,CAAK,CAAA,CAAA,qBAAK,GAAA,CAAC,CAAA,CAAE,aAAA,EAAF,EAAgB,CAAE;AAAA;AACtE,KACD;AAAA;AAEL,CAAC;;;;"}
1
+ {"version":3,"file":"plugin.esm.js","sources":["../src/plugin.tsx"],"sourcesContent":["import {\n createFrontendPlugin,\n PageBlueprint,\n SubPageBlueprint,\n ApiBlueprint,\n createApiFactory,\n discoveryApiRef,\n fetchApiRef,\n} from '@backstage/frontend-plugin-api';\nimport { RiRobot3Fill } from '@remixicon/react';\nimport {\n devAiHubResourceApiRef,\n DevAiHubResourceClient,\n} from './api/DevAiHubResourceClient';\nimport { rootRouteRef } from './routes';\n\nexport const devAiHubPlugin = createFrontendPlugin({\n pluginId: 'dev-ai-hub',\n routes: {\n root: rootRouteRef,\n },\n extensions: [\n PageBlueprint.make({\n params: {\n path: '/dev-ai-hub',\n title: 'Dev AI Hub',\n icon: <RiRobot3Fill />,\n routeRef: rootRouteRef,\n },\n }),\n ApiBlueprint.make({\n name: 'resources',\n params: defineParams =>\n defineParams(\n createApiFactory({\n api: devAiHubResourceApiRef,\n deps: { discoveryApi: discoveryApiRef, fetchApi: fetchApiRef },\n factory: ({ discoveryApi, fetchApi }) =>\n new DevAiHubResourceClient(discoveryApi, fetchApi),\n }),\n ),\n }),\n SubPageBlueprint.make({\n name: 'browse',\n params: {\n path: 'browse',\n title: 'Browse',\n loader: () =>\n import('./components/AiResourcesPage').then(m => (\n <m.AiResourcesPage />\n )),\n },\n }),\n ],\n});\n"],"names":[],"mappings":";;;;;;AAgBO,MAAM,iBAAiB,oBAAA,CAAqB;AAAA,EACjD,QAAA,EAAU,YAAA;AAAA,EACV,MAAA,EAAQ;AAAA,IACN,IAAA,EAAM;AAAA,GACR;AAAA,EACA,UAAA,EAAY;AAAA,IACV,cAAc,IAAA,CAAK;AAAA,MACjB,MAAA,EAAQ;AAAA,QACN,IAAA,EAAM,aAAA;AAAA,QACN,KAAA,EAAO,YAAA;AAAA,QACP,IAAA,sBAAO,YAAA,EAAA,EAAa,CAAA;AAAA,QACpB,QAAA,EAAU;AAAA;AACZ,KACD,CAAA;AAAA,IACD,aAAa,IAAA,CAAK;AAAA,MAChB,IAAA,EAAM,WAAA;AAAA,MACN,QAAQ,CAAA,YAAA,KACN,YAAA;AAAA,QACE,gBAAA,CAAiB;AAAA,UACf,GAAA,EAAK,sBAAA;AAAA,UACL,IAAA,EAAM,EAAE,YAAA,EAAc,eAAA,EAAiB,UAAU,WAAA,EAAY;AAAA,UAC7D,OAAA,EAAS,CAAC,EAAE,YAAA,EAAc,UAAS,KACjC,IAAI,sBAAA,CAAuB,YAAA,EAAc,QAAQ;AAAA,SACpD;AAAA;AACH,KACH,CAAA;AAAA,IACD,iBAAiB,IAAA,CAAK;AAAA,MACpB,IAAA,EAAM,QAAA;AAAA,MACN,MAAA,EAAQ;AAAA,QACN,IAAA,EAAM,QAAA;AAAA,QACN,KAAA,EAAO,QAAA;AAAA,QACP,MAAA,EAAQ,MACN,OAAO,2CAA8B,CAAA,CAAE,IAAA,CAAK,CAAA,CAAA,qBAC1C,GAAA,CAAC,CAAA,CAAE,eAAA,EAAF,EAAkB,CACpB;AAAA;AACL,KACD;AAAA;AAEL,CAAC;;;;"}
@@ -1,7 +1,6 @@
1
1
  import { createRouteRef } from '@backstage/frontend-plugin-api';
2
2
 
3
3
  const rootRouteRef = createRouteRef();
4
- const mcpConfigRouteRef = createRouteRef();
5
4
 
6
- export { mcpConfigRouteRef, rootRouteRef };
5
+ export { rootRouteRef };
7
6
  //# sourceMappingURL=routes.esm.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"routes.esm.js","sources":["../src/routes.ts"],"sourcesContent":["import { createRouteRef } from '@backstage/frontend-plugin-api';\n\nexport const rootRouteRef = createRouteRef();\nexport const mcpConfigRouteRef = createRouteRef();\n"],"names":[],"mappings":";;AAEO,MAAM,eAAe,cAAA;AACrB,MAAM,oBAAoB,cAAA;;;;"}
1
+ {"version":3,"file":"routes.esm.js","sources":["../src/routes.ts"],"sourcesContent":["import { createRouteRef } from '@backstage/frontend-plugin-api';\n\nexport const rootRouteRef = createRouteRef();\n"],"names":[],"mappings":";;AAEO,MAAM,eAAe,cAAA;;;;"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@nospt/plugin-dev-ai-hub",
3
- "version": "0.2.4",
3
+ "version": "0.3.0",
4
4
  "description": "Frontend plugin for Dev AI Hub",
5
5
  "license": "Apache-2.0",
6
6
  "main": "./dist/index.esm.js",
@@ -38,6 +38,9 @@
38
38
  ]
39
39
  },
40
40
  "sideEffects": false,
41
+ "files": [
42
+ "dist"
43
+ ],
41
44
  "scripts": {
42
45
  "start": "backstage-cli package start --config ../../app-config.dev.yaml",
43
46
  "build": "backstage-cli package build",
@@ -52,13 +55,11 @@
52
55
  "tsc": "tsc --noEmit"
53
56
  },
54
57
  "dependencies": {
55
- "@backstage/core-components": "^0.18.9",
56
- "@backstage/core-plugin-api": "^1.12.5",
57
- "@backstage/frontend-plugin-api": "^0.16.2",
58
- "@backstage/ui": "^0.14.3",
59
- "@mui/icons-material": "^5.15.0",
60
- "@mui/material": "^5.15.0",
61
- "@nospt/plugin-dev-ai-hub-common": "^0.2.4",
58
+ "@backstage/core-components": "^0.18.10",
59
+ "@backstage/core-plugin-api": "^1.12.6",
60
+ "@backstage/frontend-plugin-api": "^0.17.0",
61
+ "@backstage/ui": "^0.15.0",
62
+ "@nospt/plugin-dev-ai-hub-common": "^0.3.0",
62
63
  "@remixicon/react": "^4.6.0",
63
64
  "react-markdown": "^9.0.0",
64
65
  "react-syntax-highlighter": "^16.1.1",
@@ -66,14 +67,20 @@
66
67
  "simple-icons": "^13.0.0"
67
68
  },
68
69
  "devDependencies": {
69
- "@backstage/cli": "^0.36.1",
70
- "@backstage/dev-utils": "^1.1.22",
70
+ "@backstage/cli": "^0.36.2",
71
+ "@backstage/dev-utils": "^1.1.23",
72
+ "@backstage/frontend-defaults": "^0.5.2",
73
+ "@backstage/plugin-app-react": "^0.2.3",
74
+ "@backstage/plugin-catalog": "^2.0.5",
75
+ "@backstage/plugin-catalog-react": "^3.0.0",
76
+ "@backstage/plugin-user-settings": "^0.9.3",
71
77
  "@playwright/test": "^1.60.0",
72
78
  "@testing-library/dom": "^10.0.0",
73
79
  "@testing-library/jest-dom": "^6.0.0",
74
80
  "@testing-library/react": "^16.3.1",
75
81
  "@types/jest": "^30.0.0",
76
82
  "@types/react": "^18.0.0",
83
+ "@types/react-dom": "^18.0.0",
77
84
  "@types/react-syntax-highlighter": "^15.0.0",
78
85
  "jest": "^30.2.0",
79
86
  "jsdom": "^27.1.0",
package/.eslintrc.js DELETED
@@ -1 +0,0 @@
1
- module.exports = require('@backstage/cli/config/eslint-factory')(__dirname);
package/dev/index.tsx DELETED
@@ -1,32 +0,0 @@
1
- import { createDevApp } from '@backstage/dev-utils';
2
- import { discoveryApiRef, fetchApiRef } from '@backstage/core-plugin-api';
3
- import { DevAiHubPage } from '../src/components/DevAiHubPage';
4
- import { McpConfigPage } from '../src/components/McpConfigPage';
5
- import { devAiHubApiRef, DevAiHubClient } from '../src/api/DevAiHubClient';
6
-
7
- // Bypass Backstage guest auth without a running auth backend.
8
- // The legacy token path generates the token locally — same keys the e2e
9
- // base fixture injects via page.addInitScript().
10
- if (typeof window !== 'undefined') {
11
- localStorage.setItem('@backstage/core:SignInPage:provider', 'guest');
12
- localStorage.setItem('enableLegacyGuestToken', 'true');
13
- }
14
-
15
- createDevApp()
16
- .registerApi({
17
- api: devAiHubApiRef,
18
- deps: { discoveryApi: discoveryApiRef, fetchApi: fetchApiRef },
19
- factory: ({ discoveryApi, fetchApi }) =>
20
- new DevAiHubClient(discoveryApi, fetchApi),
21
- })
22
- .addPage({
23
- element: <DevAiHubPage />,
24
- title: 'Dev AI Hub',
25
- path: '/dev-ai-hub',
26
- })
27
- .addPage({
28
- element: <McpConfigPage />,
29
- title: 'MCP Config',
30
- path: '/mcp-config',
31
- })
32
- .render();
@@ -1,78 +0,0 @@
1
- import { createApiRef } from '@backstage/core-plugin-api';
2
-
3
- const devAiHubApiRef = createApiRef({
4
- id: "plugin.dev-ai-hub.service"
5
- });
6
- class DevAiHubClient {
7
- constructor(discoveryApi, fetchApi) {
8
- this.discoveryApi = discoveryApi;
9
- this.fetchApi = fetchApi;
10
- }
11
- discoveryApi;
12
- fetchApi;
13
- async baseUrl() {
14
- return this.discoveryApi.getBaseUrl("dev-ai-hub");
15
- }
16
- async fetch(path, init) {
17
- const base = await this.baseUrl();
18
- const response = await this.fetchApi.fetch(`${base}${path}`, init);
19
- if (!response.ok) {
20
- const text = await response.text();
21
- throw new Error(
22
- `Dev AI Hub API error ${response.status}: ${text}`
23
- );
24
- }
25
- return response.json();
26
- }
27
- async listAssets(filter) {
28
- const params = new URLSearchParams();
29
- if (filter?.type) params.set("type", filter.type);
30
- if (filter?.tool) params.set("tool", filter.tool);
31
- if (filter?.search) params.set("search", filter.search);
32
- if (filter?.providerId) params.set("provider", filter.providerId);
33
- if (filter?.tags?.length) params.set("tags", filter.tags.join(","));
34
- if (filter?.page) params.set("page", String(filter.page));
35
- if (filter?.pageSize) params.set("pageSize", String(filter.pageSize));
36
- const qs = params.toString();
37
- return this.fetch(`/assets${qs ? `?${qs}` : ""}`);
38
- }
39
- async getAsset(id) {
40
- return this.fetch(`/assets/${encodeURIComponent(id)}`);
41
- }
42
- async getAssetRaw(id) {
43
- const base = await this.baseUrl();
44
- const response = await this.fetchApi.fetch(
45
- `${base}/assets/${encodeURIComponent(id)}/raw`
46
- );
47
- if (!response.ok) throw new Error(`Failed to fetch raw asset: ${response.status}`);
48
- return response.text();
49
- }
50
- async getDownloadUrl(id) {
51
- const base = await this.baseUrl();
52
- return `${base}/assets/${encodeURIComponent(id)}/download`;
53
- }
54
- async trackInstall(id) {
55
- await this.fetch(`/assets/${encodeURIComponent(id)}/track-install`, {
56
- method: "POST"
57
- });
58
- }
59
- async listProviders() {
60
- return this.fetch("/providers");
61
- }
62
- async getProviderStatus(id) {
63
- return this.fetch(
64
- `/providers/${encodeURIComponent(id)}/status`
65
- );
66
- }
67
- async triggerSync(id) {
68
- await this.fetch(`/providers/${encodeURIComponent(id)}/sync`, {
69
- method: "POST"
70
- });
71
- }
72
- async getStats() {
73
- return this.fetch("/stats");
74
- }
75
- }
76
-
77
- export { DevAiHubClient, devAiHubApiRef };
78
- //# sourceMappingURL=DevAiHubClient.esm.js.map
@@ -1 +0,0 @@
1
- {"version":3,"file":"DevAiHubClient.esm.js","sources":["../../src/api/DevAiHubClient.ts"],"sourcesContent":["import {\n createApiRef,\n DiscoveryApi,\n FetchApi,\n} from '@backstage/core-plugin-api';\nimport type {\n AiAsset,\n AiAssetListResponse,\n AiHubProvider,\n AiHubStats,\n AssetListFilter,\n} from '@nospt/plugin-dev-ai-hub-common';\n\nexport const devAiHubApiRef = createApiRef<DevAiHubApi>({\n id: 'plugin.dev-ai-hub.service',\n});\n\nexport interface DevAiHubApi {\n listAssets(filter?: AssetListFilter): Promise<AiAssetListResponse>;\n getAsset(id: string): Promise<AiAsset>;\n getAssetRaw(id: string): Promise<string>;\n /** Returns the absolute URL for the download endpoint (zip for skills, md for others). */\n getDownloadUrl(id: string): Promise<string>;\n trackInstall(id: string): Promise<void>;\n listProviders(): Promise<AiHubProvider[]>;\n getProviderStatus(id: string): Promise<AiHubProvider>;\n triggerSync(id: string): Promise<void>;\n getStats(): Promise<AiHubStats>;\n}\n\nexport class DevAiHubClient implements DevAiHubApi {\n constructor(\n private readonly discoveryApi: DiscoveryApi,\n private readonly fetchApi: FetchApi,\n ) {}\n\n private async baseUrl(): Promise<string> {\n return this.discoveryApi.getBaseUrl('dev-ai-hub');\n }\n\n private async fetch<T>(path: string, init?: RequestInit): Promise<T> {\n const base = await this.baseUrl();\n const response = await this.fetchApi.fetch(`${base}${path}`, init);\n if (!response.ok) {\n const text = await response.text();\n throw new Error(\n `Dev AI Hub API error ${response.status}: ${text}`,\n );\n }\n return response.json() as Promise<T>;\n }\n\n async listAssets(filter?: AssetListFilter): Promise<AiAssetListResponse> {\n const params = new URLSearchParams();\n if (filter?.type) params.set('type', filter.type);\n if (filter?.tool) params.set('tool', filter.tool);\n if (filter?.search) params.set('search', filter.search);\n if (filter?.providerId) params.set('provider', filter.providerId);\n if (filter?.tags?.length) params.set('tags', filter.tags.join(','));\n if (filter?.page) params.set('page', String(filter.page));\n if (filter?.pageSize) params.set('pageSize', String(filter.pageSize));\n\n const qs = params.toString();\n return this.fetch<AiAssetListResponse>(`/assets${qs ? `?${qs}` : ''}`);\n }\n\n async getAsset(id: string): Promise<AiAsset> {\n return this.fetch<AiAsset>(`/assets/${encodeURIComponent(id)}`);\n }\n\n async getAssetRaw(id: string): Promise<string> {\n const base = await this.baseUrl();\n const response = await this.fetchApi.fetch(\n `${base}/assets/${encodeURIComponent(id)}/raw`,\n );\n if (!response.ok) throw new Error(`Failed to fetch raw asset: ${response.status}`);\n return response.text();\n }\n\n async getDownloadUrl(id: string): Promise<string> {\n const base = await this.baseUrl();\n return `${base}/assets/${encodeURIComponent(id)}/download`;\n }\n\n async trackInstall(id: string): Promise<void> {\n await this.fetch(`/assets/${encodeURIComponent(id)}/track-install`, {\n method: 'POST',\n });\n }\n\n async listProviders(): Promise<AiHubProvider[]> {\n return this.fetch<AiHubProvider[]>('/providers');\n }\n\n async getProviderStatus(id: string): Promise<AiHubProvider> {\n return this.fetch<AiHubProvider>(\n `/providers/${encodeURIComponent(id)}/status`,\n );\n }\n\n async triggerSync(id: string): Promise<void> {\n await this.fetch(`/providers/${encodeURIComponent(id)}/sync`, {\n method: 'POST',\n });\n }\n\n async getStats(): Promise<AiHubStats> {\n return this.fetch<AiHubStats>('/stats');\n }\n}\n"],"names":[],"mappings":"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"}