Skip to main content
Glama
prompts.ts6.04 kB
import fs from "node:fs"; import path from "node:path"; import { fileURLToPath } from "node:url"; import type { PromptDescriptor, PromptFlavor } from "../types.js"; import { inferLlmMetadata, inferVariantFromFilename, } from "../lib/llmMetadata.js"; import { slugifyWithLimit, toTitleCase } from "../lib/slug.js"; /** * @title Prompt Discovery Constants * @notice Extension and filename filters used during filesystem scanning. */ const TEXT_EXTENSIONS = new Set([".txt", ".md", ".yaml", ".yml", ".json"]); const SUMMARY_FILENAMES = new Set(["summary.md"]); const IGNORED_FILES = new Set(["readme.md", "view-thread-yaml.png"]); /** * @title Resolve Prompt Root * @notice Chooses the directory that should be scanned for prompts. */ function resolveRoot(): string { const explicit = process.env.PROMPT_LIBRARY_ROOT; if (explicit) { return path.resolve(explicit); } const currentFile = fileURLToPath(import.meta.url); const repoRoot = path.resolve(path.dirname(currentFile), "../../.."); const promptsDir = path.join(repoRoot, "prompts"); if (fs.existsSync(promptsDir) && fs.statSync(promptsDir).isDirectory()) { return promptsDir; } return repoRoot; } /** * @title Classify Flavor * @notice Determines whether a file is a summary, system prompt, or tool definition. */ function classifyFlavor(filename: string): PromptFlavor { const lower = filename.toLowerCase(); if (SUMMARY_FILENAMES.has(lower)) { return "summary"; } if (lower.includes("tool") || lower.endsWith(".json")) { return "tools"; } return "system"; } /** * @title Create Prompt Descriptor * @notice Builds metadata for a single prompt file encountered during traversal. */ function createPromptDescriptor( serviceSegments: string[], absolutePath: string, flavor: PromptFlavor, ): PromptDescriptor | undefined { if (serviceSegments.length === 0) { return undefined; } const filename = path.basename(absolutePath); const ext = path.extname(filename).toLowerCase(); if (!TEXT_EXTENSIONS.has(ext)) { return undefined; } if (IGNORED_FILES.has(filename.toLowerCase())) { return undefined; } const variant = inferVariantFromFilename(filename, flavor); const [service, ...variantPrefixes] = serviceSegments; if (!service) { return undefined; } const serviceTitle = toTitleCase(service); const prefixTitle = variantPrefixes .map(toTitleCase) .filter(Boolean) .join(" "); const variantTitle = prefixTitle && variant.toLowerCase() !== "summary" ? `${prefixTitle} ${toTitleCase(variant)}` : prefixTitle && variant.toLowerCase() === "summary" ? `${prefixTitle} Summary` : toTitleCase(variant); const llmMetadata = inferLlmMetadata(service, filename, flavor); const flavorLabel = flavor === "system" ? "System Prompt" : flavor === "summary" ? "Summary" : "Tooling"; const titleParts = [ serviceTitle, variant === "summary" && !prefixTitle ? "" : variantTitle, flavorLabel, ].filter(Boolean); const title = titleParts.join(" ").trim(); const slugSegments = new Set<string>(); slugSegments.add(serviceTitle); if ( !( flavor === "summary" && variant.toLowerCase() === "summary" && !prefixTitle ) ) { slugSegments.add(variantTitle); } slugSegments.add(flavor === "system" ? "system" : flavor); const slugBase = Array.from(slugSegments).filter(Boolean).join("-"); const toolName = slugifyWithLimit(slugBase, absolutePath, 60); const descriptionSegments = [ `Return the ${flavorLabel.toLowerCase()} for ${serviceTitle}${ variantTitle && (!prefixTitle || variant.toLowerCase() !== "summary") ? ` (${variantTitle})` : "" }.`, `Source: ${absolutePath}`, ]; if (llmMetadata) { descriptionSegments.push( `Optimized for ${llmMetadata.provider} (${llmMetadata.modelHint ?? llmMetadata.family}).`, ); } else { descriptionSegments.push("Applies broadly across models."); } return { id: toolName, service: serviceTitle, flavor, variant, path: absolutePath, toolName, title, description: descriptionSegments.join(" "), ...(llmMetadata ? { llm: llmMetadata } : {}), }; } /** * @title Discover Prompt Descriptors * @notice Recursively walks the prompt root and returns metadata for each prompt file. */ export function discoverPromptDescriptors( rootDir?: string, ): PromptDescriptor[] { const resolvedRoot = rootDir ? path.resolve(rootDir) : resolveRoot(); const descriptors: PromptDescriptor[] = []; const queue: string[] = [resolvedRoot]; while (queue.length > 0) { const currentDir = queue.pop(); if (!currentDir) { continue; } const entries = fs.readdirSync(currentDir, { withFileTypes: true }); for (const entry of entries) { if (entry.name.startsWith(".")) { continue; } const absolutePath = path.join(currentDir, entry.name); if (entry.isDirectory()) { queue.push(absolutePath); continue; } const relativePath = path.relative(resolvedRoot, absolutePath); const segments = relativePath.split(path.sep).filter(Boolean); if (segments.length === 0) { continue; } const directories = segments.slice(0, -1); const filename = segments[segments.length - 1]!; const fallbackBase = path.basename(filename, path.extname(filename)); const fallbackService = fallbackBase.length > 0 ? fallbackBase : path.basename(currentDir); const serviceSegments: string[] = directories.length > 0 ? [...directories] : [fallbackService]; const flavor = classifyFlavor(filename); const descriptor = createPromptDescriptor( serviceSegments, absolutePath, flavor, ); if (descriptor) { descriptors.push(descriptor); } } } return descriptors.sort((a, b) => a.toolName.localeCompare(b.toolName)); }

Latest Blog Posts

MCP directory API

We provide all the information about MCP servers via our MCP API.

curl -X GET 'https://glama.ai/api/mcp/v1/servers/JamesANZ/system-prompts-mcp-server'

If you have feedback or need assistance with the MCP directory API, please join our Discord server