UNPKG

@nanocollective/nanocoder

Version:

A local-first CLI coding agent that brings the power of agentic coding tools like Claude Code and Gemini CLI to local models or controlled APIs like OpenRouter

125 lines 7.63 kB
import { jsx as _jsx, jsxs as _jsxs, Fragment as _Fragment } from "react/jsx-runtime"; import { Box, Text } from 'ink'; import React from 'react'; import { InfoMessage } from '../components/message-box.js'; import { TitledBoxWithPreferences } from '../components/ui/titled-box.js'; import { CustomCommandLoader } from '../custom-commands/loader.js'; import { useTheme } from '../hooks/useTheme.js'; function formatCommand(cmd) { const parts = [`/${cmd.fullName}`]; if (cmd.metadata.parameters && cmd.metadata.parameters.length > 0) { parts.push(cmd.metadata.parameters.map((p) => `<${p}>`).join(' ')); } if (cmd.metadata.description) { parts.push(`- ${cmd.metadata.description}`); } if (cmd.metadata.aliases && cmd.metadata.aliases.length > 0) { const aliasNames = cmd.metadata.aliases.map((a) => cmd.namespace ? `${cmd.namespace}:${a}` : a); parts.push(`(aliases: ${aliasNames.join(', ')})`); } return parts.join(' '); } function CustomCommands({ commands }) { const { colors } = useTheme(); // Sort commands alphabetically by full name const sortedCommands = [...commands].sort((a, b) => a.fullName.localeCompare(b.fullName)); // Separate auto-injectable commands (with triggers/tags) from manual-only const autoInjectable = sortedCommands.filter(cmd => cmd.metadata.triggers?.length || cmd.metadata.tags?.length); const manualOnly = sortedCommands.filter(cmd => !cmd.metadata.triggers?.length && !cmd.metadata.tags?.length); return (_jsx(TitledBoxWithPreferences, { title: "Custom Commands", width: 75, borderColor: colors.primary, paddingX: 2, paddingY: 1, flexDirection: "column", marginBottom: 1, children: commands.length === 0 ? (_jsxs(_Fragment, { children: [_jsx(Box, { marginBottom: 1, children: _jsx(Text, { color: colors.text, bold: true, children: "No custom commands found" }) }), _jsx(Text, { color: colors.text, children: "To create custom commands:" }), _jsxs(Text, { color: colors.secondary, children: ["1. Create a ", _jsx(Text, { color: colors.primary, children: ".nanocoder/commands" }), ' ', "directory in your project"] }), _jsxs(Text, { color: colors.secondary, children: ["2. Add ", _jsx(Text, { color: colors.primary, children: ".md" }), " files with command prompts"] }), _jsx(Text, { color: colors.secondary, children: "3. Optionally add frontmatter for metadata:" }), _jsx(Box, { marginTop: 1, marginBottom: 1, children: _jsxs(Text, { color: colors.secondary, children: [`---\n`, `description: Generate unit tests\n`, `aliases: [test, unittest]\n`, `parameters: [filename]\n`, `tags: [testing, quality]\n`, `triggers: [write tests, unit test]\n`, `---\n`, `Generate comprehensive unit tests for {{filename}}...`] }) })] })) : (_jsxs(_Fragment, { children: [manualOnly.length > 0 && (_jsxs(_Fragment, { children: [_jsx(Box, { marginBottom: 1, children: _jsxs(Text, { color: colors.text, children: ["Found ", manualOnly.length, " custom command", manualOnly.length !== 1 ? 's' : '', ":"] }) }), manualOnly.map((cmd, index) => (_jsxs(Text, { color: colors.text, children: ["\u2022 ", formatCommand(cmd)] }, index)))] })), autoInjectable.length > 0 && (_jsxs(_Fragment, { children: [manualOnly.length > 0 && _jsx(Box, { marginTop: 1 }), _jsx(Box, { marginBottom: 1, children: _jsxs(Text, { color: colors.text, bold: true, children: ["Auto-injectable (", autoInjectable.length, "):"] }) }), autoInjectable.map((cmd, index) => { const tokenEst = cmd.metadata.estimatedTokens ? ` (~${cmd.metadata.estimatedTokens} tokens)` : ''; return (_jsxs(Box, { flexDirection: "column", children: [_jsxs(Text, { color: colors.text, children: ["\u2022 ", formatCommand(cmd), tokenEst] }), cmd.metadata.tags?.length && (_jsxs(Text, { color: colors.secondary, children: [' ', "Tags:", ' ', cmd.metadata.tags .map((t) => `\`${t}\``) .join(', ')] }))] }, index)); })] }))] })) })); } function showCommandDetails(command) { let output = `${command.fullName}\n`; if (command.metadata.category) output += `Category: ${command.metadata.category} `; if (command.metadata.version) output += `Version: ${command.metadata.version} `; if (command.metadata.author) output += `Author: ${command.metadata.author}`; output += '\n'; output += `Source: ${command.source ?? 'project'} (${command.path})\n\n`; if (command.metadata.description) { output += `${command.metadata.description}\n\n`; } if (command.metadata.examples?.length) { output += 'Examples:\n'; for (const ex of command.metadata.examples) { output += ` - ${ex}\n`; } output += '\n'; } if (command.loadedResources?.length) { output += 'Resources:\n'; for (const r of command.loadedResources) { output += ` • ${r.name} (${r.type})${r.executable ? ' [executable]' : ''}\n`; } output += '\n'; } if (command.metadata.references?.length) { output += `References: ${command.metadata.references.join(', ')}\n\n`; } if (command.lastModified) { output += `Last modified: ${command.lastModified.toLocaleDateString()}`; } return React.createElement(InfoMessage, { key: `commands-show-${Date.now()}`, message: output, hideBox: true, }); } export const commandsCommand = { name: 'custom-commands', description: 'List all custom commands. Subcommands: show <name>, refresh, create <name>', handler: (args) => { const loader = new CustomCommandLoader(); loader.loadCommands(); const sub = args[0]; if (sub === 'show') { const name = args[1] ?? ''; if (!name) { return Promise.resolve(React.createElement(InfoMessage, { key: `commands-${Date.now()}`, message: 'Usage: /commands show <command-name>', hideBox: true, })); } const command = loader.getCommand(name); if (!command) { return Promise.resolve(React.createElement(InfoMessage, { key: `commands-${Date.now()}`, message: `Command "${name}" not found. Use /commands to list available commands.`, hideBox: true, })); } return Promise.resolve(showCommandDetails(command)); } if (sub === 'refresh') { loader.loadCommands(); return Promise.resolve(React.createElement(InfoMessage, { key: `commands-${Date.now()}`, message: 'Commands cache refreshed.', hideBox: true, })); } if (sub === 'create') { return Promise.resolve(React.createElement(InfoMessage, { key: `commands-${Date.now()}`, message: 'Usage: /commands create <name>\nExample: /commands create review-code\n\nThis creates a new command file and starts an AI-assisted session to write its content.', hideBox: true, })); } const commands = loader.getAllCommands() || []; return Promise.resolve(React.createElement(CustomCommands, { key: `custom-commands-${Date.now()}`, commands: commands, })); }, }; //# sourceMappingURL=custom-commands.js.map