@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
141 lines • 5.99 kB
JavaScript
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
/**
* Git Add Tool
*
* Stage files for commit.
*/
import { Box, Text } from 'ink';
import { getCurrentMode } from '../../context/mode-context.js';
import { useTheme } from '../../hooks/useTheme.js';
import { jsonSchema, tool } from '../../types/core.js';
import { execGit, formatStatusChar, getDiffStats, parseGitStatus } from './utils.js';
// ============================================================================
// Execution
// ============================================================================
const executeGitAdd = async (args) => {
try {
const gitArgs = ['add'];
if (args.all) {
gitArgs.push('-A');
}
else if (args.update) {
gitArgs.push('-u');
}
else if (args.files && args.files.length > 0) {
gitArgs.push(...args.files);
}
else {
// Default to staging all changes
gitArgs.push('-A');
}
await execGit(gitArgs);
// Get the new status to show what was staged
const statusOutput = await execGit(['status', '--porcelain']);
const { staged } = parseGitStatus(statusOutput);
// Get diff stats for staged files
const stats = await getDiffStats(true);
for (const file of staged) {
const fileStats = stats.get(file.path);
if (fileStats) {
file.additions = fileStats.additions;
file.deletions = fileStats.deletions;
}
}
if (staged.length === 0) {
return 'No changes to stage.';
}
const totalAdditions = staged.reduce((sum, f) => sum + f.additions, 0);
const totalDeletions = staged.reduce((sum, f) => sum + f.deletions, 0);
const lines = [];
lines.push(`Staged ${staged.length} file(s) (+${totalAdditions}, -${totalDeletions}):`);
lines.push('');
for (const file of staged.slice(0, 15)) {
const char = formatStatusChar(file.status);
const fileStats = file.additions || file.deletions
? ` (+${file.additions}, -${file.deletions})`
: '';
lines.push(` ${char} ${file.path}${fileStats}`);
}
if (staged.length > 15) {
lines.push(` ... and ${staged.length - 15} more files`);
}
return lines.join('\n');
}
catch (error) {
return `Error: ${error instanceof Error ? error.message : 'Unknown error'}`;
}
};
// ============================================================================
// Tool Definition
// ============================================================================
const gitAddCoreTool = tool({
description: 'Stage files for commit. Use files array for specific files, all=true for all changes including untracked, or update=true for only tracked files.',
inputSchema: jsonSchema({
type: 'object',
properties: {
files: {
type: 'array',
items: { type: 'string' },
description: 'Specific files or patterns to stage',
},
all: {
type: 'boolean',
description: 'Stage all changes including untracked files (-A)',
},
update: {
type: 'boolean',
description: 'Stage only already tracked files (-u)',
},
},
required: [],
}),
// STANDARD - requires approval in normal mode, skipped in auto-accept
needsApproval: () => {
const mode = getCurrentMode();
return mode === 'normal';
},
execute: async (args, _options) => {
return await executeGitAdd(args);
},
});
// ============================================================================
// Formatter
// ============================================================================
function GitAddFormatter({ args, result, }) {
const { colors } = useTheme();
// Parse result for display
let fileCount = 0;
let additions = 0;
let deletions = 0;
if (result) {
const countMatch = result.match(/Staged (\d+) file/);
if (countMatch)
fileCount = parseInt(countMatch[1], 10);
const statsMatch = result.match(/\(\+(\d+), -(\d+)\)/);
if (statsMatch) {
additions = parseInt(statsMatch[1], 10);
deletions = parseInt(statsMatch[2], 10);
}
}
// Determine mode
let mode = 'all';
if (args.files && args.files.length > 0) {
mode = `${args.files.length} specific file(s)`;
}
else if (args.update) {
mode = 'tracked files only';
}
return (_jsxs(Box, { flexDirection: "column", marginBottom: 1, children: [_jsx(Text, { color: colors.tool, children: "\u2692 git_add" }), _jsxs(Box, { children: [_jsx(Text, { color: colors.secondary, children: "Mode: " }), _jsx(Text, { color: colors.text, children: mode })] }), fileCount > 0 && (_jsxs(Box, { children: [_jsx(Text, { color: colors.secondary, children: "Staging: " }), _jsxs(Text, { color: colors.text, children: [fileCount, " files "] }), _jsxs(Text, { color: colors.success, children: ["(+", additions] }), _jsx(Text, { color: colors.text, children: ", " }), _jsxs(Text, { color: colors.error, children: ["-", deletions] }), _jsx(Text, { color: colors.text, children: ")" })] })), result?.includes('No changes') && (_jsx(Box, { children: _jsx(Text, { color: colors.warning, children: "No changes to stage" }) }))] }));
}
const formatter = (args, result) => {
return _jsx(GitAddFormatter, { args: args, result: result });
};
// ============================================================================
// Export
// ============================================================================
export const gitAddTool = {
name: 'git_add',
tool: gitAddCoreTool,
formatter,
};
//# sourceMappingURL=git-add.js.map