@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
192 lines (177 loc) • 9.26 kB
JavaScript
import { jsx as _jsx, jsxs as _jsxs, Fragment as _Fragment } from "react/jsx-runtime";
import { existsSync, mkdirSync, writeFileSync } from 'fs';
import { Box, Text } from 'ink';
import { join } from 'path';
import React from 'react';
import { ErrorMessage } from '../components/message-box.js';
import { TitledBoxWithPreferences } from '../components/ui/titled-box.js';
import { getColors } from '../config/index.js';
import { useTerminalWidth } from '../hooks/useTerminalWidth.js';
import { AgentsTemplateGenerator } from '../init/agents-template-generator.js';
import { ExistingRulesExtractor } from '../init/existing-rules-extractor.js';
import { ProjectAnalyzer } from '../init/project-analyzer.js';
function InitSuccess({ created, analysis, }) {
const colors = getColors();
const boxWidth = useTerminalWidth();
return (_jsxs(TitledBoxWithPreferences, { title: "Project Initialized", width: boxWidth, borderColor: colors.primary, paddingX: 2, paddingY: 1, flexDirection: "column", marginBottom: 1, children: [_jsx(Box, { marginBottom: 1, children: _jsx(Text, { color: colors.primary, bold: true, children: "\u2713 Nanocoder project initialized successfully!" }) }), analysis && (_jsxs(_Fragment, { children: [_jsx(Box, { marginBottom: 1, children: _jsx(Text, { color: colors.text, bold: true, children: "Project Analysis:" }) }), _jsxs(Text, { color: colors.secondary, children: ["\u2022 Type: ", analysis.projectType] }), _jsxs(Text, { color: colors.secondary, children: ["\u2022 Primary Language: ", analysis.primaryLanguage] }), analysis.frameworks.length > 0 && (_jsxs(Text, { color: colors.secondary, children: ["\u2022 Frameworks: ", analysis.frameworks.slice(0, 3).join(', ')] })), _jsxs(Text, { color: colors.secondary, children: ["\u2022 Files Analyzed: ", analysis.totalFiles] }), _jsx(Box, { marginBottom: 1 })] })), _jsx(Box, { marginBottom: 1, children: _jsx(Text, { color: colors.text, bold: true, children: "Files Created:" }) }), created.map((item, index) => (_jsxs(Text, { color: colors.secondary, children: ["\u2022 ", item] }, index))), _jsxs(Box, { marginTop: 1, flexDirection: "column", children: [_jsx(Box, { marginBottom: 1, children: _jsx(Text, { color: colors.text, children: "Your project is now ready for AI-assisted development!" }) }), _jsx(Text, { color: colors.secondary, children: "The AGENTS.md file will help AI understand your project context." })] })] }));
}
function InitError({ message }) {
return _jsx(ErrorMessage, { hideBox: true, message: `✗ ${message}` });
}
// Enhanced example commands based on detected project type
const getExampleCommands = (projectType, primaryLanguage) => {
const baseCommands = {
'review.md': `---
description: Review code and suggest improvements
aliases: [code-review, cr]
parameters: [files]
---
Review the code in {{files}} and provide detailed feedback on:
1. Code quality and best practices
2. Potential bugs or issues
3. Performance considerations
4. Readability and maintainability
5. Security concerns
Provide specific, actionable suggestions for improvement.`,
'test.md': `---
description: Generate comprehensive unit tests
aliases: [unittest, test-gen]
parameters: [filename]
---
Generate comprehensive unit tests for {{filename}}.
Consider:
1. Test all public functions and methods
2. Include edge cases and error scenarios
3. Use appropriate mocking where needed
4. Follow existing test framework conventions
5. Ensure good test coverage
If no filename provided, suggest which files need tests.`,
};
// Add language/framework-specific commands
const additionalCommands = {};
if (primaryLanguage === 'JavaScript' || primaryLanguage === 'TypeScript') {
additionalCommands['refactor.md'] = `---
description: Refactor JavaScript/TypeScript code
aliases: [refactor-js, clean]
parameters: [target]
---
Refactor {{target}} to improve:
1. Code structure and organization
2. Modern ES6+ syntax usage
3. Performance optimizations
4. Type safety (for TypeScript)
5. Reusability and maintainability
Follow current project conventions and patterns.`;
}
if (primaryLanguage === 'Python') {
additionalCommands['optimize.md'] = `---
description: Optimize Python code for performance
aliases: [perf, optimize-py]
parameters: [file]
---
Analyze and optimize {{file}} for:
1. Algorithm efficiency
2. Memory usage
3. Pythonic patterns
4. Performance bottlenecks
5. Code readability
Follow PEP 8 and project conventions.`;
}
if (projectType.includes('Web')) {
additionalCommands['component.md'] = `---
description: Create a new UI component
aliases: [comp, ui]
parameters: [name, type]
---
Create a new {{type}} component named {{name}} that:
1. Follows project component patterns
2. Includes proper TypeScript types
3. Has responsive design considerations
4. Includes basic styling structure
5. Has proper prop validation
Make it reusable and well-documented.`;
}
return { ...baseCommands, ...additionalCommands };
};
export const initCommand = {
name: 'init',
description: 'Initialize nanocoder configuration and analyze project structure. Use --force to regenerate AGENTS.md.',
handler: (args, _messages, _metadata) => {
const cwd = process.cwd();
const created = [];
const forceRegenerate = args.includes('--force') || args.includes('-f');
try {
// Check if already initialized
const agentsPath = join(cwd, 'AGENTS.md');
const nanocoderDir = join(cwd, '.nanocoder');
// Check for existing initialization
const hasAgents = existsSync(agentsPath);
const hasNanocoder = existsSync(nanocoderDir);
if (hasAgents && hasNanocoder && !forceRegenerate) {
return Promise.resolve(React.createElement(InitError, {
key: `init-error-${Date.now()}`,
message: 'Project already initialized. Found AGENTS.md and .nanocoder/ directory. Use /init --force to regenerate.',
}));
}
// Show progress indicator for analysis
// Note: In a real implementation, we'd want to show this as a loading state
// For now, we'll do the analysis synchronously
// Analyze the project
const analyzer = new ProjectAnalyzer(cwd);
const analysis = analyzer.analyze();
// Extract existing AI configuration files (skip AGENTS.md when force regenerating)
const rulesExtractor = new ExistingRulesExtractor(cwd, forceRegenerate);
const existingRules = rulesExtractor.extractExistingRules();
// Create AGENTS.md based on analysis and existing rules
if (!hasAgents || forceRegenerate) {
const agentsContent = AgentsTemplateGenerator.generateAgentsMd(analysis, existingRules);
writeFileSync(agentsPath, agentsContent);
created.push(hasAgents ? 'AGENTS.md (regenerated)' : 'AGENTS.md');
// Report found existing rules
if (existingRules.length > 0) {
const sourceFiles = existingRules.map(r => r.source).join(', ');
created.push(`↳ Merged content from: ${sourceFiles}`);
}
}
// Create .nanocoder directory structure
if (!hasNanocoder) {
mkdirSync(nanocoderDir, { recursive: true });
created.push('.nanocoder/');
}
const commandsDir = join(nanocoderDir, 'commands');
if (!existsSync(commandsDir)) {
mkdirSync(commandsDir, { recursive: true });
created.push('.nanocoder/commands/');
}
// Create example custom commands based on project analysis
const exampleCommands = getExampleCommands(analysis.projectType, analysis.languages.primary?.name || 'Unknown');
for (const [filename, content] of Object.entries(exampleCommands)) {
const filePath = join(commandsDir, filename);
if (!existsSync(filePath)) {
writeFileSync(filePath, content);
created.push(`.nanocoder/commands/${filename}`);
}
}
// Prepare analysis summary for display
const analysisSummary = {
projectType: analysis.projectType,
primaryLanguage: analysis.languages.primary?.name || 'Unknown',
frameworks: analysis.dependencies.frameworks.map((f) => f.name),
totalFiles: analysis.structure.scannedFiles,
};
return Promise.resolve(React.createElement(InitSuccess, {
key: `init-success-${Date.now()}`,
created,
analysis: analysisSummary,
}));
}
catch (error) {
const errorMessage = error instanceof Error ? error.message : 'Unknown error';
return Promise.resolve(React.createElement(InitError, {
key: `init-error-${Date.now()}`,
message: `Failed to initialize project: ${errorMessage}`,
}));
}
},
};
//# sourceMappingURL=init.js.map