{"version":3,"file":"resources-cli.d.ts","sourceRoot":"","sources":["../src/resources-cli.ts"],"names":[],"mappings":"AAuJA,wBAAsB,sBAAsB,CAAC,IAAI,EAAE,MAAM,EAAE,GAAG,OAAO,CAAC,OAAO,CAAC,CAoD7E","sourcesContent":["import { existsSync, readdirSync, readFileSync } from \"node:fs\";\nimport { homedir } from \"node:os\";\nimport { join } from \"node:path\";\nimport chalk from \"chalk\";\nimport { APP_NAME, CONFIG_DIR_NAME, getAgentDir, getHooCodeDir } from \"./config.js\";\nimport { loadAgentRegistry } from \"./core/agent-registry.js\";\nimport { DefaultResourceLoader } from \"./core/resource-loader.js\";\nimport { SettingsManager } from \"./core/settings-manager.js\";\nimport type { SourceInfo } from \"./core/source-info.js\";\n\ninterface ResourceRow {\n\tname: string;\n\tdetail?: string;\n\torigin: string;\n\tpath: string;\n}\n\n/**\n * Derive a short origin label from a resource path and scope. Distinguishes the\n * four discovery surfaces a user cares about when debugging: project vs user, and\n * the cross-vendor `.agents/` vs Claude Code `.claude/` directories.\n */\nfunction classifyOrigin(path: string, scope: string): string {\n\tif (path.includes(`${join(\"\", \".agents\", \"\")}`) || path.includes(\"/.agents/\")) return `${scope} (.agents)`;\n\tif (path.includes(\"/.claude/\")) return `${scope} (.claude)`;\n\tif (path.includes(`${CONFIG_DIR_NAME}/`)) return `${scope} (${CONFIG_DIR_NAME})`;\n\treturn scope;\n}\n\nfunction originFromSourceInfo(sourceInfo: SourceInfo): string {\n\treturn classifyOrigin(sourceInfo.path, sourceInfo.scope);\n}\n\n/** Map an AgentSource value to the same origin vocabulary used elsewhere. */\nfunction originFromAgentSource(source: string, path: string | undefined): string {\n\tswitch (source) {\n\t\tcase \"builtin\":\n\t\t\treturn \"builtin\";\n\t\tcase \"user\":\n\t\t\treturn path ? classifyOrigin(path, \"user\") : \"user\";\n\t\tcase \"project\":\n\t\t\treturn path ? classifyOrigin(path, \"project\") : \"project\";\n\t\tcase \"claude-user\":\n\t\t\treturn \"user (.claude)\";\n\t\tcase \"claude-project\":\n\t\t\treturn \"project (.claude)\";\n\t\tdefault:\n\t\t\treturn source;\n\t}\n}\n\ninterface DiscoveredMcpServer {\n\tname: string;\n\tpath: string;\n\tscope: \"user\" | \"project\";\n}\n\n/**\n * Read-only replica of the MCP discovery surfaces in\n * `extensions/core/mcp-loader.ts` (setupMcpLoader). First-wins by name across all\n * sources. Kept in sync with that loader; it is the source of truth.\n */\nfunction discoverMcpServers(cwd: string): DiscoveredMcpServer[] {\n\tconst servers: DiscoveredMcpServer[] = [];\n\tconst seen = new Set<string>();\n\tconst add = (name: string, path: string, scope: \"user\" | \"project\"): void => {\n\t\tif (!name || seen.has(name)) return;\n\t\tseen.add(name);\n\t\tservers.push({ name, path, scope });\n\t};\n\n\tconst standardFiles: Array<{ path: string; scope: \"user\" | \"project\" }> = [\n\t\t{ path: join(homedir(), \".agents\", \"mcp.json\"), scope: \"user\" },\n\t\t{ path: join(cwd, \".agents\", \"mcp.json\"), scope: \"project\" },\n\t\t{ path: join(homedir(), \".config\", \"claude\", \"mcp.json\"), scope: \"user\" },\n\t];\n\tfor (const { path, scope } of standardFiles) {\n\t\tif (!existsSync(path)) continue;\n\t\ttry {\n\t\t\tconst config = JSON.parse(readFileSync(path, \"utf8\")) as { mcpServers?: Record<string, unknown> };\n\t\t\tfor (const name of Object.keys(config.mcpServers ?? {})) {\n\t\t\t\tadd(name, path, scope);\n\t\t\t}\n\t\t} catch {\n\t\t\t// Ignore malformed config; the loader does the same.\n\t\t}\n\t}\n\n\tconst perServerDirs: Array<{ dir: string; scope: \"user\" | \"project\" }> = [\n\t\t{ dir: join(getHooCodeDir(), \"mcp-servers\"), scope: \"user\" },\n\t\t{ dir: join(cwd, CONFIG_DIR_NAME, \"mcp-servers\"), scope: \"project\" },\n\t];\n\tfor (const { dir, scope } of perServerDirs) {\n\t\tif (!existsSync(dir)) continue;\n\t\tlet files: string[];\n\t\ttry {\n\t\t\tfiles = readdirSync(dir).filter((f) => f.endsWith(\".json\"));\n\t\t} catch {\n\t\t\tcontinue;\n\t\t}\n\t\tfor (const file of files) {\n\t\t\tconst path = join(dir, file);\n\t\t\ttry {\n\t\t\t\tconst config = JSON.parse(readFileSync(path, \"utf8\")) as { name?: string };\n\t\t\t\tif (config.name) add(config.name, path, scope);\n\t\t\t} catch {\n\t\t\t\t// Ignore malformed config.\n\t\t\t}\n\t\t}\n\t}\n\n\treturn servers;\n}\n\n/** Collapse a description to a single short line for the table. */\nfunction oneLine(text: string | undefined): string | undefined {\n\tif (!text) return undefined;\n\tconst firstLine = text\n\t\t.split(\"\\n\")\n\t\t.map((l) => l.trim())\n\t\t.find((l) => l.length > 0);\n\tif (!firstLine) return undefined;\n\treturn firstLine.length > 80 ? `${firstLine.slice(0, 77)}...` : firstLine;\n}\n\nfunction printSection(title: string, rows: ResourceRow[]): void {\n\tconsole.log(chalk.bold(`${title} (${rows.length})`));\n\tif (rows.length === 0) {\n\t\tconsole.log(chalk.dim(\"  (none)\"));\n\t\tconsole.log();\n\t\treturn;\n\t}\n\tfor (const row of rows) {\n\t\tconst summary = oneLine(row.detail);\n\t\tconst detail = summary ? chalk.dim(` - ${summary}`) : \"\";\n\t\tconsole.log(`  ${row.name}${detail}  ${chalk.dim(`[${row.origin}]`)}`);\n\t\tconsole.log(chalk.dim(`    ${row.path}`));\n\t}\n\tconsole.log();\n}\n\nfunction printHelp(): void {\n\tconsole.log(`${chalk.bold(\"Usage:\")}\n  ${APP_NAME} resources\n\nPrint the skills, subagents, slash commands, and MCP servers discovered for the\ncurrent working directory, with their source path and origin. Read-only; makes\nno LLM call.\n`);\n}\n\nexport async function handleResourcesCommand(args: string[]): Promise<boolean> {\n\tif (args[0] !== \"resources\") {\n\t\treturn false;\n\t}\n\n\tif (args.includes(\"-h\") || args.includes(\"--help\")) {\n\t\tprintHelp();\n\t\treturn true;\n\t}\n\n\tconst cwd = process.cwd();\n\tconst agentDir = getAgentDir();\n\tconst settingsManager = SettingsManager.create(cwd, agentDir);\n\tconst resourceLoader = new DefaultResourceLoader({ cwd, agentDir, settingsManager });\n\tawait resourceLoader.reload();\n\n\tconst skills = resourceLoader.getSkills().skills.map<ResourceRow>((skill) => ({\n\t\tname: skill.name,\n\t\tdetail: skill.description,\n\t\torigin: originFromSourceInfo(skill.sourceInfo),\n\t\tpath: skill.sourceInfo.path,\n\t}));\n\n\tconst agents = loadAgentRegistry({ cwd, agentDir })\n\t\t.list()\n\t\t.map<ResourceRow>((agent) => ({\n\t\t\tname: agent.name,\n\t\t\tdetail: agent.description,\n\t\t\torigin: originFromAgentSource(agent.source, agent.filePath),\n\t\t\tpath: agent.filePath ?? \"<builtin>\",\n\t\t}));\n\n\tconst slashCommands = resourceLoader.getPrompts().prompts.map<ResourceRow>((prompt) => ({\n\t\tname: `/${prompt.name}`,\n\t\tdetail: prompt.description,\n\t\torigin: originFromSourceInfo(prompt.sourceInfo),\n\t\tpath: prompt.sourceInfo.path,\n\t}));\n\n\tconst mcpServers = discoverMcpServers(cwd).map<ResourceRow>((server) => ({\n\t\tname: server.name,\n\t\torigin: classifyOrigin(server.path, server.scope),\n\t\tpath: server.path,\n\t}));\n\n\tconsole.log(chalk.dim(`Resources discovered for ${cwd}\\n`));\n\tprintSection(\"Skills\", skills);\n\tprintSection(\"Subagents\", agents);\n\tprintSection(\"Slash commands\", slashCommands);\n\tprintSection(\"MCP servers\", mcpServers);\n\n\treturn true;\n}\n"]}