@nospt/plugin-dev-ai-hub-backend 0.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.
Files changed (36) hide show
  1. package/.eslintrc.js +1 -0
  2. package/dev/index.ts +8 -0
  3. package/dist/database/AiAssetStore.cjs.js +224 -0
  4. package/dist/database/AiAssetStore.cjs.js.map +1 -0
  5. package/dist/index.cjs.js +11 -0
  6. package/dist/index.cjs.js.map +1 -0
  7. package/dist/index.d.ts +5 -0
  8. package/dist/plugin.cjs.js +70 -0
  9. package/dist/plugin.cjs.js.map +1 -0
  10. package/dist/router.cjs.js +247 -0
  11. package/dist/router.cjs.js.map +1 -0
  12. package/dist/service/AiAssetSyncService.cjs.js +210 -0
  13. package/dist/service/AiAssetSyncService.cjs.js.map +1 -0
  14. package/dist/service/AssetParser.cjs.js +91 -0
  15. package/dist/service/AssetParser.cjs.js.map +1 -0
  16. package/dist/service/McpServerService.cjs.js +366 -0
  17. package/dist/service/McpServerService.cjs.js.map +1 -0
  18. package/migrations/001_init.js +47 -0
  19. package/migrations/002_install_count.js +17 -0
  20. package/migrations/003_install_paths.js +19 -0
  21. package/migrations/004_label.js +17 -0
  22. package/migrations/005_remove_unused_fields.js +22 -0
  23. package/migrations/006_resources_content.js +16 -0
  24. package/package.json +72 -0
  25. package/package.json-prepack +64 -0
  26. package/src/database/AiAssetStore.test.ts +294 -0
  27. package/src/database/AiAssetStore.ts +256 -0
  28. package/src/index.ts +2 -0
  29. package/src/plugin.ts +84 -0
  30. package/src/router.ts +338 -0
  31. package/src/service/AiAssetSyncService.ts +267 -0
  32. package/src/service/AssetParser.test.ts +261 -0
  33. package/src/service/AssetParser.ts +117 -0
  34. package/src/service/McpServerService.ts +464 -0
  35. package/src/types.ts +31 -0
  36. package/tsconfig.json +8 -0
@@ -0,0 +1 @@
1
+ {"version":3,"file":"router.cjs.js","sources":["../src/router.ts"],"sourcesContent":["import { randomUUID } from 'crypto';\nimport express from 'express';\nimport archiver from 'archiver';\nimport { StreamableHTTPServerTransport } from '@modelcontextprotocol/sdk/server/streamableHttp.js';\nimport type { LoggerService } from '@backstage/backend-plugin-api';\nimport type { AiAssetStore } from './database/AiAssetStore';\nimport type { AiAssetSyncService } from './service/AiAssetSyncService';\nimport { createMcpServer } from './service/McpServerService';\nimport type { ProviderConfig, AssetListFilter } from './types';\nimport type { AssetType } from '@nospt/plugin-dev-ai-hub-common';\n\ninterface RouterOptions {\n logger: LoggerService;\n store: AiAssetStore;\n syncService: AiAssetSyncService;\n providers: ProviderConfig[];\n}\n\nexport function createRouter(options: RouterOptions): express.Router {\n const { store, syncService, providers } = options;\n const router = express.Router();\n\n /** Active MCP sessions: sessionId → transport */\n const mcpSessions = new Map<string, StreamableHTTPServerTransport>();\n\n router.use(express.json());\n\n // ── Assets ────────────────────────────────────────────────────────────────\n\n router.get('/assets', async (req, res) => {\n try {\n const filter: AssetListFilter = {\n type: req.query.type as AssetType | undefined,\n tool: req.query.tool as string | undefined,\n providerId: req.query.provider as string | undefined,\n search: req.query.search as string | undefined,\n tags: req.query.tags\n ? (req.query.tags as string).split(',').filter(Boolean)\n : undefined,\n page: req.query.page ? Number(req.query.page) : 1,\n pageSize: req.query.pageSize ? Number(req.query.pageSize) : 20,\n };\n\n const { items, totalCount } = await store.listAssets(filter);\n\n res.json({\n items,\n totalCount,\n page: filter.page,\n pageSize: filter.pageSize,\n });\n } catch (err) {\n options.logger.error('GET /assets failed', err as Error);\n res.status(500).json({ error: 'Internal server error' });\n }\n });\n\n router.get('/assets/:id', async (req, res) => {\n try {\n const asset = await store.getAsset(req.params.id);\n if (!asset) return res.status(404).json({ error: 'Asset not found' });\n return res.json(asset);\n } catch (err) {\n options.logger.error('GET /assets/:id failed', err as Error);\n return res.status(500).json({ error: 'Internal server error' });\n }\n });\n\n /** Returns the pure markdown content of the asset */\n router.get('/assets/:id/raw', async (req, res) => {\n try {\n const asset = await store.getAsset(req.params.id);\n if (!asset) return res.status(404).json({ error: 'Asset not found' });\n res.setHeader('Content-Type', 'text/markdown; charset=utf-8');\n return res.send(asset.content);\n } catch (err) {\n options.logger.error('GET /assets/:id/raw failed', err as Error);\n return res.status(500).json({ error: 'Internal server error' });\n }\n });\n\n /**\n * For skills: returns a zip with the markdown + metadata about bundled resources.\n * For other types: returns the markdown as a text file.\n */\n router.get('/assets/:id/download', async (req, res) => {\n try {\n const asset = await store.getAsset(req.params.id);\n if (!asset) return res.status(404).json({ error: 'Asset not found' });\n\n const filename = `${asset.name.replace(/\\s+/g, '-').toLowerCase()}`;\n\n if (asset.type === 'skill') {\n res.setHeader('Content-Type', 'application/zip');\n res.setHeader(\n 'Content-Disposition',\n `attachment; filename=\"${filename}.zip\"`,\n );\n const archive = archiver('zip');\n archive.pipe(res);\n archive.append(asset.content, { name: 'SKILL.md' });\n if (asset.resourcesContent) {\n for (const [resourcePath, resourceContent] of Object.entries(asset.resourcesContent)) {\n archive.append(resourceContent, { name: resourcePath });\n }\n }\n await archive.finalize();\n } else {\n res.setHeader('Content-Type', 'text/markdown; charset=utf-8');\n res.setHeader(\n 'Content-Disposition',\n `attachment; filename=\"${filename}.md\"`,\n );\n return res.send(asset.content);\n }\n\n return undefined;\n } catch (err) {\n options.logger.error('GET /assets/:id/download failed', err as Error);\n return res.status(500).json({ error: 'Internal server error' });\n }\n });\n\n router.post('/assets/:id/track-install', async (req, res) => {\n try {\n const asset = await store.getAsset(req.params.id);\n if (!asset) return res.status(404).json({ error: 'Asset not found' });\n await store.incrementInstallCount(req.params.id);\n return res.status(204).send();\n } catch (err) {\n options.logger.error('POST /assets/:id/track-install failed', err as Error);\n return res.status(500).json({ error: 'Internal server error' });\n }\n });\n\n // ── Providers ─────────────────────────────────────────────────────────────\n\n router.get('/providers', async (_req, res) => {\n try {\n const statuses = await store.getAllSyncStatuses();\n const statusMap = new Map(statuses.map(s => [s.providerId, s]));\n\n const result = providers.map(p => {\n const status = statusMap.get(p.id);\n return {\n id: p.id,\n type: p.type,\n target: p.target,\n branch: p.branch,\n lastSync: status?.lastSync,\n lastCommit: status?.lastCommit,\n status: status?.status ?? 'idle',\n error: status?.error,\n assetCount: status?.assetCount ?? 0,\n };\n });\n\n res.json(result);\n } catch (err) {\n options.logger.error('GET /providers failed', err as Error);\n res.status(500).json({ error: 'Internal server error' });\n }\n });\n\n router.get('/providers/:id/status', async (req, res) => {\n try {\n const provider = providers.find(p => p.id === req.params.id);\n if (!provider) return res.status(404).json({ error: 'Provider not found' });\n\n const status = await store.getSyncStatus(req.params.id);\n return res.json(\n status ?? {\n providerId: req.params.id,\n status: 'idle',\n assetCount: 0,\n },\n );\n } catch (err) {\n options.logger.error('GET /providers/:id/status failed', err as Error);\n return res.status(500).json({ error: 'Internal server error' });\n }\n });\n\n router.post('/providers/:id/sync', async (req, res) => {\n try {\n const provider = providers.find(p => p.id === req.params.id);\n if (!provider) return res.status(404).json({ error: 'Provider not found' });\n\n // Fire and forget — respond immediately, sync runs in background\n setImmediate(() => syncService.syncProvider(provider));\n\n return res.json({ message: 'Sync triggered', providerId: provider.id });\n } catch (err) {\n options.logger.error('POST /providers/:id/sync failed', err as Error);\n return res.status(500).json({ error: 'Internal server error' });\n }\n });\n\n // ── Stats ─────────────────────────────────────────────────────────────────\n\n router.get('/stats', async (_req, res) => {\n try {\n const stats = await store.getStats();\n res.json(stats);\n } catch (err) {\n options.logger.error('GET /stats failed', err as Error);\n res.status(500).json({ error: 'Internal server error' });\n }\n });\n\n // ── MCP — StreamableHTTP transport ────────────────────────────────────────\n //\n // Clients connect to /mcp?tool=<ai-tool-name> using the MCP HTTP protocol.\n // The `tool` query parameter filters assets to those compatible with that\n // AI tool. Each session is stateful: the tool filter is captured at\n // initialization time and reused for the session lifetime.\n //\n // Client configuration example (.mcp.json / mcp_settings.json):\n //\n // {\n // \"mcpServers\": {\n // \"dev-ai-hub\": {\n // \"type\": \"http\",\n // \"url\": \"http://<backstage-host>:7007/api/dev-ai-hub/mcp?tool=claude-code\"\n // }\n // }\n // }\n //\n // Set AI_HUB_TOOL in your environment and substitute it into the URL, or\n // hard-code the tool name per workspace.\n\n /**\n * POST /mcp — Initialize a new MCP session or handle an existing one.\n * On first request (no mcp-session-id header), a new session is created\n * and its ID is returned in the response header.\n */\n router.post('/mcp', async (req, res) => {\n try {\n const sessionId = req.headers['mcp-session-id'] as string | undefined;\n\n if (sessionId) {\n const transport = mcpSessions.get(sessionId);\n if (!transport) {\n res.status(404).json({ error: 'MCP session not found' });\n return;\n }\n await transport.handleRequest(req, res, req.body);\n return;\n }\n\n // New session — extract filters from query params or headers\n const toolFilter =\n (req.query.tool as string | undefined) ??\n (req.headers['x-ai-hub-tool'] as string | undefined) ??\n '';\n const providerFilter =\n (req.query.provider as string | undefined) ??\n (req.headers['x-ai-hub-provider'] as string | undefined) ??\n '';\n // proactive defaults to false; pass ?proactive=true to enable check_for_assets + suggest_assets\n const proactiveEnabled =\n ((req.query.proactive as string | undefined) ??\n (req.headers['x-ai-hub-proactive'] as string | undefined) ??\n 'false') === 'true';\n\n const newSessionId = randomUUID();\n const transport = new StreamableHTTPServerTransport({\n sessionIdGenerator: () => newSessionId,\n onsessioninitialized: id => {\n mcpSessions.set(id, transport);\n options.logger.debug(\n `dev-ai-hub MCP: session ${id} opened (tool=\"${toolFilter || 'all'}\", provider=\"${providerFilter || 'all'}\", proactive=${proactiveEnabled})`,\n );\n },\n });\n\n transport.onclose = () => {\n mcpSessions.delete(newSessionId);\n options.logger.debug(`dev-ai-hub MCP: session ${newSessionId} closed`);\n };\n\n const server = createMcpServer(store, toolFilter, providerFilter, providers, proactiveEnabled);\n await server.connect(transport);\n\n await transport.handleRequest(req, res, req.body);\n } catch (err) {\n options.logger.error('POST /mcp failed', err as Error);\n if (!res.headersSent) res.status(500).json({ error: 'Internal server error' });\n }\n });\n\n /**\n * GET /mcp — SSE stream for server-to-client notifications on an existing session.\n */\n router.get('/mcp', async (req, res) => {\n try {\n const sessionId = req.headers['mcp-session-id'] as string | undefined;\n if (!sessionId) {\n res.status(400).json({ error: 'mcp-session-id header required' });\n return;\n }\n const transport = mcpSessions.get(sessionId);\n if (!transport) {\n res.status(404).json({ error: 'MCP session not found' });\n return;\n }\n await transport.handleRequest(req, res);\n } catch (err) {\n options.logger.error('GET /mcp failed', err as Error);\n if (!res.headersSent) res.status(500).json({ error: 'Internal server error' });\n }\n });\n\n /**\n * DELETE /mcp — Terminate an existing MCP session.\n */\n router.delete('/mcp', async (req, res) => {\n try {\n const sessionId = req.headers['mcp-session-id'] as string | undefined;\n if (!sessionId) {\n res.status(400).json({ error: 'mcp-session-id header required' });\n return;\n }\n const transport = mcpSessions.get(sessionId);\n if (!transport) {\n res.status(404).json({ error: 'MCP session not found' });\n return;\n }\n await transport.handleRequest(req, res);\n mcpSessions.delete(sessionId);\n } catch (err) {\n options.logger.error('DELETE /mcp failed', err as Error);\n if (!res.headersSent) res.status(500).json({ error: 'Internal server error' });\n }\n });\n\n return router;\n}\n"],"names":["express","archiver","transport","randomUUID","StreamableHTTPServerTransport","createMcpServer"],"mappings":"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"}
@@ -0,0 +1,210 @@
1
+ 'use strict';
2
+
3
+ var AssetParser = require('./AssetParser.cjs.js');
4
+
5
+ class AiAssetSyncService {
6
+ constructor(options) {
7
+ this.options = options;
8
+ }
9
+ async start() {
10
+ const { scheduler, logger, providers, externalProviders } = this.options;
11
+ if (providers.length === 0 && externalProviders.length === 0) {
12
+ logger.warn(
13
+ "dev-ai-hub: no providers configured under devAiHub.providers"
14
+ );
15
+ return;
16
+ }
17
+ for (const provider of providers) {
18
+ const { frequency, timeout } = provider.schedule;
19
+ const frequencyDuration = frequency.minutes ? { minutes: frequency.minutes } : { hours: frequency.hours ?? 1 };
20
+ const timeoutDuration = timeout.minutes ? { minutes: timeout.minutes } : { hours: timeout.hours ?? 1 };
21
+ await scheduler.scheduleTask({
22
+ id: `dev-ai-hub-sync-${provider.id}`,
23
+ frequency: frequencyDuration,
24
+ timeout: timeoutDuration,
25
+ initialDelay: { seconds: 15 },
26
+ fn: async () => {
27
+ await this.syncProvider(provider);
28
+ }
29
+ });
30
+ logger.info(
31
+ `dev-ai-hub: scheduled sync for provider "${provider.id}" every ${JSON.stringify(frequencyDuration)}`
32
+ );
33
+ }
34
+ for (const extProvider of externalProviders) {
35
+ await scheduler.scheduleTask({
36
+ id: `dev-ai-hub-sync-external-${extProvider.id}`,
37
+ frequency: { minutes: 60 },
38
+ timeout: { minutes: 5 },
39
+ initialDelay: { seconds: 15 },
40
+ fn: async () => {
41
+ await this.syncExternalProvider(extProvider);
42
+ }
43
+ });
44
+ logger.info(
45
+ `dev-ai-hub: scheduled sync for external provider "${extProvider.id}"`
46
+ );
47
+ }
48
+ }
49
+ async syncProvider(provider) {
50
+ const { logger, urlReader, store } = this.options;
51
+ logger.info(`dev-ai-hub: starting sync for provider "${provider.id}"`);
52
+ await store.upsertSyncStatus({
53
+ providerId: provider.id,
54
+ status: "syncing",
55
+ assetCount: 0
56
+ });
57
+ try {
58
+ const treeUrl = buildTreeUrl(provider);
59
+ const tree = await urlReader.readTree(treeUrl);
60
+ const files = await tree.files();
61
+ const fileMap = /* @__PURE__ */ new Map();
62
+ for (const file of files) {
63
+ fileMap.set(normalizePath(file.path), file);
64
+ }
65
+ const syncedIds = [];
66
+ for (const [filePath, file] of fileMap) {
67
+ if (!filePath.endsWith(".yaml")) continue;
68
+ if (!AssetParser.AssetParser.isAssetFile(filePath)) continue;
69
+ const yamlContent = (await file.content()).toString("utf-8");
70
+ const parsed = AssetParser.AssetParser.parseYaml(yamlContent, filePath);
71
+ if (!parsed) {
72
+ logger.warn(
73
+ `dev-ai-hub: skipping invalid YAML at ${filePath} (provider ${provider.id})`
74
+ );
75
+ continue;
76
+ }
77
+ if (provider.filters?.types && !provider.filters.types.includes(parsed.meta.type)) {
78
+ continue;
79
+ }
80
+ if (provider.filters?.tools) {
81
+ const hasMatchingTool = parsed.meta.tools.some(
82
+ (t) => provider.filters.tools.includes(t)
83
+ );
84
+ if (!hasMatchingTool) continue;
85
+ }
86
+ const mdFile = fileMap.get(normalizePath(parsed.mdPath));
87
+ if (!mdFile) {
88
+ logger.warn(
89
+ `dev-ai-hub: .md not found for ${filePath} (expected ${parsed.mdPath}), skipping`
90
+ );
91
+ continue;
92
+ }
93
+ const mdContent = (await mdFile.content()).toString("utf-8");
94
+ let resourcesContent;
95
+ const resourcePaths = parsed.meta.resources;
96
+ if (resourcePaths && resourcePaths.length > 0) {
97
+ resourcesContent = {};
98
+ const yamlDir = filePath.split("/").slice(0, -1).join("/");
99
+ for (const resourcePath of resourcePaths) {
100
+ const fullPath = normalizePath(
101
+ yamlDir ? `${yamlDir}/${resourcePath}` : resourcePath
102
+ );
103
+ const resourceFile = fileMap.get(fullPath);
104
+ if (resourceFile) {
105
+ resourcesContent[resourcePath] = (await resourceFile.content()).toString("utf-8");
106
+ } else {
107
+ logger.warn(
108
+ `dev-ai-hub: resource file not found: ${fullPath} (asset ${filePath})`
109
+ );
110
+ }
111
+ }
112
+ if (Object.keys(resourcesContent).length === 0) resourcesContent = void 0;
113
+ }
114
+ const asset = AssetParser.AssetParser.buildAsset(
115
+ parsed,
116
+ mdContent,
117
+ provider.id,
118
+ provider.target,
119
+ provider.branch,
120
+ filePath,
121
+ resourcesContent
122
+ );
123
+ await store.upsertAsset(asset);
124
+ syncedIds.push(asset.id);
125
+ }
126
+ await store.deleteAssetsNotIn(provider.id, syncedIds);
127
+ await store.upsertSyncStatus({
128
+ providerId: provider.id,
129
+ lastSync: (/* @__PURE__ */ new Date()).toISOString(),
130
+ status: "idle",
131
+ assetCount: syncedIds.length
132
+ });
133
+ logger.info(
134
+ `dev-ai-hub: sync complete for provider "${provider.id}" \u2014 ${syncedIds.length} assets`
135
+ );
136
+ } catch (err) {
137
+ const message = err instanceof Error ? err.message : String(err);
138
+ this.options.logger.error(
139
+ `dev-ai-hub: sync failed for provider "${provider.id}": ${message}`
140
+ );
141
+ await store.upsertSyncStatus({
142
+ providerId: provider.id,
143
+ status: "error",
144
+ error: message,
145
+ assetCount: 0
146
+ });
147
+ }
148
+ }
149
+ async syncExternalProvider(provider) {
150
+ const { logger, store } = this.options;
151
+ logger.info(`dev-ai-hub: syncing external provider "${provider.id}"`);
152
+ await store.upsertSyncStatus({
153
+ providerId: provider.id,
154
+ status: "syncing",
155
+ assetCount: 0
156
+ });
157
+ try {
158
+ const assets = await provider.getAssets();
159
+ const syncedIds = [];
160
+ for (const assetData of assets) {
161
+ const id = AssetParser.AssetParser.buildId(provider.id, assetData.yamlPath);
162
+ await store.upsertAsset({ ...assetData, id });
163
+ syncedIds.push(id);
164
+ }
165
+ await store.deleteAssetsNotIn(provider.id, syncedIds);
166
+ await store.upsertSyncStatus({
167
+ providerId: provider.id,
168
+ lastSync: (/* @__PURE__ */ new Date()).toISOString(),
169
+ status: "idle",
170
+ assetCount: syncedIds.length
171
+ });
172
+ logger.info(
173
+ `dev-ai-hub: external sync complete for "${provider.id}" \u2014 ${syncedIds.length} assets`
174
+ );
175
+ } catch (err) {
176
+ const message = err instanceof Error ? err.message : String(err);
177
+ logger.error(
178
+ `dev-ai-hub: external sync failed for "${provider.id}": ${message}`
179
+ );
180
+ await store.upsertSyncStatus({
181
+ providerId: provider.id,
182
+ status: "error",
183
+ error: message,
184
+ assetCount: 0
185
+ });
186
+ }
187
+ }
188
+ }
189
+ function normalizePath(p) {
190
+ return p.replace(/\\/g, "/").replace(/^\//, "");
191
+ }
192
+ function buildTreeUrl(provider) {
193
+ const base = provider.target.replace(/\.git$/, "");
194
+ const { branch, type } = provider;
195
+ switch (type) {
196
+ case "gitlab":
197
+ return `${base}/-/tree/${branch}/`;
198
+ case "bitbucket":
199
+ return `${base}/src/${branch}/`;
200
+ case "azure-devops":
201
+ return `${base}?version=GB${branch}`;
202
+ case "github":
203
+ case "git":
204
+ default:
205
+ return `${base}/tree/${branch}/`;
206
+ }
207
+ }
208
+
209
+ exports.AiAssetSyncService = AiAssetSyncService;
210
+ //# sourceMappingURL=AiAssetSyncService.cjs.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"AiAssetSyncService.cjs.js","sources":["../../src/service/AiAssetSyncService.ts"],"sourcesContent":["import type {\n LoggerService,\n SchedulerService,\n UrlReaderService,\n} from '@backstage/backend-plugin-api';\nimport type { AiAssetStore } from '../database/AiAssetStore';\nimport type { ProviderConfig } from '../types';\nimport type { AiAssetProvider } from '@nospt/plugin-dev-ai-hub-node';\nimport { AssetParser } from './AssetParser';\n\ninterface Options {\n logger: LoggerService;\n store: AiAssetStore;\n scheduler: SchedulerService;\n urlReader: UrlReaderService;\n providers: ProviderConfig[];\n externalProviders: AiAssetProvider[];\n}\n\nexport class AiAssetSyncService {\n constructor(private readonly options: Options) {}\n\n async start(): Promise<void> {\n const { scheduler, logger, providers, externalProviders } = this.options;\n\n if (providers.length === 0 && externalProviders.length === 0) {\n logger.warn(\n 'dev-ai-hub: no providers configured under devAiHub.providers',\n );\n return;\n }\n\n for (const provider of providers) {\n const { frequency, timeout } = provider.schedule;\n const frequencyDuration = frequency.minutes\n ? { minutes: frequency.minutes }\n : { hours: frequency.hours ?? 1 };\n const timeoutDuration = timeout.minutes\n ? { minutes: timeout.minutes }\n : { hours: timeout.hours ?? 1 };\n\n await scheduler.scheduleTask({\n id: `dev-ai-hub-sync-${provider.id}`,\n frequency: frequencyDuration,\n timeout: timeoutDuration,\n initialDelay: { seconds: 15 },\n fn: async () => {\n await this.syncProvider(provider);\n },\n });\n\n logger.info(\n `dev-ai-hub: scheduled sync for provider \"${provider.id}\" every ${JSON.stringify(frequencyDuration)}`,\n );\n }\n\n for (const extProvider of externalProviders) {\n await scheduler.scheduleTask({\n id: `dev-ai-hub-sync-external-${extProvider.id}`,\n frequency: { minutes: 60 },\n timeout: { minutes: 5 },\n initialDelay: { seconds: 15 },\n fn: async () => {\n await this.syncExternalProvider(extProvider);\n },\n });\n\n logger.info(\n `dev-ai-hub: scheduled sync for external provider \"${extProvider.id}\"`,\n );\n }\n }\n\n async syncProvider(provider: ProviderConfig): Promise<void> {\n const { logger, urlReader, store } = this.options;\n\n logger.info(`dev-ai-hub: starting sync for provider \"${provider.id}\"`);\n\n await store.upsertSyncStatus({\n providerId: provider.id,\n status: 'syncing',\n assetCount: 0,\n });\n\n try {\n const treeUrl = buildTreeUrl(provider);\n const tree = await urlReader.readTree(treeUrl);\n const files = await tree.files();\n\n const fileMap = new Map<\n string,\n { content(): Promise<Buffer>; path: string }\n >();\n for (const file of files) {\n fileMap.set(normalizePath(file.path), file);\n }\n\n const syncedIds: string[] = [];\n\n for (const [filePath, file] of fileMap) {\n if (!filePath.endsWith('.yaml')) continue;\n if (!AssetParser.isAssetFile(filePath)) continue;\n\n const yamlContent = (await file.content()).toString('utf-8');\n const parsed = AssetParser.parseYaml(yamlContent, filePath);\n\n if (!parsed) {\n logger.warn(\n `dev-ai-hub: skipping invalid YAML at ${filePath} (provider ${provider.id})`,\n );\n continue;\n }\n\n if (\n provider.filters?.types &&\n !provider.filters.types.includes(parsed.meta.type)\n ) {\n continue;\n }\n if (provider.filters?.tools) {\n const hasMatchingTool = parsed.meta.tools.some(t =>\n provider.filters!.tools!.includes(t),\n );\n if (!hasMatchingTool) continue;\n }\n\n const mdFile = fileMap.get(normalizePath(parsed.mdPath));\n if (!mdFile) {\n logger.warn(\n `dev-ai-hub: .md not found for ${filePath} (expected ${parsed.mdPath}), skipping`,\n );\n continue;\n }\n\n const mdContent = (await mdFile.content()).toString('utf-8');\n\n // For skills: read bundled resource files and store their content\n let resourcesContent: Record<string, string> | undefined;\n const resourcePaths = parsed.meta.resources;\n if (resourcePaths && resourcePaths.length > 0) {\n resourcesContent = {};\n const yamlDir = filePath.split('/').slice(0, -1).join('/');\n for (const resourcePath of resourcePaths) {\n const fullPath = normalizePath(\n yamlDir ? `${yamlDir}/${resourcePath}` : resourcePath,\n );\n const resourceFile = fileMap.get(fullPath);\n if (resourceFile) {\n resourcesContent[resourcePath] = (await resourceFile.content()).toString('utf-8');\n } else {\n logger.warn(\n `dev-ai-hub: resource file not found: ${fullPath} (asset ${filePath})`,\n );\n }\n }\n if (Object.keys(resourcesContent).length === 0) resourcesContent = undefined;\n }\n\n const asset = AssetParser.buildAsset(\n parsed,\n mdContent,\n provider.id,\n provider.target,\n provider.branch,\n filePath,\n resourcesContent,\n );\n\n await store.upsertAsset(asset);\n syncedIds.push(asset.id);\n }\n\n await store.deleteAssetsNotIn(provider.id, syncedIds);\n\n await store.upsertSyncStatus({\n providerId: provider.id,\n lastSync: new Date().toISOString(),\n status: 'idle',\n assetCount: syncedIds.length,\n });\n\n logger.info(\n `dev-ai-hub: sync complete for provider \"${provider.id}\" — ${syncedIds.length} assets`,\n );\n } catch (err) {\n const message = err instanceof Error ? err.message : String(err);\n this.options.logger.error(\n `dev-ai-hub: sync failed for provider \"${provider.id}\": ${message}`,\n );\n await store.upsertSyncStatus({\n providerId: provider.id,\n status: 'error',\n error: message,\n assetCount: 0,\n });\n }\n }\n\n private async syncExternalProvider(provider: AiAssetProvider): Promise<void> {\n const { logger, store } = this.options;\n\n logger.info(`dev-ai-hub: syncing external provider \"${provider.id}\"`);\n\n await store.upsertSyncStatus({\n providerId: provider.id,\n status: 'syncing',\n assetCount: 0,\n });\n\n try {\n const assets = await provider.getAssets();\n const syncedIds: string[] = [];\n\n for (const assetData of assets) {\n const id = AssetParser.buildId(provider.id, assetData.yamlPath);\n await store.upsertAsset({ ...assetData, id });\n syncedIds.push(id);\n }\n\n await store.deleteAssetsNotIn(provider.id, syncedIds);\n\n await store.upsertSyncStatus({\n providerId: provider.id,\n lastSync: new Date().toISOString(),\n status: 'idle',\n assetCount: syncedIds.length,\n });\n\n logger.info(\n `dev-ai-hub: external sync complete for \"${provider.id}\" — ${syncedIds.length} assets`,\n );\n } catch (err) {\n const message = err instanceof Error ? err.message : String(err);\n logger.error(\n `dev-ai-hub: external sync failed for \"${provider.id}\": ${message}`,\n );\n await store.upsertSyncStatus({\n providerId: provider.id,\n status: 'error',\n error: message,\n assetCount: 0,\n });\n }\n }\n}\n\nfunction normalizePath(p: string): string {\n return p.replace(/\\\\/g, '/').replace(/^\\//, '');\n}\n\nfunction buildTreeUrl(provider: ProviderConfig): string {\n const base = provider.target.replace(/\\.git$/, '');\n const { branch, type } = provider;\n\n switch (type) {\n case 'gitlab':\n return `${base}/-/tree/${branch}/`;\n case 'bitbucket':\n return `${base}/src/${branch}/`;\n case 'azure-devops':\n return `${base}?version=GB${branch}`;\n case 'github':\n case 'git':\n default:\n return `${base}/tree/${branch}/`;\n }\n}\n"],"names":["AssetParser"],"mappings":"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"}
@@ -0,0 +1,91 @@
1
+ 'use strict';
2
+
3
+ var yaml = require('js-yaml');
4
+ var path = require('path');
5
+ var pluginDevAiHubCommon = require('@nospt/plugin-dev-ai-hub-common');
6
+
7
+ function _interopDefaultCompat (e) { return e && typeof e === 'object' && 'default' in e ? e : { default: e }; }
8
+
9
+ var yaml__default = /*#__PURE__*/_interopDefaultCompat(yaml);
10
+ var path__default = /*#__PURE__*/_interopDefaultCompat(path);
11
+
12
+ class AssetParser {
13
+ /**
14
+ * Parse the YAML metadata file (the envelope).
15
+ * Returns null when the YAML is invalid or missing required fields.
16
+ */
17
+ static parseYaml(yamlContent, yamlFilePath) {
18
+ let data;
19
+ try {
20
+ data = yaml__default.default.load(yamlContent);
21
+ } catch {
22
+ return null;
23
+ }
24
+ const result = pluginDevAiHubCommon.AiAssetFrontmatterSchema.safeParse(data);
25
+ if (!result.success) {
26
+ return null;
27
+ }
28
+ const meta = result.data;
29
+ const mdReference = meta.content ?? `${path__default.default.basename(yamlFilePath, ".yaml")}.md`;
30
+ const mdPath = path__default.default.posix.join(
31
+ path__default.default.posix.dirname(yamlFilePath),
32
+ mdReference
33
+ );
34
+ return { meta, mdPath, yamlRaw: yamlContent };
35
+ }
36
+ /**
37
+ * Build the full AiAssetInput from parsed metadata + raw markdown content.
38
+ * The mdContent is stored verbatim — never modified.
39
+ */
40
+ static buildAsset(parsed, mdContent, providerId, repoUrl, branch, yamlFilePath, resourcesContent) {
41
+ const { meta } = parsed;
42
+ const metadata = {};
43
+ if (meta.resources) metadata.resources = meta.resources;
44
+ if (meta.mcpServers) metadata.mcpServers = meta.mcpServers;
45
+ if (meta.steps) metadata.steps = meta.steps;
46
+ return {
47
+ id: AssetParser.buildId(providerId, yamlFilePath),
48
+ providerId,
49
+ name: meta.name,
50
+ label: meta.label,
51
+ description: meta.description,
52
+ type: meta.type,
53
+ tools: meta.tools,
54
+ tags: meta.tags ?? [],
55
+ author: meta.author ?? "Unknown",
56
+ icon: meta.icon,
57
+ version: meta.version ?? "1.0.0",
58
+ installPath: meta.installPath,
59
+ installPaths: meta.installPaths,
60
+ content: mdContent,
61
+ yamlRaw: parsed.yamlRaw,
62
+ metadata: Object.keys(metadata).length > 0 ? metadata : void 0,
63
+ resourcesContent,
64
+ yamlPath: yamlFilePath,
65
+ mdPath: parsed.mdPath,
66
+ repoUrl,
67
+ branch
68
+ };
69
+ }
70
+ static buildId(providerId, yamlPath) {
71
+ const normalized = yamlPath.replace(/\\/g, "/").replace(/^\//, "");
72
+ return Buffer.from(`${providerId}:${normalized}`).toString("base64url");
73
+ }
74
+ /** Known top-level directories that contain assets */
75
+ static ASSET_DIRS = [
76
+ "instructions/",
77
+ "agents/",
78
+ "skills/",
79
+ "workflows/"
80
+ ];
81
+ /** True if the file is in a known asset directory (root or .github/) */
82
+ static isAssetFile(filePath) {
83
+ const normalized = filePath.replace(/\\/g, "/");
84
+ return AssetParser.ASSET_DIRS.some(
85
+ (dir) => normalized.startsWith(dir) || normalized.startsWith(`.github/${dir}`)
86
+ );
87
+ }
88
+ }
89
+
90
+ exports.AssetParser = AssetParser;
91
+ //# sourceMappingURL=AssetParser.cjs.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"AssetParser.cjs.js","sources":["../../src/service/AssetParser.ts"],"sourcesContent":["import yaml from 'js-yaml';\nimport path from 'path';\nimport { AiAssetFrontmatterSchema } from '@nospt/plugin-dev-ai-hub-common';\nimport type { AiAssetFrontmatter } from '@nospt/plugin-dev-ai-hub-common';\nimport type { AiAssetInput } from '../types';\n\nexport interface ParsedAssetMeta {\n meta: AiAssetFrontmatter;\n /** Resolved path of the .md content file within the repo tree */\n mdPath: string;\n yamlRaw: string;\n}\n\nexport class AssetParser {\n /**\n * Parse the YAML metadata file (the envelope).\n * Returns null when the YAML is invalid or missing required fields.\n */\n static parseYaml(\n yamlContent: string,\n yamlFilePath: string,\n ): ParsedAssetMeta | null {\n let data: unknown;\n try {\n data = yaml.load(yamlContent);\n } catch {\n return null;\n }\n\n const result = AiAssetFrontmatterSchema.safeParse(data);\n if (!result.success) {\n return null;\n }\n\n const meta = result.data;\n\n // Resolve .md path: use the `content` field, or fall back to <same-name>.md\n const mdReference =\n meta.content ?? `${path.basename(yamlFilePath, '.yaml')}.md`;\n const mdPath = path.posix.join(\n path.posix.dirname(yamlFilePath),\n mdReference,\n );\n\n return { meta, mdPath, yamlRaw: yamlContent };\n }\n\n /**\n * Build the full AiAssetInput from parsed metadata + raw markdown content.\n * The mdContent is stored verbatim — never modified.\n */\n static buildAsset(\n parsed: ParsedAssetMeta,\n mdContent: string,\n providerId: string,\n repoUrl: string,\n branch: string,\n yamlFilePath: string,\n resourcesContent?: Record<string, string>,\n ): AiAssetInput {\n const { meta } = parsed;\n\n // Store extra fields in metadata (used for display/reference)\n const metadata: Record<string, unknown> = {};\n if (meta.resources) metadata.resources = meta.resources;\n if ((meta as any).mcpServers) metadata.mcpServers = (meta as any).mcpServers;\n if ((meta as any).steps) metadata.steps = (meta as any).steps;\n\n return {\n id: AssetParser.buildId(providerId, yamlFilePath),\n providerId,\n name: meta.name,\n label: meta.label,\n description: meta.description,\n type: meta.type,\n tools: meta.tools,\n tags: meta.tags ?? [],\n author: meta.author ?? 'Unknown',\n icon: meta.icon,\n version: meta.version ?? '1.0.0',\n installPath: meta.installPath,\n installPaths: meta.installPaths,\n content: mdContent,\n yamlRaw: parsed.yamlRaw,\n metadata: Object.keys(metadata).length > 0 ? metadata : undefined,\n resourcesContent,\n yamlPath: yamlFilePath,\n mdPath: parsed.mdPath,\n repoUrl,\n branch,\n };\n }\n\n static buildId(providerId: string, yamlPath: string): string {\n // Simple deterministic ID: provider + normalized path\n const normalized = yamlPath.replace(/\\\\/g, '/').replace(/^\\//, '');\n return Buffer.from(`${providerId}:${normalized}`).toString('base64url');\n }\n\n /** Known top-level directories that contain assets */\n private static readonly ASSET_DIRS = [\n 'instructions/',\n 'agents/',\n 'skills/',\n 'workflows/',\n ];\n\n /** True if the file is in a known asset directory (root or .github/) */\n static isAssetFile(filePath: string): boolean {\n const normalized = filePath.replace(/\\\\/g, '/');\n return AssetParser.ASSET_DIRS.some(\n dir =>\n normalized.startsWith(dir) ||\n normalized.startsWith(`.github/${dir}`),\n );\n }\n}\n"],"names":["yaml","AiAssetFrontmatterSchema","path"],"mappings":"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"}