@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
JavaScript
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